Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 0 additions & 3 deletions catalyst_voices/apps/voices/lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,6 @@ class _AppState extends State<App> {
BlocProvider<DevToolsBloc>(
create: (_) => Dependencies.instance.get<DevToolsBloc>(),
),
BlocProvider<PublicProfileEmailStatusCubit>(
create: (_) => Dependencies.instance.get<PublicProfileEmailStatusCubit>(),
),
BlocProvider<CampaignPhaseAwareCubit>(
// Making it not lazy to not show two loading screens in a row (one for app splash screen and one for campaign phase aware)
lazy: false,
Expand Down
29 changes: 16 additions & 13 deletions catalyst_voices/apps/voices/lib/app/view/app_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:catalyst_voices/app/view/app_splash_screen_manager.dart';
import 'package:catalyst_voices/app/view/video_cache/app_video_manager_scope.dart';
import 'package:catalyst_voices/app/view/video_cache/app_video_precache.dart';
import 'package:catalyst_voices/common/ext/preferences_ext.dart';
import 'package:catalyst_voices/notification/catalyst_messenger.dart';
import 'package:catalyst_voices/pages/campaign_phase_aware/widgets/bubble_campaign_phase_aware_background.dart';
import 'package:catalyst_voices/share/share_manager.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
Expand Down Expand Up @@ -76,19 +77,21 @@ final class _AppContent extends StatelessWidget {
child: AppVideoManagerScope(
child: AppVideoPrecache(
child: GlobalPrecacheImages(
child: GlobalSessionListener(
// IMPORTANT: AppSplashScreenManager must be placed above all
// widgets that render visible UI elements. Any widget that
// displays content should be a descendant of
// AppSplashScreenManager to ensure proper splash
// screen behavior.
child: AppSplashScreenManager(
child: AppMobileAccessRestriction(
routerConfig: routerConfig,
child: DefaultShareManager(
child: _AppContentBackground(
key: const Key('AppContentBackground'),
child: child,
child: CatalystMessenger(
child: GlobalSessionListener(
// IMPORTANT: AppSplashScreenManager must be placed above all
// widgets that render visible UI elements. Any widget that
// displays content should be a descendant of
// AppSplashScreenManager to ensure proper splash
// screen behavior.
child: AppSplashScreenManager(
child: AppMobileAccessRestriction(
routerConfig: routerConfig,
child: DefaultShareManager(
child: _AppContentBackground(
key: const Key('AppContentBackground'),
child: child,
),
),
),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'package:catalyst_voices/common/signal_handler.dart';
import 'package:catalyst_voices/notification/catalyst_messenger.dart';
import 'package:catalyst_voices/notification/specialized/account_needs_verification_banner.dart';
import 'package:catalyst_voices/routes/routes.dart';
import 'package:catalyst_voices/widgets/snackbar/voices_snackbar.dart';
import 'package:catalyst_voices/widgets/snackbar/voices_snackbar_type.dart';
Expand All @@ -21,18 +24,35 @@ class GlobalSessionListener extends StatefulWidget {
State<GlobalSessionListener> createState() => _GlobalSessionListenerState();
}

class _GlobalSessionListenerState extends State<GlobalSessionListener> {
class _GlobalSessionListenerState extends State<GlobalSessionListener>
with SignalHandlerStateMixin<SessionCubit, SessionSignal, GlobalSessionListener> {
String? _lastLocation;

@override
Widget build(BuildContext context) {
// TODO(damian-molinski): refactor it to use signals
return BlocListener<SessionCubit, SessionState>(
listenWhen: _listenToSessionChangesWhen,
listener: _onSessionChanged,
child: widget.child,
);
}

@override
void handleSignal(SessionSignal signal) {
switch (signal) {
case AccountNeedsVerificationSignal(:final isProposer):
final notification = isProposer
? AccountProposerNeedsVerificationBanner()
: AccountContributorNeedsVerificationBanner();
CatalystMessenger.of(context).add(notification);
case CancelAccountNeedsVerificationSignal():
CatalystMessenger.of(
context,
).cancelWhere((notification) => notification is AccountNeedsVerificationBanner);
}
}

bool _listenToSessionChangesWhen(SessionState prev, SessionState next) {
// We deliberately check if previous was guest because we don't
// want to show the snackbar after the registration is completed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,6 @@ final class Dependencies extends DependencyProvider {
get<DocumentsService>(),
);
})
..registerFactory<PublicProfileEmailStatusCubit>(() {
return PublicProfileEmailStatusCubit(
get<UserService>(),
);
})
..registerFactory<DocumentLookupBloc>(() {
return DocumentLookupBloc(
get<DocumentsService>(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'package:catalyst_voices/widgets/buttons/voices_icon_button.dart';
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

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

@override
Widget build(BuildContext context) {
return VoicesIconButton(
style: const ButtonStyle(iconSize: WidgetStatePropertyAll(18)),
onTap: () {
final messengerState = ScaffoldMessenger.maybeOf(context);
if (messengerState == null) {
if (kDebugMode) {
print('Can not dismiss banner because messenger key state is empty!');
}
return;
}

messengerState.hideCurrentMaterialBanner(reason: MaterialBannerClosedReason.dismiss);
},
child: VoicesAssets.icons.x.buildIcon(),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:catalyst_voices/notification/catalyst_notification.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

const _titleKey = 'title';

class BannerContent extends StatefulWidget {
final BannerNotification notification;

const BannerContent({
super.key,
required this.notification,
});

@override
State<BannerContent> createState() => _BannerContentState();
}

class _BannerContentState extends State<BannerContent> {
final _gestureRecognizers = <String, GestureRecognizer>{};

@override
Widget build(BuildContext context) {
final title = widget.notification.title(context);
final message = widget.notification.message(context);

final text = '{$_titleKey}: ${message.text}';
final placeholders = <String, CatalystNotificationTextPart>{
_titleKey: CatalystNotificationTextPart(text: title, bold: true),
...message.placeholders,
};

return PlaceholderRichText(
text,
placeholderSpanBuilder: (context, placeholder) {
if (!placeholders.containsKey(placeholder)) {
return TextSpan(text: placeholder);
}

final replacement = placeholders[placeholder]!;
final onTap = replacement.onTap;

return TextSpan(
text: replacement.text,
style: TextStyle(
fontWeight: replacement.bold ? FontWeight.bold : null,
decoration: replacement.underlined ? TextDecoration.underline : null,
),
recognizer: onTap != null
? _gestureRecognizers.putIfAbsent(
placeholder,
() => TapGestureRecognizer()..onTap = () => onTap(context),
)
: null,
);
},
);
}

@override
void dispose() {
final keys = List.of(_gestureRecognizers.keys);
for (final key in keys) {
_gestureRecognizers.remove(key)?.dispose();
}
super.dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
part of 'catalyst_notification.dart';

abstract base class BannerNotification extends CatalystNotification {
const BannerNotification({
required super.id,
super.priority,
super.type,
super.routerPredicate,
});

BannerNotificationMessage message(BuildContext context);

String title(BuildContext context);
}
Loading