Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: future must not be created during build #250

Merged
merged 4 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions app/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:prototype/core_client.dart';
import 'package:prototype/navigation/navigation.dart';
import 'package:prototype/platform.dart';
import 'package:prototype/observable_user.dart';
import 'package:prototype/loadable_user_cubit.dart';
import 'package:prototype/user_cubit.dart';
import 'package:provider/provider.dart';

import 'registration/registration.dart';
Expand Down Expand Up @@ -78,34 +79,47 @@ class _AppState extends State<App> with WidgetsBindingObserver {
BlocProvider<NavigationCubit>(create: (context) => NavigationCubit()),
BlocProvider<RegistrationCubit>(
create: (context) => RegistrationCubit(coreClient: _coreClient)),
BlocProvider<ObservableUser>(
BlocProvider<LoadableUserCubit>(
create: (context) =>
// loads the user on startup
ObservableUser((_coreClient..loadUser()).userStream),
LoadableUserCubit((_coreClient..loadUser()).userStream),
),
],
child: BlocListener<ObservableUser, UserState>(
listenWhen: (previous, current) =>
// only fire the side effect when the user logs in or out
(current.user == null || previous.user == null) &&
current.user != previous.user,
listener: (context, user) {
// This bloc has two tasks:
// 1. Listen to the loadable user and switch the navigation accordingly.
// 2. Provide the logged in user to the app, when it is loaded.
child: BlocConsumer<LoadableUserCubit, LoadableUser>(
listenWhen: _nullToSomeOrSomeToNull,
buildWhen: _nullToSomeOrSomeToNull,
listener: (context, loadableUser) {
// Side Effect: navigate to the home screen or away to the intro
// screen, depending on whether the user was loaded or unloaded.
switch (user) {
case LoadedUserState(user: final _?):
switch (loadableUser) {
case LoadedUser(user: final _?):
context.read<NavigationCubit>().openHome();
case LoadingUserState() || LoadedUserState(user: null):
case LoadingUser() || LoadedUser(user: null):
context.read<NavigationCubit>().openIntro();
}
},
child: router!,
builder: (context, loadableUser) => loadableUser.user != null
// Logged in user is accessible everywhere inside the app after
// the user is loaded
? BlocProvider<UserCubit>(
create: (context) =>
UserCubit(coreClient: context.coreClient),
child: router!,
)
: router!,
),
),
);
}
}

bool _nullToSomeOrSomeToNull(LoadableUser previous, LoadableUser current) =>
(previous.user != null || current.user != null) &&
previous.user != current.user;

void _requestMobileNotifications() async {
// Mobile initialization
if (Platform.isAndroid || Platform.isIOS) {
Expand Down
19 changes: 10 additions & 9 deletions app/lib/conversation_list_pane/conversation_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'package:prototype/styles.dart';
import 'package:prototype/theme/theme.dart';
import 'package:convert/convert.dart';
import 'package:collection/collection.dart';
import 'package:prototype/user_cubit.dart';
import 'package:provider/provider.dart';

class ConversationList extends StatefulWidget {
Expand Down Expand Up @@ -142,7 +143,7 @@ class _ConversationListState extends State<ConversationList> {
);
}

Widget _lastMessage(int index) {
Widget _lastMessage(String userName, int index) {
var sender = '';
var displayedLastMessage = '';
final lastMessage = _conversations[index].lastMessage;
Expand All @@ -160,13 +161,11 @@ class _ConversationListState extends State<ConversationList> {

final senderStyle = style.copyWith(fontVariations: variationSemiBold);

final coreClient = context.coreClient;

if (lastMessage != null) {
lastMessage.message.when(
contentFlight: (c) {
final lastContentMessage = c.last;
if (lastContentMessage.sender == coreClient.username) {
if (lastContentMessage.sender == userName) {
sender = 'You: ';
}
displayedLastMessage = lastContentMessage.content.body;
Expand Down Expand Up @@ -246,14 +245,14 @@ class _ConversationListState extends State<ConversationList> {
);
}

Widget _bottomPart(int index) {
Widget _bottomPart(String userName, int index) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: _lastMessage(index),
child: _lastMessage(userName, index),
),
),
const SizedBox(width: 16),
Expand All @@ -265,7 +264,7 @@ class _ConversationListState extends State<ConversationList> {
);
}

Widget _listTile(int index) {
Widget _listTile(String userName, int index) {
return ListTile(
horizontalTitleGap: 0,
contentPadding: const EdgeInsets.symmetric(
Expand Down Expand Up @@ -295,7 +294,7 @@ class _ConversationListState extends State<ConversationList> {
children: [
_topPart(index),
const SizedBox(height: 2),
Expanded(child: _bottomPart(index)),
Expanded(child: _bottomPart(userName, index)),
],
),
),
Expand Down Expand Up @@ -328,6 +327,8 @@ class _ConversationListState extends State<ConversationList> {

@override
Widget build(BuildContext context) {
final userName = context.select((UserCubit cubit) => cubit.state.userName);

if (_conversations.isNotEmpty) {
return ListView.builder(
itemCount: _conversations.length,
Expand All @@ -336,7 +337,7 @@ class _ConversationListState extends State<ConversationList> {
),
controller: _scrollController,
itemBuilder: (BuildContext context, int index) {
return _listTile(index);
return _listTile(userName, index);
},
);
} else {
Expand Down
54 changes: 6 additions & 48 deletions app/lib/conversation_list_pane/pane.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,16 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later

import 'dart:async';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:prototype/conversation_list_pane/conversation_list.dart';
import 'package:prototype/conversation_list_pane/footer.dart';
import 'package:prototype/conversation_list_pane/top.dart';
import 'package:prototype/core/api/types.dart';
import 'package:prototype/core_client.dart';
import 'package:prototype/styles.dart';
import 'package:prototype/theme/theme.dart';

class ConversationView extends StatefulWidget {
class ConversationView extends StatelessWidget {
const ConversationView({super.key});

@override
State<ConversationView> createState() => _ConversationViewState();
}

class _ConversationViewState extends State<ConversationView> {
String? displayName;
Uint8List? profilePicture;
late final StreamSubscription<UiUserProfile> _profileSubscription;

@override
void initState() {
super.initState();

final coreClient = context.coreClient;
setState(() {
displayName = coreClient.ownProfile.displayName;
profilePicture = coreClient.ownProfile.profilePictureOption;
});

// Listen for changes to the user's profile picture
_profileSubscription = coreClient.onOwnProfileUpdate.listen((profile) {
setState(() {
profilePicture = profile.profilePictureOption;
displayName = profile.displayName;
});
});
}

@override
void dispose() {
_profileSubscription.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Container(
Expand All @@ -63,18 +24,15 @@ class _ConversationViewState extends State<ConversationView> {
),
),
),
child: Scaffold(
child: const Scaffold(
backgroundColor: convPaneBackgroundColor,
body: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ConversationListTop(
displayName: displayName,
profilePicture: profilePicture,
),
const SizedBox(height: Spacings.s),
const Expanded(child: ConversationList()),
const ConversationListFooter(),
ConversationListTop(),
SizedBox(height: Spacings.s),
Expanded(child: ConversationList()),
ConversationListFooter(),
],
),
),
Expand Down
98 changes: 60 additions & 38 deletions app/lib/conversation_list_pane/top.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,18 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later

import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:prototype/core_client.dart';
import 'package:prototype/elements.dart';
import 'package:prototype/navigation/navigation.dart';
import 'package:prototype/styles.dart';
import 'package:prototype/user_cubit.dart';
import 'package:provider/provider.dart';

class ConversationListTop extends StatelessWidget {
const ConversationListTop({
super.key,
required this.displayName,
required this.profilePicture,
});

final String? displayName;
final Uint8List? profilePicture;

double _topOffset() {
return isPointer() ? 30 : kToolbarHeight;
}
Expand All @@ -29,7 +22,44 @@ class ConversationListTop extends StatelessWidget {
return 60 + _topOffset();
}

Widget _avatar(BuildContext context) {
@override
Widget build(BuildContext context) {
return Stack(
children: [
SizedBox(
height: _topHeight(),
child: FrostedGlass(
color: convPaneBackgroundColor, height: _topHeight()),
),
Padding(
padding: EdgeInsets.only(left: 8, right: 8, top: _topOffset()),
child: const Row(
children: [
_Avatar(),
Expanded(
child: _UsernameSpace(),
),
_SettingsButton(),
],
),
),
],
);
}
}

class _Avatar extends StatelessWidget {
const _Avatar();

@override
Widget build(BuildContext context) {
final (userName, profilePicture) = context.select(
(UserCubit cubit) => (
cubit.state.userName,
cubit.state.profilePicture,
),
);

return Padding(
padding: const EdgeInsets.only(left: 18.0),
child: Row(
Expand All @@ -38,7 +68,7 @@ class ConversationListTop extends StatelessWidget {
children: [
UserAvatar(
size: 32,
username: context.coreClient.username,
username: userName,
image: profilePicture,
onPressed: () {
context.read<NavigationCubit>().openUserSettings();
Expand All @@ -48,8 +78,20 @@ class ConversationListTop extends StatelessWidget {
),
);
}
}

class _UsernameSpace extends StatelessWidget {
const _UsernameSpace();

@override
Widget build(BuildContext context) {
final (userName, displayName) = context.select(
(UserCubit cubit) => (
cubit.state.userName,
cubit.state.displayName,
),
);

Column _usernameSpace(String username) {
return Column(
children: [
Text(
Expand All @@ -63,7 +105,7 @@ class ConversationListTop extends StatelessWidget {
),
const SizedBox(height: 5),
Text(
username,
userName,
style: const TextStyle(
color: colorDMB,
fontSize: 10,
Expand All @@ -75,8 +117,13 @@ class ConversationListTop extends StatelessWidget {
],
);
}
}

class _SettingsButton extends StatelessWidget {
const _SettingsButton();

Widget _settingsButton(BuildContext context) {
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: () {
context.read<NavigationCubit>().openDeveloperSettings();
Expand All @@ -92,29 +139,4 @@ class ConversationListTop extends StatelessWidget {
),
);
}

@override
Widget build(BuildContext context) {
return Stack(
children: [
SizedBox(
height: _topHeight(),
child: FrostedGlass(
color: convPaneBackgroundColor, height: _topHeight()),
),
Padding(
padding: EdgeInsets.only(left: 8, right: 8, top: _topOffset()),
child: Row(
children: [
_avatar(context),
Expanded(
child: _usernameSpace(context.coreClient.username),
),
_settingsButton(context),
],
),
),
],
);
}
}
Loading
Loading