veilidchat/lib/layout/home/home_screen.dart

291 lines
11 KiB
Dart
Raw Normal View History

2024-06-11 21:27:20 -04:00
import 'dart:math';
2024-06-15 23:29:15 -04:00
import 'package:async_tools/async_tools.dart';
2024-02-11 23:18:20 -05:00
import 'package:flutter/material.dart';
2024-06-15 00:01:08 -04:00
import 'package:flutter_bloc/flutter_bloc.dart';
2024-06-11 21:27:20 -04:00
import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
2024-02-13 22:03:26 -05:00
import 'package:provider/provider.dart';
2024-06-15 23:29:15 -04:00
import 'package:veilid_support/veilid_support.dart';
2024-02-11 23:18:20 -05:00
2024-02-13 22:03:26 -05:00
import '../../account_manager/account_manager.dart';
2024-06-15 23:29:15 -04:00
import '../../chat/chat.dart';
import '../../chat_list/chat_list.dart';
import '../../contact_invitation/contact_invitation.dart';
import '../../contacts/contacts.dart';
import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto;
2024-02-11 23:18:20 -05:00
import '../../theme/theme.dart';
2024-06-16 22:12:24 -04:00
import '../../tools/tools.dart';
import 'active_account_page_controller_wrapper.dart';
2024-06-11 21:27:20 -04:00
import 'drawer_menu/drawer_menu.dart';
2024-02-13 22:03:26 -05:00
import 'home_account_invalid.dart';
import 'home_account_locked.dart';
import 'home_account_missing.dart';
2024-06-16 22:12:24 -04:00
import 'home_account_ready/home_account_ready.dart';
2024-02-13 22:03:26 -05:00
import 'home_no_active.dart';
2024-02-11 23:18:20 -05:00
2024-06-16 22:12:24 -04:00
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
2024-02-11 23:18:20 -05:00
@override
2024-06-16 22:12:24 -04:00
HomeScreenState createState() => HomeScreenState();
2024-02-11 23:18:20 -05:00
}
2024-06-16 22:12:24 -04:00
class HomeScreenState extends State<HomeScreen> {
2024-02-11 23:18:20 -05:00
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
2024-06-15 23:29:15 -04:00
// Process all accepted or rejected invitations
void _invitationStatusListener(
BuildContext context, WaitingInvitationsBlocMapState state) {
_singleInvitationStatusProcessor.updateState(state, (newState) async {
final contactListCubit = context.read<ContactListCubit>();
final contactInvitationListCubit =
context.read<ContactInvitationListCubit>();
for (final entry in newState.entries) {
final contactRequestInboxRecordKey = entry.key;
final invStatus = entry.value.asData?.value;
// Skip invitations that have not yet been accepted or rejected
if (invStatus == null) {
continue;
}
// Delete invitation and process the accepted or rejected contact
final acceptedContact = invStatus.acceptedContact;
if (acceptedContact != null) {
await contactInvitationListCubit.deleteInvitation(
accepted: true,
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
// Accept
await contactListCubit.createContact(
profile: acceptedContact.remoteProfile,
remoteSuperIdentity: acceptedContact.remoteIdentity,
remoteConversationRecordKey:
acceptedContact.remoteConversationRecordKey,
localConversationRecordKey:
acceptedContact.localConversationRecordKey,
);
} else {
// Reject
await contactInvitationListCubit.deleteInvitation(
accepted: false,
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
}
}
});
}
2024-06-16 22:12:24 -04:00
Widget _buildAccountReadyDeviceSpecific(BuildContext context) {
final hasActiveChat = context.watch<ActiveChatCubit>().state != null;
if (responsiveVisibility(
context: context,
tablet: false,
tabletLandscape: false,
desktop: false)) {
if (hasActiveChat) {
return const HomeAccountReadyChat();
}
}
return const HomeAccountReadyMain();
}
Widget _buildUnlockedAccount(BuildContext context) {
final accountRecordKey = context.select<AccountInfoCubit, TypedKey>(
2024-06-15 23:29:15 -04:00
(c) => c.state.unlockedAccountInfo!.accountRecordKey);
final contactListRecordPointer =
context.select<AccountRecordCubit, OwnedDHTRecordPointer?>(
(c) => c.state.asData?.value.contactList.toVeilid());
final contactInvitationListRecordPointer =
context.select<AccountRecordCubit, OwnedDHTRecordPointer?>(
(c) => c.state.asData?.value.contactInvitationRecords.toVeilid());
final chatListRecordPointer =
context.select<AccountRecordCubit, OwnedDHTRecordPointer?>(
(c) => c.state.asData?.value.chatList.toVeilid());
2024-02-13 22:03:26 -05:00
2024-06-15 23:29:15 -04:00
if (contactListRecordPointer == null ||
contactInvitationListRecordPointer == null ||
chatListRecordPointer == null) {
2024-06-11 21:27:20 -04:00
return waitingPage();
}
2024-02-13 22:03:26 -05:00
2024-06-15 23:29:15 -04:00
return MultiBlocProvider(
providers: [
// Contact Cubits
BlocProvider(
create: (context) => ContactInvitationListCubit(
locator: context.read,
accountRecordKey: accountRecordKey,
contactInvitationListRecordPointer:
contactInvitationListRecordPointer,
)),
BlocProvider(
create: (context) => ContactListCubit(
locator: context.read,
accountRecordKey: accountRecordKey,
contactListRecordPointer: contactListRecordPointer)),
BlocProvider(
create: (context) => WaitingInvitationsBlocMapCubit(
locator: context.read,
)),
// Chat Cubits
BlocProvider(
2024-06-16 22:12:24 -04:00
create: (context) => ActiveChatCubit(
null,
)),
2024-06-15 23:29:15 -04:00
BlocProvider(
create: (context) => ChatListCubit(
locator: context.read,
accountRecordKey: accountRecordKey,
chatListRecordPointer: chatListRecordPointer)),
// Conversation Cubits
BlocProvider(
create: (context) => ActiveConversationsBlocMapCubit(
locator: context.read,
)),
BlocProvider(
create: (context) => ActiveSingleContactChatBlocMapCubit(
locator: context.read,
)),
],
child: MultiBlocListener(listeners: [
BlocListener<WaitingInvitationsBlocMapCubit,
WaitingInvitationsBlocMapState>(
listener: _invitationStatusListener,
)
2024-06-16 22:12:24 -04:00
], child: Builder(builder: _buildAccountReadyDeviceSpecific)));
2024-06-15 23:29:15 -04:00
}
2024-06-16 22:12:24 -04:00
Widget _buildAccount(BuildContext context) {
2024-06-15 23:29:15 -04:00
// Get active account info status
final (
accountInfoStatus,
accountInfoActive,
superIdentityRecordKey
) = context
2024-06-16 22:12:24 -04:00
.select<AccountInfoCubit, (AccountInfoStatus, bool, TypedKey?)>((c) => (
c.state.status,
c.state.active,
c.state.unlockedAccountInfo?.superIdentityRecordKey
));
2024-06-15 23:29:15 -04:00
switch (accountInfoStatus) {
2024-02-13 22:03:26 -05:00
case AccountInfoStatus.noAccount:
return const HomeAccountMissing();
case AccountInfoStatus.accountInvalid:
return const HomeAccountInvalid();
case AccountInfoStatus.accountLocked:
return const HomeAccountLocked();
2024-06-16 22:12:24 -04:00
case AccountInfoStatus.accountUnlocked:
2024-06-15 23:29:15 -04:00
// Get the current active account record cubit
final activeAccountRecordCubit =
context.select<AccountRecordsBlocMapCubit, AccountRecordCubit?>(
(c) => superIdentityRecordKey == null
? null
: c.tryOperate(superIdentityRecordKey, closure: (x) => x));
if (activeAccountRecordCubit == null) {
return waitingPage();
}
return MultiBlocProvider(providers: [
BlocProvider<AccountRecordCubit>.value(
value: activeAccountRecordCubit),
2024-06-16 22:12:24 -04:00
], child: Builder(builder: _buildUnlockedAccount));
}
}
Widget _buildAccountPageView(BuildContext context) {
final localAccounts = context.watch<LocalAccountsCubit>().state;
final activeLocalAccountCubit = context.read<ActiveLocalAccountCubit>();
final activeIndex = localAccounts.indexWhere(
(x) => x.superIdentity.recordKey == activeLocalAccountCubit.state);
if (activeIndex == -1) {
return const HomeNoActive();
2024-02-13 22:03:26 -05:00
}
2024-06-16 22:12:24 -04:00
return Provider<ActiveAccountPageControllerWrapper>(
lazy: false,
create: (context) =>
ActiveAccountPageControllerWrapper(context.read, activeIndex),
dispose: (context, value) {
value.dispose();
},
child: Builder(
builder: (context) => PageView.builder(
itemCount: localAccounts.length,
onPageChanged: (idx) {
singleFuture(this, () async {
await AccountRepository.instance.switchToAccount(
localAccounts[idx].superIdentity.recordKey);
});
},
controller: context
.read<ActiveAccountPageControllerWrapper>()
.pageController,
itemBuilder: (context, index) {
final localAccount = localAccounts[index];
return BlocProvider<AccountInfoCubit>(
key: ValueKey(localAccount.superIdentity.recordKey),
create: (context) => AccountInfoCubit(
AccountRepository.instance,
localAccount.superIdentity.recordKey),
child: Builder(builder: _buildAccount));
})));
2024-02-13 22:03:26 -05:00
}
2024-02-11 23:18:20 -05:00
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
2024-06-11 21:27:20 -04:00
final gradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
scale.tertiaryScale.subtleBackground,
scale.tertiaryScale.appBackground,
]);
2024-02-11 23:18:20 -05:00
return SafeArea(
2024-04-10 16:13:08 -04:00
child: DecoratedBox(
2024-06-11 21:27:20 -04:00
decoration: BoxDecoration(gradient: gradient),
child: ZoomDrawer(
controller: _zoomDrawerController,
//menuBackgroundColor: Colors.transparent,
menuScreen: const DrawerMenu(),
mainScreen: DecoratedBox(
decoration: BoxDecoration(
color: scale.primaryScale.activeElementBackground),
2024-06-15 23:29:15 -04:00
child: Provider<ZoomDrawerController>.value(
value: _zoomDrawerController,
2024-06-16 22:12:24 -04:00
child: Builder(builder: _buildAccountPageView))),
2024-06-11 21:27:20 -04:00
borderRadius: 24,
showShadow: true,
angle: 0,
drawerShadowsBackgroundColor: theme.shadowColor,
mainScreenOverlayColor: theme.shadowColor.withAlpha(0x3F),
openCurve: Curves.fastEaseInToSlowEaseOut,
// duration: const Duration(milliseconds: 250),
// reverseDuration: const Duration(milliseconds: 250),
menuScreenTapClose: true,
mainScreenTapClose: true,
2024-06-11 21:27:20 -04:00
mainScreenScale: .25,
slideWidth: min(360, MediaQuery.of(context).size.width * 0.9),
)));
2024-02-11 23:18:20 -05:00
}
2024-06-11 21:27:20 -04:00
2024-06-15 23:29:15 -04:00
final _zoomDrawerController = ZoomDrawerController();
final _singleInvitationStatusProcessor =
SingleStateProcessor<WaitingInvitationsBlocMapState>();
2024-02-11 23:18:20 -05:00
}