mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-08-15 01:15:42 -04:00
checkpoint
This commit is contained in:
parent
c516323e7d
commit
31f562119a
70 changed files with 1174 additions and 817 deletions
|
@ -1,18 +0,0 @@
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class DefaultAppBar extends AppBar {
|
||||
DefaultAppBar(
|
||||
{required super.title, super.key, Widget? leading, super.actions})
|
||||
: super(
|
||||
leading: leading ??
|
||||
Container(
|
||||
margin: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(32),
|
||||
shape: BoxShape.circle),
|
||||
child:
|
||||
SvgPicture.asset('assets/images/vlogo.svg', height: 32)
|
||||
.paddingAll(4)));
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../providers/connection_state.dart';
|
||||
import '../tools/tools.dart';
|
||||
|
||||
xxx move to feature level
|
||||
|
||||
class SignalStrengthMeterWidget extends Widget {
|
||||
const SignalStrengthMeterWidget({super.key});
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
const iconSize = 16.0;
|
||||
final connState = ref.watch(connectionStateProvider);
|
||||
|
||||
late final double value;
|
||||
late final Color color;
|
||||
late final Color inactiveColor;
|
||||
switch (connState.attachment.state) {
|
||||
case AttachmentState.detached:
|
||||
return Icon(Icons.signal_cellular_nodata,
|
||||
size: iconSize, color: scale.grayScale.text);
|
||||
case AttachmentState.detaching:
|
||||
return Icon(Icons.signal_cellular_off,
|
||||
size: iconSize, color: scale.grayScale.text);
|
||||
case AttachmentState.attaching:
|
||||
value = 0;
|
||||
color = scale.primaryScale.text;
|
||||
case AttachmentState.attachedWeak:
|
||||
value = 1;
|
||||
color = scale.primaryScale.text;
|
||||
case AttachmentState.attachedStrong:
|
||||
value = 2;
|
||||
color = scale.primaryScale.text;
|
||||
case AttachmentState.attachedGood:
|
||||
value = 3;
|
||||
color = scale.primaryScale.text;
|
||||
case AttachmentState.fullyAttached:
|
||||
value = 4;
|
||||
color = scale.primaryScale.text;
|
||||
case AttachmentState.overAttached:
|
||||
value = 4;
|
||||
color = scale.secondaryScale.subtleText;
|
||||
}
|
||||
inactiveColor = scale.grayScale.subtleText;
|
||||
|
||||
return GestureDetector(
|
||||
onLongPress: () async {
|
||||
await context.push('/developer');
|
||||
},
|
||||
child: SignalStrengthIndicator.bars(
|
||||
value: value,
|
||||
activeColor: color,
|
||||
inactiveColor: inactiveColor,
|
||||
size: iconSize,
|
||||
barCount: 4,
|
||||
spacing: 1,
|
||||
));
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../providers/window_control.dart';
|
||||
import 'home.dart';
|
||||
|
||||
class ChatOnlyPage extends StatefulWidget {
|
||||
const ChatOnlyPage({super.key});
|
||||
|
||||
@override
|
||||
ChatOnlyPageState createState() => ChatOnlyPageState();
|
||||
}
|
||||
|
||||
class ChatOnlyPageState extends ConsumerState<ChatOnlyPage>
|
||||
with TickerProviderStateMixin {
|
||||
final _unfocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
setState(() {});
|
||||
await ref.read(windowControlProvider.notifier).changeWindowSetup(
|
||||
TitleBarStyle.normal, OrientationCapability.normal);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_unfocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.watch(windowControlProvider);
|
||||
|
||||
return SafeArea(
|
||||
child: GestureDetector(
|
||||
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
|
||||
child: HomePage.buildChatComponent(context, ref),
|
||||
));
|
||||
}
|
||||
}
|
|
@ -1,256 +0,0 @@
|
|||
import 'package:ansicolor/ansicolor.dart';
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:cool_dropdown/cool_dropdown.dart';
|
||||
import 'package:cool_dropdown/models/cool_dropdown_item.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:loggy/loggy.dart';
|
||||
import 'package:quickalert/quickalert.dart';
|
||||
import 'package:xterm/xterm.dart';
|
||||
|
||||
import '../../tools/tools.dart';
|
||||
import '../../../packages/veilid_support/veilid_support.dart';
|
||||
|
||||
final globalDebugTerminal = Terminal(
|
||||
maxLines: 50000,
|
||||
);
|
||||
|
||||
const kDefaultTerminalStyle = TerminalStyle(
|
||||
fontSize: 11,
|
||||
// height: 1.2,
|
||||
fontFamily: 'Source Code Pro');
|
||||
|
||||
class DeveloperPage extends StatefulWidget {
|
||||
const DeveloperPage({super.key});
|
||||
|
||||
@override
|
||||
DeveloperPageState createState() => DeveloperPageState();
|
||||
}
|
||||
|
||||
class DeveloperPageState extends ConsumerState<DeveloperPage> {
|
||||
final _terminalController = TerminalController();
|
||||
final _debugCommandController = TextEditingController();
|
||||
final _logLevelController = DropdownController(duration: 250.ms);
|
||||
final List<CoolDropdownItem<LogLevel>> _logLevelDropdownItems = [];
|
||||
var _logLevelDropDown = log.level.logLevel;
|
||||
var _showEllet = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_terminalController.addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
for (var i = 0; i < logLevels.length; i++) {
|
||||
_logLevelDropdownItems.add(CoolDropdownItem<LogLevel>(
|
||||
label: logLevelName(logLevels[i]),
|
||||
icon: Text(logLevelEmoji(logLevels[i])),
|
||||
value: logLevels[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void _debugOut(String out) {
|
||||
final pen = AnsiPen()..cyan(bold: true);
|
||||
final colorOut = pen(out);
|
||||
debugPrint(colorOut);
|
||||
globalDebugTerminal.write(colorOut.replaceAll('\n', '\r\n'));
|
||||
}
|
||||
|
||||
Future<void> _sendDebugCommand(String debugCommand) async {
|
||||
if (debugCommand == 'ellet') {
|
||||
setState(() {
|
||||
_showEllet = !_showEllet;
|
||||
});
|
||||
return;
|
||||
}
|
||||
_debugOut('DEBUG >>>\n$debugCommand\n');
|
||||
try {
|
||||
final out = await Veilid.instance.debug(debugCommand);
|
||||
_debugOut('<<< DEBUG\n$out\n');
|
||||
} on Exception catch (e, st) {
|
||||
_debugOut('<<< ERROR\n$e\n<<< STACK\n$st');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clear(BuildContext context) async {
|
||||
globalDebugTerminal.buffer.clear();
|
||||
if (context.mounted) {
|
||||
showInfoToast(context, translate('developer.cleared'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> copySelection(BuildContext context) async {
|
||||
final selection = _terminalController.selection;
|
||||
if (selection != null) {
|
||||
final text = globalDebugTerminal.buffer.getText(selection);
|
||||
_terminalController.clearSelection();
|
||||
await Clipboard.setData(ClipboardData(text: text));
|
||||
if (context.mounted) {
|
||||
showInfoToast(context, translate('developer.copied'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final textTheme = theme.textTheme;
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// if (!_isScrolling && _wantsBottom) {
|
||||
// _scrollToBottom();
|
||||
// }
|
||||
// });
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back, color: scale.primaryScale.text),
|
||||
onPressed: () => GoRouterHelper(context).pop(),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.copy),
|
||||
color: scale.primaryScale.text,
|
||||
disabledColor: scale.grayScale.subtleText,
|
||||
onPressed: _terminalController.selection == null
|
||||
? null
|
||||
: () async {
|
||||
await copySelection(context);
|
||||
}),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear_all),
|
||||
color: scale.primaryScale.text,
|
||||
disabledColor: scale.grayScale.subtleText,
|
||||
onPressed: () async {
|
||||
await QuickAlert.show(
|
||||
context: context,
|
||||
type: QuickAlertType.confirm,
|
||||
title: translate('developer.are_you_sure_clear'),
|
||||
textColor: scale.primaryScale.text,
|
||||
confirmBtnColor: scale.primaryScale.elementBackground,
|
||||
backgroundColor: scale.primaryScale.subtleBackground,
|
||||
headerBackgroundColor: scale.primaryScale.background,
|
||||
confirmBtnText: translate('button.ok'),
|
||||
cancelBtnText: translate('button.cancel'),
|
||||
onConfirmBtnTap: () async {
|
||||
Navigator.pop(context);
|
||||
if (context.mounted) {
|
||||
await clear(context);
|
||||
}
|
||||
});
|
||||
}),
|
||||
CoolDropdown<LogLevel>(
|
||||
controller: _logLevelController,
|
||||
defaultItem: _logLevelDropdownItems
|
||||
.singleWhere((x) => x.value == _logLevelDropDown),
|
||||
onChange: (value) {
|
||||
setState(() {
|
||||
_logLevelDropDown = value;
|
||||
Loggy('').level = getLogOptions(value);
|
||||
setVeilidLogLevel(value);
|
||||
_logLevelController.close();
|
||||
});
|
||||
},
|
||||
resultOptions: ResultOptions(
|
||||
width: 64,
|
||||
height: 40,
|
||||
render: ResultRender.icon,
|
||||
textStyle: textTheme.labelMedium!
|
||||
.copyWith(color: scale.primaryScale.text),
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||
openBoxDecoration: BoxDecoration(
|
||||
color: scale.primaryScale.activeElementBackground),
|
||||
boxDecoration:
|
||||
BoxDecoration(color: scale.primaryScale.elementBackground),
|
||||
),
|
||||
dropdownOptions: DropdownOptions(
|
||||
width: 160,
|
||||
align: DropdownAlign.right,
|
||||
duration: 150.ms,
|
||||
color: scale.primaryScale.elementBackground,
|
||||
borderSide: BorderSide(color: scale.primaryScale.border),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||
),
|
||||
dropdownTriangleOptions: const DropdownTriangleOptions(
|
||||
align: DropdownTriangleAlign.right),
|
||||
dropdownItemOptions: DropdownItemOptions(
|
||||
selectedTextStyle: textTheme.labelMedium!
|
||||
.copyWith(color: scale.primaryScale.text),
|
||||
textStyle: textTheme.labelMedium!
|
||||
.copyWith(color: scale.primaryScale.text),
|
||||
selectedBoxDecoration: BoxDecoration(
|
||||
color: scale.primaryScale.activeElementBackground),
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||
selectedPadding: const EdgeInsets.fromLTRB(8, 4, 8, 4)),
|
||||
dropdownList: _logLevelDropdownItems,
|
||||
)
|
||||
],
|
||||
title: Text(translate('developer.title'),
|
||||
style:
|
||||
textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.bold)),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(children: [
|
||||
Stack(alignment: AlignmentDirectional.center, children: [
|
||||
Image.asset('assets/images/ellet.png'),
|
||||
TerminalView(globalDebugTerminal,
|
||||
textStyle: kDefaultTerminalStyle,
|
||||
controller: _terminalController,
|
||||
//autofocus: true,
|
||||
backgroundOpacity: _showEllet ? 0.75 : 1.0,
|
||||
onSecondaryTapDown: (details, offset) async {
|
||||
await copySelection(context);
|
||||
})
|
||||
]).expanded(),
|
||||
TextField(
|
||||
controller: _debugCommandController,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.fromLTRB(8, 2, 8, 2),
|
||||
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),
|
||||
onPressed: _debugCommandController.text.isEmpty
|
||||
? null
|
||||
: () async {
|
||||
final debugCommand = _debugCommandController.text;
|
||||
_debugCommandController.clear();
|
||||
await _sendDebugCommand(debugCommand);
|
||||
},
|
||||
)),
|
||||
onChanged: (_) {
|
||||
setState(() => {});
|
||||
},
|
||||
onSubmitted: (debugCommand) async {
|
||||
_debugCommandController.clear();
|
||||
await _sendDebugCommand(debugCommand);
|
||||
},
|
||||
).paddingAll(4)
|
||||
])));
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty<TerminalController>(
|
||||
'terminalController', _terminalController))
|
||||
..add(
|
||||
DiagnosticsProperty<LogLevel>('logLevelDropDown', _logLevelDropDown));
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class ContactsPage extends ConsumerWidget {
|
||||
const ContactsPage({super.key});
|
||||
static const path = '/contacts';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) => const Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Contacts Page'),
|
||||
// ElevatedButton(
|
||||
// onPressed: () async {
|
||||
// ref.watch(authNotifierProvider.notifier).login(
|
||||
// "myEmail",
|
||||
// "myPassword",
|
||||
// );
|
||||
// },
|
||||
// child: const Text("Login"),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -1,269 +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_animate/flutter_animate.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../components/chat_component.dart';
|
||||
import '../../components/empty_chat_widget.dart';
|
||||
import '../../components/profile_widget.dart';
|
||||
import '../../entities/local_account.dart';
|
||||
import '../providers/account.dart';
|
||||
import '../providers/chat.dart';
|
||||
import '../providers/contact.dart';
|
||||
import '../../local_accounts/local_accounts.dart';
|
||||
import '../providers/logins.dart';
|
||||
import '../providers/window_control.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import '../../../packages/veilid_support/veilid_support.dart';
|
||||
import 'main_pager/main_pager.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
HomePageState createState() => HomePageState();
|
||||
|
||||
static Widget buildChatComponent(BuildContext context, WidgetRef ref) {
|
||||
final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
||||
const IListConst([]);
|
||||
|
||||
final activeChat = ref.watch(activeChatStateProvider);
|
||||
if (activeChat == null) {
|
||||
return const EmptyChatWidget();
|
||||
}
|
||||
|
||||
final activeAccountInfo =
|
||||
ref.watch(fetchActiveAccountProvider).asData?.value;
|
||||
if (activeAccountInfo == null) {
|
||||
return const EmptyChatWidget();
|
||||
}
|
||||
|
||||
final activeChatContactIdx = contactList.indexWhere(
|
||||
(c) =>
|
||||
proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) ==
|
||||
activeChat,
|
||||
);
|
||||
if (activeChatContactIdx == -1) {
|
||||
ref.read(activeChatStateProvider.notifier).state = null;
|
||||
return const EmptyChatWidget();
|
||||
}
|
||||
final activeChatContact = contactList[activeChatContactIdx];
|
||||
|
||||
return ChatComponent(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
activeChat: activeChat,
|
||||
activeChatContact: activeChatContact);
|
||||
}
|
||||
}
|
||||
|
||||
class HomePageState extends ConsumerState<HomePage>
|
||||
with TickerProviderStateMixin {
|
||||
final _unfocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
setState(() {});
|
||||
await ref.read(windowControlProvider.notifier).changeWindowSetup(
|
||||
TitleBarStyle.normal, OrientationCapability.normal);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_unfocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget buildAccountList() {
|
||||
return const Column(children: [
|
||||
Center(child: Text('Small Profile')),
|
||||
Center(child: Text('Contact invitations')),
|
||||
Center(child: Text('Contacts'))
|
||||
]);
|
||||
}
|
||||
|
||||
Widget buildUnlockAccount(
|
||||
BuildContext context,
|
||||
IList<LocalAccount> localAccounts,
|
||||
// ignore: prefer_expression_function_bodies
|
||||
) {
|
||||
return const Center(child: Text('unlock account'));
|
||||
}
|
||||
|
||||
/// We have an active, unlocked, user login
|
||||
Widget buildReadyAccount(
|
||||
BuildContext context,
|
||||
IList<LocalAccount> localAccounts,
|
||||
TypedKey activeUserLogin,
|
||||
proto.Account account) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
return Column(children: <Widget>[
|
||||
Row(children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
color: scale.secondaryScale.text,
|
||||
constraints: const BoxConstraints.expand(height: 64, width: 64),
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.all(scale.secondaryScale.border),
|
||||
shape: MaterialStateProperty.all(const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16))))),
|
||||
tooltip: translate('app_bar.settings_tooltip'),
|
||||
onPressed: () async {
|
||||
context.go('/home/settings');
|
||||
}).paddingLTRB(0, 0, 8, 0),
|
||||
ProfileWidget(
|
||||
name: account.profile.name,
|
||||
pronouns: account.profile.pronouns,
|
||||
).expanded(),
|
||||
]).paddingAll(8),
|
||||
MainPager(
|
||||
localAccounts: localAccounts,
|
||||
activeUserLogin: activeUserLogin,
|
||||
account: account)
|
||||
.expanded()
|
||||
]);
|
||||
}
|
||||
|
||||
Widget buildUserPanel() {
|
||||
final localAccountsV = ref.watch(localAccountsProvider);
|
||||
final loginsV = ref.watch(loginsProvider);
|
||||
|
||||
if (!localAccountsV.hasValue || !loginsV.hasValue) {
|
||||
return waitingPage(context);
|
||||
}
|
||||
final localAccounts = localAccountsV.requireValue;
|
||||
final logins = loginsV.requireValue;
|
||||
|
||||
final activeUserLogin = logins.activeUserLogin;
|
||||
if (activeUserLogin == null) {
|
||||
// If no logged in user is active, show the list of account
|
||||
return buildAccountList();
|
||||
}
|
||||
final accountV = ref
|
||||
.watch(fetchAccountProvider(accountMasterRecordKey: activeUserLogin));
|
||||
if (!accountV.hasValue) {
|
||||
return waitingPage(context);
|
||||
}
|
||||
final account = accountV.requireValue;
|
||||
switch (account.status) {
|
||||
case AccountInfoStatus.noAccount:
|
||||
Future.delayed(0.ms, () async {
|
||||
await showErrorModal(context, translate('home.missing_account_title'),
|
||||
translate('home.missing_account_text'));
|
||||
// Delete account
|
||||
await ref
|
||||
.read(localAccountsProvider.notifier)
|
||||
.deleteLocalAccount(activeUserLogin);
|
||||
// Switch to no active user login
|
||||
await ref.read(loginsProvider.notifier).switchToAccount(null);
|
||||
});
|
||||
return waitingPage(context);
|
||||
case AccountInfoStatus.accountInvalid:
|
||||
Future.delayed(0.ms, () async {
|
||||
await showErrorModal(context, translate('home.invalid_account_title'),
|
||||
translate('home.invalid_account_text'));
|
||||
// Delete account
|
||||
await ref
|
||||
.read(localAccountsProvider.notifier)
|
||||
.deleteLocalAccount(activeUserLogin);
|
||||
// Switch to no active user login
|
||||
await ref.read(loginsProvider.notifier).switchToAccount(null);
|
||||
});
|
||||
return waitingPage(context);
|
||||
case AccountInfoStatus.accountLocked:
|
||||
// Show unlock widget
|
||||
return buildUnlockAccount(context, localAccounts);
|
||||
case AccountInfoStatus.accountReady:
|
||||
return buildReadyAccount(
|
||||
context,
|
||||
localAccounts,
|
||||
activeUserLogin,
|
||||
account.account!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget buildPhone(BuildContext context) {
|
||||
return Material(color: Colors.transparent, child: buildUserPanel());
|
||||
}
|
||||
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget buildTabletLeftPane(BuildContext context) {
|
||||
//
|
||||
return Material(color: Colors.transparent, child: buildUserPanel());
|
||||
}
|
||||
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget buildTabletRightPane(BuildContext context) {
|
||||
//
|
||||
return HomePage.buildChatComponent(context, ref);
|
||||
}
|
||||
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget buildTablet(BuildContext context) {
|
||||
final w = MediaQuery.of(context).size.width;
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
final children = [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 300, maxWidth: 300),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: w / 2),
|
||||
child: buildTabletLeftPane(context))),
|
||||
SizedBox(
|
||||
width: 2,
|
||||
height: double.infinity,
|
||||
child: ColoredBox(color: scale.primaryScale.hoverBorder)),
|
||||
Expanded(child: buildTabletRightPane(context)),
|
||||
];
|
||||
|
||||
return Row(
|
||||
children: children,
|
||||
);
|
||||
|
||||
// final theme = MultiSplitViewTheme(
|
||||
// data: isDesktop
|
||||
// ? MultiSplitViewThemeData(
|
||||
// dividerThickness: 1,
|
||||
// dividerPainter: DividerPainters.grooved2(thickness: 1))
|
||||
// : MultiSplitViewThemeData(
|
||||
// dividerThickness: 3,
|
||||
// dividerPainter: DividerPainters.grooved2(thickness: 1)),
|
||||
// child: multiSplitView);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.watch(windowControlProvider);
|
||||
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
return SafeArea(
|
||||
child: GestureDetector(
|
||||
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: scale.primaryScale.activeElementBackground),
|
||||
child: responsiveVisibility(
|
||||
context: context,
|
||||
phone: false,
|
||||
)
|
||||
? buildTablet(context)
|
||||
: buildPhone(context),
|
||||
)));
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:radix_colors/radix_colors.dart';
|
||||
|
||||
class IndexPage extends StatelessWidget {
|
||||
const IndexPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final textTheme = theme.textTheme;
|
||||
final monoTextStyle = textTheme.labelSmall!
|
||||
.copyWith(fontFamily: 'Source Code Pro', fontSize: 11);
|
||||
final emojiTextStyle = textTheme.labelSmall!
|
||||
.copyWith(fontFamily: 'Noto Color Emoji', fontSize: 11);
|
||||
|
||||
return Scaffold(
|
||||
body: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: <Color>[
|
||||
RadixColors.dark.plum.step4,
|
||||
RadixColors.dark.plum.step2,
|
||||
])),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Hack to preload fonts
|
||||
Offstage(child: Text('🧱', style: emojiTextStyle)),
|
||||
// Hack to preload fonts
|
||||
Offstage(child: Text('A', style: monoTextStyle)),
|
||||
// Splash Screen
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SvgPicture.asset(
|
||||
'assets/images/icon.svg',
|
||||
)),
|
||||
Expanded(
|
||||
child: SvgPicture.asset(
|
||||
'assets/images/title.svg',
|
||||
))
|
||||
]))),
|
||||
));
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
// ignore_for_file: prefer_const_constructors
|
||||
|
||||
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_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../../../components/contact_invitation_list_widget.dart';
|
||||
import '../../../components/contact_list_widget.dart';
|
||||
import '../../../entities/local_account.dart';
|
||||
import '../../../proto/proto.dart' as proto;
|
||||
import '../../providers/contact.dart';
|
||||
import '../../providers/contact_invite.dart';
|
||||
import '../../../theme/theme.dart';
|
||||
import '../../../tools/tools.dart';
|
||||
import '../../../../packages/veilid_support/veilid_support.dart';
|
||||
|
||||
class AccountPage extends ConsumerStatefulWidget {
|
||||
const AccountPage({
|
||||
required this.localAccounts,
|
||||
required this.activeUserLogin,
|
||||
required this.account,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final IList<LocalAccount> localAccounts;
|
||||
final TypedKey activeUserLogin;
|
||||
final proto.Account account;
|
||||
|
||||
@override
|
||||
AccountPageState createState() => AccountPageState();
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(IterableProperty<LocalAccount>('localAccounts', localAccounts))
|
||||
..add(DiagnosticsProperty<TypedKey>('activeUserLogin', activeUserLogin))
|
||||
..add(DiagnosticsProperty<proto.Account>('account', account));
|
||||
}
|
||||
}
|
||||
|
||||
class AccountPageState extends ConsumerState<AccountPage> {
|
||||
final _unfocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_unfocusNode.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 contactInvitationRecordList =
|
||||
ref.watch(fetchContactInvitationRecordsProvider).asData?.value ??
|
||||
const IListConst([]);
|
||||
final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
||||
const IListConst([]);
|
||||
|
||||
return SizedBox(
|
||||
child: Column(children: <Widget>[
|
||||
if (contactInvitationRecordList.isNotEmpty)
|
||||
ExpansionTile(
|
||||
tilePadding: EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||
backgroundColor: scale.primaryScale.border,
|
||||
collapsedBackgroundColor: scale.primaryScale.border,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
collapsedShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
title: Text(
|
||||
translate('account_page.contact_invitations'),
|
||||
textAlign: TextAlign.center,
|
||||
style: textTheme.titleMedium!
|
||||
.copyWith(color: scale.primaryScale.subtleText),
|
||||
),
|
||||
initiallyExpanded: true,
|
||||
children: [
|
||||
ContactInvitationListWidget(
|
||||
contactInvitationRecordList: contactInvitationRecordList)
|
||||
],
|
||||
).paddingLTRB(8, 0, 8, 8),
|
||||
ContactListWidget(contactList: contactList).expanded(),
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -1,100 +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_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../components/chat_single_contact_list_widget.dart';
|
||||
import '../../../components/empty_chat_list_widget.dart';
|
||||
import '../../../entities/local_account.dart';
|
||||
import '../../../proto/proto.dart' as proto;
|
||||
import '../../providers/account.dart';
|
||||
import '../../providers/chat.dart';
|
||||
import '../../providers/contact.dart';
|
||||
import '../../../local_accounts/local_accounts.dart';
|
||||
import '../../providers/logins.dart';
|
||||
import '../../../tools/tools.dart';
|
||||
import '../../../../packages/veilid_support/veilid_support.dart';
|
||||
|
||||
class ChatsPage extends ConsumerStatefulWidget {
|
||||
const ChatsPage({super.key});
|
||||
|
||||
@override
|
||||
ChatsPageState createState() => ChatsPageState();
|
||||
}
|
||||
|
||||
class ChatsPageState extends ConsumerState<ChatsPage> {
|
||||
final _unfocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_unfocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// We have an active, unlocked, user login
|
||||
Widget buildChatList(
|
||||
BuildContext context,
|
||||
IList<LocalAccount> localAccounts,
|
||||
TypedKey activeUserLogin,
|
||||
proto.Account account,
|
||||
// ignore: prefer_expression_function_bodies
|
||||
) {
|
||||
final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
||||
const IListConst([]);
|
||||
final chatList =
|
||||
ref.watch(fetchChatListProvider).asData?.value ?? const IListConst([]);
|
||||
|
||||
return Column(children: <Widget>[
|
||||
if (chatList.isNotEmpty)
|
||||
ChatSingleContactListWidget(
|
||||
contactList: contactList, chatList: chatList)
|
||||
.expanded(),
|
||||
if (chatList.isEmpty) const EmptyChatListWidget().expanded(),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final localAccountsV = ref.watch(localAccountsProvider);
|
||||
final loginsV = ref.watch(loginsProvider);
|
||||
|
||||
if (!localAccountsV.hasValue || !loginsV.hasValue) {
|
||||
return waitingPage(context);
|
||||
}
|
||||
final localAccounts = localAccountsV.requireValue;
|
||||
final logins = loginsV.requireValue;
|
||||
|
||||
final activeUserLogin = logins.activeUserLogin;
|
||||
if (activeUserLogin == null) {
|
||||
// If no logged in user is active show a placeholder
|
||||
return waitingPage(context);
|
||||
}
|
||||
final accountV = ref
|
||||
.watch(fetchAccountProvider(accountMasterRecordKey: activeUserLogin));
|
||||
if (!accountV.hasValue) {
|
||||
return waitingPage(context);
|
||||
}
|
||||
final account = accountV.requireValue;
|
||||
switch (account.status) {
|
||||
case AccountInfoStatus.noAccount:
|
||||
return waitingPage(context);
|
||||
case AccountInfoStatus.accountInvalid:
|
||||
return waitingPage(context);
|
||||
case AccountInfoStatus.accountLocked:
|
||||
return waitingPage(context);
|
||||
case AccountInfoStatus.accountReady:
|
||||
return buildChatList(
|
||||
context,
|
||||
localAccounts,
|
||||
activeUserLogin,
|
||||
account.account!,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,315 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
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/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:preload_page_view/preload_page_view.dart';
|
||||
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
||||
import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
|
||||
|
||||
import '../../../components/bottom_sheet_action_button.dart';
|
||||
import '../../../components/paste_invite_dialog.dart';
|
||||
import '../../../components/scan_invite_dialog.dart';
|
||||
import '../../../components/send_invite_dialog.dart';
|
||||
import '../../../entities/local_account.dart';
|
||||
import '../../../proto/proto.dart' as proto;
|
||||
import '../../../tools/tools.dart';
|
||||
import '../../../../packages/veilid_support/veilid_support.dart';
|
||||
import 'account.dart';
|
||||
import 'chats.dart';
|
||||
|
||||
class MainPager extends ConsumerStatefulWidget {
|
||||
const MainPager(
|
||||
{required this.localAccounts,
|
||||
required this.activeUserLogin,
|
||||
required this.account,
|
||||
super.key});
|
||||
|
||||
final IList<LocalAccount> localAccounts;
|
||||
final TypedKey activeUserLogin;
|
||||
final proto.Account account;
|
||||
|
||||
@override
|
||||
MainPagerState createState() => MainPagerState();
|
||||
|
||||
static MainPagerState? of(BuildContext context) =>
|
||||
context.findAncestorStateOfType<MainPagerState>();
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(IterableProperty<LocalAccount>('localAccounts', localAccounts))
|
||||
..add(DiagnosticsProperty<TypedKey>('activeUserLogin', activeUserLogin))
|
||||
..add(DiagnosticsProperty<proto.Account>('account', account));
|
||||
}
|
||||
}
|
||||
|
||||
class MainPagerState extends ConsumerState<MainPager>
|
||||
with TickerProviderStateMixin {
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
final _unfocusNode = FocusNode();
|
||||
|
||||
var _currentPage = 0;
|
||||
final pageController = PreloadPageController();
|
||||
|
||||
final _selectedIconList = <IconData>[Icons.person, Icons.chat];
|
||||
// final _unselectedIconList = <IconData>[
|
||||
// Icons.chat_outlined,
|
||||
// Icons.person_outlined
|
||||
// ];
|
||||
final _fabIconList = <IconData>[
|
||||
Icons.person_add_sharp,
|
||||
Icons.add_comment_sharp,
|
||||
];
|
||||
final _bottomLabelList = <String>[
|
||||
translate('pager.account'),
|
||||
translate('pager.chats'),
|
||||
];
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_unfocusNode.dispose();
|
||||
pageController.dispose();
|
||||
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;
|
||||
}
|
||||
|
||||
BottomBarItem buildBottomBarItem(int index) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
return BottomBarItem(
|
||||
title: Text(_bottomLabelList[index]),
|
||||
icon: Icon(_selectedIconList[index], color: scale.primaryScale.text),
|
||||
selectedIcon:
|
||||
Icon(_selectedIconList[index], color: scale.primaryScale.text),
|
||||
backgroundColor: scale.primaryScale.text,
|
||||
//unSelectedColor: theme.colorScheme.primaryContainer,
|
||||
//selectedColor: theme.colorScheme.primary,
|
||||
//badge: const Text('9+'),
|
||||
//showBadge: true,
|
||||
);
|
||||
}
|
||||
|
||||
List<BottomBarItem> _buildBottomBarItems() {
|
||||
final bottomBarItems = List<BottomBarItem>.empty(growable: true);
|
||||
for (var index = 0; index < _bottomLabelList.length; index++) {
|
||||
final item = buildBottomBarItem(index);
|
||||
bottomBarItems.add(item);
|
||||
}
|
||||
return bottomBarItems;
|
||||
}
|
||||
|
||||
Future<void> scanContactInvitationDialog(BuildContext context) async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
// ignore: prefer_expression_function_bodies
|
||||
builder: (context) {
|
||||
return const AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(
|
||||
top: 10,
|
||||
),
|
||||
title: Text(
|
||||
'Scan Contact Invite',
|
||||
style: TextStyle(fontSize: 24),
|
||||
),
|
||||
content: ScanInviteDialog());
|
||||
});
|
||||
}
|
||||
|
||||
Widget _newContactInvitationBottomSheetBuilder(
|
||||
// ignore: prefer_expression_function_bodies
|
||||
BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final textTheme = theme.textTheme;
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
return KeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKeyEvent: (ke) {
|
||||
if (ke.logicalKey == LogicalKeyboardKey.escape) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 200,
|
||||
child: Column(children: [
|
||||
Text(translate('accounts_menu.invite_contact'),
|
||||
style: textTheme.titleMedium)
|
||||
.paddingAll(8),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
Column(children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
await SendInviteDialog.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(context);
|
||||
await ScanInviteDialog.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(context);
|
||||
await PasteInviteDialog.show(context);
|
||||
},
|
||||
iconSize: 64,
|
||||
icon: const Icon(Icons.paste),
|
||||
color: scale.primaryScale.background),
|
||||
Text(translate('accounts_menu.paste_invite'))
|
||||
])
|
||||
]).expanded()
|
||||
])));
|
||||
}
|
||||
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget _onNewChatBottomSheetBuilder(BuildContext context) {
|
||||
return const SizedBox(
|
||||
height: 200,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Group and custom chat functionality is not available yet')));
|
||||
}
|
||||
|
||||
Widget _bottomSheetBuilder(BuildContext context) {
|
||||
if (_currentPage == 0) {
|
||||
// New contact invitation
|
||||
return _newContactInvitationBottomSheetBuilder(context);
|
||||
} else if (_currentPage == 1) {
|
||||
// New chat
|
||||
return _onNewChatBottomSheetBuilder(context);
|
||||
} else {
|
||||
// Unknown error
|
||||
return waitingPage(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
return Scaffold(
|
||||
//extendBody: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
body: NotificationListener<ScrollNotification>(
|
||||
onNotification: onScrollNotification,
|
||||
child: PreloadPageView(
|
||||
controller: pageController,
|
||||
preloadPagesCount: 2,
|
||||
onPageChanged: (index) {
|
||||
setState(() {
|
||||
_currentPage = index;
|
||||
});
|
||||
},
|
||||
children: [
|
||||
AccountPage(
|
||||
localAccounts: widget.localAccounts,
|
||||
activeUserLogin: widget.activeUserLogin,
|
||||
account: widget.account),
|
||||
const ChatsPage(),
|
||||
])),
|
||||
// appBar: AppBar(
|
||||
// toolbarHeight: 24,
|
||||
// title: Text(
|
||||
// 'C',
|
||||
// style: Theme.of(context).textTheme.headlineSmall,
|
||||
// ),
|
||||
// ),
|
||||
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,
|
||||
),
|
||||
items: _buildBottomBarItems(),
|
||||
hasNotch: true,
|
||||
fabLocation: StylishBarFabLocation.end,
|
||||
currentIndex: _currentPage,
|
||||
onTap: (index) async {
|
||||
await pageController.animateToPage(index,
|
||||
duration: 250.ms, curve: Curves.easeInOut);
|
||||
},
|
||||
),
|
||||
|
||||
floatingActionButton: BottomSheetActionButton(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14))),
|
||||
foregroundColor: scale.secondaryScale.text,
|
||||
backgroundColor: scale.secondaryScale.hoverBorder,
|
||||
builder: (context) => Icon(
|
||||
_fabIconList[_currentPage],
|
||||
color: scale.secondaryScale.text,
|
||||
),
|
||||
bottomSheetBuilder: _bottomSheetBuilder),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<PreloadPageController>(
|
||||
'pageController', pageController));
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../../components/default_app_bar.dart';
|
||||
import '../../components/signal_strength_meter.dart';
|
||||
import '../../entities/preferences.dart';
|
||||
import '../providers/window_control.dart';
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
SettingsPageState createState() => SettingsPageState();
|
||||
}
|
||||
|
||||
class SettingsPageState extends ConsumerState<SettingsPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
late bool isInAsyncCall = false;
|
||||
// ThemePreferences? themePreferences;
|
||||
static const String formFieldTheme = 'theme';
|
||||
static const String formFieldBrightness = 'brightness';
|
||||
// static const String formFieldTitle = 'title';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
List<DropdownMenuItem<dynamic>> _getThemeDropdownItems() {
|
||||
const colorPrefs = ColorPreference.values;
|
||||
final colorNames = {
|
||||
ColorPreference.scarlet: translate('themes.scarlet'),
|
||||
ColorPreference.vapor: translate('themes.vapor'),
|
||||
ColorPreference.babydoll: translate('themes.babydoll'),
|
||||
ColorPreference.gold: translate('themes.gold'),
|
||||
ColorPreference.garden: translate('themes.garden'),
|
||||
ColorPreference.forest: translate('themes.forest'),
|
||||
ColorPreference.arctic: translate('themes.arctic'),
|
||||
ColorPreference.lapis: translate('themes.lapis'),
|
||||
ColorPreference.eggplant: translate('themes.eggplant'),
|
||||
ColorPreference.lime: translate('themes.lime'),
|
||||
ColorPreference.grim: translate('themes.grim'),
|
||||
ColorPreference.contrast: translate('themes.contrast')
|
||||
};
|
||||
|
||||
return colorPrefs
|
||||
.map((e) => DropdownMenuItem(value: e, child: Text(colorNames[e]!)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<DropdownMenuItem<dynamic>> _getBrightnessDropdownItems() {
|
||||
const brightnessPrefs = BrightnessPreference.values;
|
||||
final brightnessNames = {
|
||||
BrightnessPreference.system: translate('brightness.system'),
|
||||
BrightnessPreference.light: translate('brightness.light'),
|
||||
BrightnessPreference.dark: translate('brightness.dark')
|
||||
};
|
||||
|
||||
return brightnessPrefs
|
||||
.map(
|
||||
(e) => DropdownMenuItem(value: e, child: Text(brightnessNames[e]!)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.watch(windowControlProvider);
|
||||
final themeService = ref.watch(themeServiceProvider).valueOrNull;
|
||||
if (themeService == null) {
|
||||
return waitingPage(context);
|
||||
}
|
||||
final themePreferences = themeService.load();
|
||||
|
||||
return ThemeSwitchingArea(
|
||||
child: Scaffold(
|
||||
// resizeToAvoidBottomInset: false,
|
||||
appBar: DefaultAppBar(
|
||||
title: Text(translate('settings_page.titlebar')),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => context.pop<void>(),
|
||||
),
|
||||
actions: <Widget>[
|
||||
const SignalStrengthMeterWidget().paddingLTRB(16, 0, 16, 0),
|
||||
]),
|
||||
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
ThemeSwitcher.withTheme(
|
||||
builder: (_, switcher, theme) => FormBuilderDropdown(
|
||||
name: formFieldTheme,
|
||||
decoration: InputDecoration(
|
||||
label: Text(translate('settings_page.color_theme'))),
|
||||
items: _getThemeDropdownItems(),
|
||||
initialValue: themePreferences.colorPreference,
|
||||
onChanged: (value) async {
|
||||
final newPrefs = themePreferences.copyWith(
|
||||
colorPreference: value as ColorPreference);
|
||||
await themeService.save(newPrefs);
|
||||
switcher.changeTheme(theme: themeService.get(newPrefs));
|
||||
ref.invalidate(themeServiceProvider);
|
||||
setState(() {});
|
||||
})),
|
||||
ThemeSwitcher.withTheme(
|
||||
builder: (_, switcher, theme) => FormBuilderDropdown(
|
||||
name: formFieldBrightness,
|
||||
decoration: InputDecoration(
|
||||
label:
|
||||
Text(translate('settings_page.brightness_mode'))),
|
||||
items: _getBrightnessDropdownItems(),
|
||||
initialValue: themePreferences.brightnessPreference,
|
||||
onChanged: (value) async {
|
||||
final newPrefs = themePreferences.copyWith(
|
||||
brightnessPreference: value as BrightnessPreference);
|
||||
await themeService.save(newPrefs);
|
||||
switcher.changeTheme(theme: themeService.get(newPrefs));
|
||||
ref.invalidate(themeServiceProvider);
|
||||
setState(() {});
|
||||
})),
|
||||
],
|
||||
),
|
||||
).paddingSymmetric(horizontal: 24, vertical: 8),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<bool>('isInAsyncCall', isInAsyncCall));
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../../../packages/veilid_support/veilid_support.dart';
|
||||
|
||||
part 'connection_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class ConnectionState with _$ConnectionState {
|
||||
const factory ConnectionState({
|
||||
required VeilidStateAttachment attachment,
|
||||
}) = _ConnectionState;
|
||||
const ConnectionState._();
|
||||
|
||||
bool get isAttached => !(attachment.state == AttachmentState.detached ||
|
||||
attachment.state == AttachmentState.detaching ||
|
||||
attachment.state == AttachmentState.attaching);
|
||||
|
||||
bool get isPublicInternetReady => attachment.publicInternetReady;
|
||||
}
|
||||
|
||||
final connectionState = StateController<ConnectionState>(const ConnectionState(
|
||||
attachment: VeilidStateAttachment(
|
||||
state: AttachmentState.detached,
|
||||
publicInternetReady: false,
|
||||
localNetworkReady: false)));
|
||||
final connectionStateProvider =
|
||||
StateNotifierProvider<StateController<ConnectionState>, ConnectionState>(
|
||||
(ref) => connectionState);
|
|
@ -1,138 +0,0 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'connection_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ConnectionState {
|
||||
VeilidStateAttachment get attachment => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ConnectionStateCopyWith<ConnectionState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ConnectionStateCopyWith<$Res> {
|
||||
factory $ConnectionStateCopyWith(
|
||||
ConnectionState value, $Res Function(ConnectionState) then) =
|
||||
_$ConnectionStateCopyWithImpl<$Res, ConnectionState>;
|
||||
@useResult
|
||||
$Res call({VeilidStateAttachment attachment});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ConnectionStateCopyWithImpl<$Res, $Val extends ConnectionState>
|
||||
implements $ConnectionStateCopyWith<$Res> {
|
||||
_$ConnectionStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? attachment = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
attachment: freezed == attachment
|
||||
? _value.attachment
|
||||
: attachment // ignore: cast_nullable_to_non_nullable
|
||||
as VeilidStateAttachment,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ConnectionStateImplCopyWith<$Res>
|
||||
implements $ConnectionStateCopyWith<$Res> {
|
||||
factory _$$ConnectionStateImplCopyWith(_$ConnectionStateImpl value,
|
||||
$Res Function(_$ConnectionStateImpl) then) =
|
||||
__$$ConnectionStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({VeilidStateAttachment attachment});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ConnectionStateImplCopyWithImpl<$Res>
|
||||
extends _$ConnectionStateCopyWithImpl<$Res, _$ConnectionStateImpl>
|
||||
implements _$$ConnectionStateImplCopyWith<$Res> {
|
||||
__$$ConnectionStateImplCopyWithImpl(
|
||||
_$ConnectionStateImpl _value, $Res Function(_$ConnectionStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? attachment = freezed,
|
||||
}) {
|
||||
return _then(_$ConnectionStateImpl(
|
||||
attachment: freezed == attachment
|
||||
? _value.attachment
|
||||
: attachment // ignore: cast_nullable_to_non_nullable
|
||||
as VeilidStateAttachment,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ConnectionStateImpl extends _ConnectionState {
|
||||
const _$ConnectionStateImpl({required this.attachment}) : super._();
|
||||
|
||||
@override
|
||||
final VeilidStateAttachment attachment;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ConnectionState(attachment: $attachment)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ConnectionStateImpl &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.attachment, attachment));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, const DeepCollectionEquality().hash(attachment));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ConnectionStateImplCopyWith<_$ConnectionStateImpl> get copyWith =>
|
||||
__$$ConnectionStateImplCopyWithImpl<_$ConnectionStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ConnectionState extends ConnectionState {
|
||||
const factory _ConnectionState(
|
||||
{required final VeilidStateAttachment attachment}) =
|
||||
_$ConnectionStateImpl;
|
||||
const _ConnectionState._() : super._();
|
||||
|
||||
@override
|
||||
VeilidStateAttachment get attachment;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ConnectionStateImplCopyWith<_$ConnectionStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue