diff --git a/assets/images/tokenized_communities/1.5x/creator_monetization_live.png b/assets/images/tokenized_communities/1.5x/creator_monetization_live.png index 77688f4c97..86f2c3ab99 100644 Binary files a/assets/images/tokenized_communities/1.5x/creator_monetization_live.png and b/assets/images/tokenized_communities/1.5x/creator_monetization_live.png differ diff --git a/assets/images/tokenized_communities/1.5x/onboarding_ads.png b/assets/images/tokenized_communities/1.5x/onboarding_ads.png new file mode 100644 index 0000000000..8ed4c5bf8b Binary files /dev/null and b/assets/images/tokenized_communities/1.5x/onboarding_ads.png differ diff --git a/assets/images/tokenized_communities/1.5x/onboarding_rewards.png b/assets/images/tokenized_communities/1.5x/onboarding_rewards.png new file mode 100644 index 0000000000..ee8ce702ee Binary files /dev/null and b/assets/images/tokenized_communities/1.5x/onboarding_rewards.png differ diff --git a/assets/images/tokenized_communities/1.5x/onboarding_support.png b/assets/images/tokenized_communities/1.5x/onboarding_support.png new file mode 100644 index 0000000000..62c83f2f20 Binary files /dev/null and b/assets/images/tokenized_communities/1.5x/onboarding_support.png differ diff --git a/assets/images/tokenized_communities/1.5x/onboarding_web3.png b/assets/images/tokenized_communities/1.5x/onboarding_web3.png new file mode 100644 index 0000000000..83ee2c6e8b Binary files /dev/null and b/assets/images/tokenized_communities/1.5x/onboarding_web3.png differ diff --git a/assets/images/tokenized_communities/2.0x/creator_monetization_live.png b/assets/images/tokenized_communities/2.0x/creator_monetization_live.png index 8bae3c98d1..9249fdebe4 100644 Binary files a/assets/images/tokenized_communities/2.0x/creator_monetization_live.png and b/assets/images/tokenized_communities/2.0x/creator_monetization_live.png differ diff --git a/assets/images/tokenized_communities/2.0x/onboarding_ads.png b/assets/images/tokenized_communities/2.0x/onboarding_ads.png new file mode 100644 index 0000000000..c52a6d2f94 Binary files /dev/null and b/assets/images/tokenized_communities/2.0x/onboarding_ads.png differ diff --git a/assets/images/tokenized_communities/2.0x/onboarding_rewards.png b/assets/images/tokenized_communities/2.0x/onboarding_rewards.png new file mode 100644 index 0000000000..097f68d60c Binary files /dev/null and b/assets/images/tokenized_communities/2.0x/onboarding_rewards.png differ diff --git a/assets/images/tokenized_communities/2.0x/onboarding_support.png b/assets/images/tokenized_communities/2.0x/onboarding_support.png new file mode 100644 index 0000000000..f2964ff2b0 Binary files /dev/null and b/assets/images/tokenized_communities/2.0x/onboarding_support.png differ diff --git a/assets/images/tokenized_communities/2.0x/onboarding_web3.png b/assets/images/tokenized_communities/2.0x/onboarding_web3.png new file mode 100644 index 0000000000..6693717091 Binary files /dev/null and b/assets/images/tokenized_communities/2.0x/onboarding_web3.png differ diff --git a/assets/images/tokenized_communities/3.0x/creator_monetization_live.png b/assets/images/tokenized_communities/3.0x/creator_monetization_live.png index 5e851ed5b3..62b51372e2 100644 Binary files a/assets/images/tokenized_communities/3.0x/creator_monetization_live.png and b/assets/images/tokenized_communities/3.0x/creator_monetization_live.png differ diff --git a/assets/images/tokenized_communities/3.0x/onboarding_ads.png b/assets/images/tokenized_communities/3.0x/onboarding_ads.png new file mode 100644 index 0000000000..1801b911c4 Binary files /dev/null and b/assets/images/tokenized_communities/3.0x/onboarding_ads.png differ diff --git a/assets/images/tokenized_communities/3.0x/onboarding_rewards.png b/assets/images/tokenized_communities/3.0x/onboarding_rewards.png new file mode 100644 index 0000000000..eefba3383e Binary files /dev/null and b/assets/images/tokenized_communities/3.0x/onboarding_rewards.png differ diff --git a/assets/images/tokenized_communities/3.0x/onboarding_support.png b/assets/images/tokenized_communities/3.0x/onboarding_support.png new file mode 100644 index 0000000000..6960b0d53e Binary files /dev/null and b/assets/images/tokenized_communities/3.0x/onboarding_support.png differ diff --git a/assets/images/tokenized_communities/3.0x/onboarding_web3.png b/assets/images/tokenized_communities/3.0x/onboarding_web3.png new file mode 100644 index 0000000000..f49891e458 Binary files /dev/null and b/assets/images/tokenized_communities/3.0x/onboarding_web3.png differ diff --git a/assets/images/tokenized_communities/4.0x/creator_monetization_live.png b/assets/images/tokenized_communities/4.0x/creator_monetization_live.png index d128f3ba2d..55ae93dc63 100644 Binary files a/assets/images/tokenized_communities/4.0x/creator_monetization_live.png and b/assets/images/tokenized_communities/4.0x/creator_monetization_live.png differ diff --git a/assets/images/tokenized_communities/4.0x/onboarding_ads.png b/assets/images/tokenized_communities/4.0x/onboarding_ads.png new file mode 100644 index 0000000000..fa79d0a66b Binary files /dev/null and b/assets/images/tokenized_communities/4.0x/onboarding_ads.png differ diff --git a/assets/images/tokenized_communities/4.0x/onboarding_rewards.png b/assets/images/tokenized_communities/4.0x/onboarding_rewards.png new file mode 100644 index 0000000000..26a8ebe6ea Binary files /dev/null and b/assets/images/tokenized_communities/4.0x/onboarding_rewards.png differ diff --git a/assets/images/tokenized_communities/4.0x/onboarding_support.png b/assets/images/tokenized_communities/4.0x/onboarding_support.png new file mode 100644 index 0000000000..2a66b57bfc Binary files /dev/null and b/assets/images/tokenized_communities/4.0x/onboarding_support.png differ diff --git a/assets/images/tokenized_communities/4.0x/onboarding_web3.png b/assets/images/tokenized_communities/4.0x/onboarding_web3.png new file mode 100644 index 0000000000..384b42a51d Binary files /dev/null and b/assets/images/tokenized_communities/4.0x/onboarding_web3.png differ diff --git a/assets/images/tokenized_communities/creator_monetization_live.png b/assets/images/tokenized_communities/creator_monetization_live.png index 985ab7c629..8bbb924d3c 100644 Binary files a/assets/images/tokenized_communities/creator_monetization_live.png and b/assets/images/tokenized_communities/creator_monetization_live.png differ diff --git a/assets/images/tokenized_communities/onboarding_ads.png b/assets/images/tokenized_communities/onboarding_ads.png new file mode 100644 index 0000000000..f6ee233043 Binary files /dev/null and b/assets/images/tokenized_communities/onboarding_ads.png differ diff --git a/assets/images/tokenized_communities/onboarding_rewards.png b/assets/images/tokenized_communities/onboarding_rewards.png new file mode 100644 index 0000000000..8840743bfe Binary files /dev/null and b/assets/images/tokenized_communities/onboarding_rewards.png differ diff --git a/assets/images/tokenized_communities/onboarding_support.png b/assets/images/tokenized_communities/onboarding_support.png new file mode 100644 index 0000000000..9f4d41ec55 Binary files /dev/null and b/assets/images/tokenized_communities/onboarding_support.png differ diff --git a/assets/images/tokenized_communities/onboarding_web3.png b/assets/images/tokenized_communities/onboarding_web3.png new file mode 100644 index 0000000000..d9419d19b1 Binary files /dev/null and b/assets/images/tokenized_communities/onboarding_web3.png differ diff --git a/lib/app/features/auth/views/pages/link_new_device/link_new_device_dialog.dart b/lib/app/features/auth/views/pages/link_new_device/link_new_device_dialog.dart index 4b1ba460e6..6352792ae8 100644 --- a/lib/app/features/auth/views/pages/link_new_device/link_new_device_dialog.dart +++ b/lib/app/features/auth/views/pages/link_new_device/link_new_device_dialog.dart @@ -24,11 +24,11 @@ import 'package:ion/generated/assets.gen.dart'; import 'package:ion_identity_client/ion_identity.dart'; class ShowLinkNewDeviceDialogEvent extends UiEvent { - const ShowLinkNewDeviceDialogEvent(); + const ShowLinkNewDeviceDialogEvent() : super(id: 'link_new_device_dialog'); @override - void performAction(BuildContext context) { - showSimpleBottomSheet( + Future performAction(BuildContext context) async { + await showSimpleBottomSheet( context: context, isDismissible: false, child: const LinkNewDeviceDialog(), diff --git a/lib/app/features/auth/views/pages/required_bsc_wallet/creator_monetization_is_live_dialog.dart b/lib/app/features/auth/views/pages/required_bsc_wallet/creator_monetization_is_live_dialog.dart index cca80fad33..e3b90c38ce 100644 --- a/lib/app/features/auth/views/pages/required_bsc_wallet/creator_monetization_is_live_dialog.dart +++ b/lib/app/features/auth/views/pages/required_bsc_wallet/creator_monetization_is_live_dialog.dart @@ -20,15 +20,15 @@ import 'package:ion/generated/assets.gen.dart'; import 'package:ion_identity_client/ion_identity.dart'; class CreatorMonetizationIsLiveDialogEvent extends UiEvent { - const CreatorMonetizationIsLiveDialogEvent(); + const CreatorMonetizationIsLiveDialogEvent() : super(id: 'creator_monetization_is_live_dialog'); static bool shown = false; @override - void performAction(BuildContext context) { + Future performAction(BuildContext context) async { if (!shown) { shown = true; - showSimpleBottomSheet( + await showSimpleBottomSheet( context: context, isDismissible: false, backgroundColor: context.theme.appColors.forest, @@ -59,14 +59,6 @@ class _ContentState extends ConsumerWidget { final textStyles = context.theme.appTextThemes; final colors = context.theme.appColors; - ref.listen(bscWalletCheckProvider, (prev, next) { - next.whenData((result) { - if (result.hasBscWallet && context.mounted) { - Navigator.of(context).pop(); - } - }); - }); - final isCreatingWallet = ref.watch( walletAddressNotifierProvider.select((state) => state.isLoading), ); @@ -132,7 +124,6 @@ class _ContentState extends ConsumerWidget { final address = await _createBscWallet(context, ref, network: bscNetwork); if (!context.mounted || address == null) return; - ref.invalidate(bscWalletCheckProvider); await ref .read(userMetadataInvalidatorNotifierProvider.notifier) .invalidateCurrentUserMetadataProviders(); diff --git a/lib/app/features/auth/views/pages/turn_on_notifications/turn_on_notifications.dart b/lib/app/features/auth/views/pages/turn_on_notifications/turn_on_notifications.dart index 097edb3c96..3f75e028ad 100644 --- a/lib/app/features/auth/views/pages/turn_on_notifications/turn_on_notifications.dart +++ b/lib/app/features/auth/views/pages/turn_on_notifications/turn_on_notifications.dart @@ -18,11 +18,11 @@ import 'package:ion/app/router/components/sheet_content/sheet_content.dart'; import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart'; class ShowTurnOnNotificationsEvent extends UiEvent { - const ShowTurnOnNotificationsEvent(); + const ShowTurnOnNotificationsEvent() : super(id: 'turn_on_notifications'); @override - void performAction(BuildContext context) { - context.go(NotificationsRoute().location); + Future performAction(BuildContext context) async { + await context.push(NotificationsRoute().location); } } diff --git a/lib/app/features/core/providers/init_provider.r.dart b/lib/app/features/core/providers/init_provider.r.dart index 78a17d8b59..1f7891cfd0 100644 --- a/lib/app/features/core/providers/init_provider.r.dart +++ b/lib/app/features/core/providers/init_provider.r.dart @@ -19,14 +19,15 @@ import 'package:ion/app/features/force_update/providers/force_update_provider.r. import 'package:ion/app/features/ion_connect/providers/global_subscription.r.dart'; import 'package:ion/app/features/push_notifications/background/firebase_messaging_background_service.dart'; import 'package:ion/app/features/push_notifications/providers/pushes_init_provider.r.dart'; +import 'package:ion/app/features/tokenized_communities/providers/tokenized_community_onboarding_provider.r.dart'; import 'package:ion/app/features/user/providers/account_notifications_sync_provider.r.dart'; import 'package:ion/app/features/user/providers/force_account_security_notifier.r.dart'; import 'package:ion/app/features/user/providers/relays/user_chat_relays_sync_provider.r.dart'; import 'package:ion/app/features/user/providers/relays/user_file_storage_relays_sync_provider.r.dart'; import 'package:ion/app/features/user/providers/relays/user_relays_sync_provider.r.dart'; import 'package:ion/app/features/user/providers/user_awards_sync_provider.r.dart'; -import 'package:ion/app/features/wallets/providers/bsc_wallet_check_service.r.dart'; import 'package:ion/app/features/wallets/providers/coins_sync_provider.r.dart'; +import 'package:ion/app/features/wallets/providers/creator_monetization_dialog_provider.r.dart'; import 'package:ion/app/features/wallets/providers/user_public_wallets_sync_provider.r.dart'; import 'package:ion/app/features/wallets/providers/wallets_initializer_provider.r.dart'; import 'package:ion/app/services/conversion_tracking/facebook_conversion_tracking_service.r.dart'; @@ -76,7 +77,7 @@ Future initApp(Ref ref) async { ..listen(userRelaysSyncProvider, noop) ..listen(userAwardsSyncProvider, noop) ..listen(forceAccountSecurityServiceProvider, noop) - ..listen(bscWalletCheckServiceProvider, noop) + ..listen(creatorMonetizationDialogServiceProvider, noop) ..listen(userChatRelaysSyncProvider, noop) ..listen(userFileStorageRelaysSyncProvider, noop) ..listen(feedBookmarksSyncProvider, noop) @@ -84,7 +85,8 @@ Future initApp(Ref ref) async { ..listen(pushesInitProvider, noop) ..listen(globalSubscriptionProvider, (_, subscription) => subscription?.init()) ..listen(accountNotificationsSyncProvider, noop) - ..listen(deepLinkHandlerProvider, noop); + ..listen(deepLinkHandlerProvider, noop) + ..listen(tokenizedCommunityOnboardingServiceProvider, noop); initFirebaseMessagingBackgroundHandler(); diff --git a/lib/app/features/force_update/view/pages/app_update_modal.dart b/lib/app/features/force_update/view/pages/app_update_modal.dart index 771553ce1c..58c044f917 100644 --- a/lib/app/features/force_update/view/pages/app_update_modal.dart +++ b/lib/app/features/force_update/view/pages/app_update_modal.dart @@ -21,15 +21,15 @@ import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart'; import 'package:ion/generated/assets.gen.dart'; class ShowAppUpdateModalEvent extends UiEvent { - const ShowAppUpdateModalEvent(); + const ShowAppUpdateModalEvent() : super(id: 'app_update_modal'); static bool shown = false; @override - void performAction(BuildContext context) { + Future performAction(BuildContext context) async { if (!shown) { shown = true; - showSimpleBottomSheet( + await showSimpleBottomSheet( context: context, isDismissible: false, child: const AppUpdateModal( @@ -41,17 +41,17 @@ class ShowAppUpdateModalEvent extends UiEvent { } class ShowInAppUpdateModalEvent extends UiEvent { - const ShowInAppUpdateModalEvent(); + const ShowInAppUpdateModalEvent() : super(id: 'in_app_update_modal'); static bool shown = false; @override - void performAction(BuildContext context) { + Future performAction(BuildContext context) async { final ref = ProviderScope.containerOf(context); if (!shown) { shown = true; - showSimpleBottomSheet( + await showSimpleBottomSheet( context: context, child: AppUpdateModal( appUpdateType: AppUpdateType.androidSoftUpdate, diff --git a/lib/app/features/protect_account/secure_account/views/pages/secure_account_modal.dart b/lib/app/features/protect_account/secure_account/views/pages/secure_account_modal.dart index eabd877c4e..2d0d45ae92 100644 --- a/lib/app/features/protect_account/secure_account/views/pages/secure_account_modal.dart +++ b/lib/app/features/protect_account/secure_account/views/pages/secure_account_modal.dart @@ -17,15 +17,15 @@ import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart'; import 'package:ion/generated/assets.gen.dart'; class SecureAccountDialogEvent extends UiEvent { - const SecureAccountDialogEvent(); + const SecureAccountDialogEvent() : super(id: 'secure_account_dialog'); static bool shown = false; @override - void performAction(BuildContext context) { + Future performAction(BuildContext context) async { if (!shown) { shown = true; - showSimpleBottomSheet( + await showSimpleBottomSheet( context: context, isDismissible: false, child: const SecureAccountModal( diff --git a/lib/app/features/tokenized_communities/providers/tokenized_community_onboarding_provider.r.dart b/lib/app/features/tokenized_communities/providers/tokenized_community_onboarding_provider.r.dart new file mode 100644 index 0000000000..50fe850049 --- /dev/null +++ b/lib/app/features/tokenized_communities/providers/tokenized_community_onboarding_provider.r.dart @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: ice License 1.0 + +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:ion/app/features/auth/providers/auth_provider.m.dart'; +import 'package:ion/app/features/auth/providers/delegation_complete_provider.r.dart'; +import 'package:ion/app/features/tokenized_communities/views/pages/tokenized_community_onboarding_dialog/tokenized_community_onboarding_dialog.dart'; +import 'package:ion/app/features/wallets/providers/bsc_wallet_check_provider.m.dart'; +import 'package:ion/app/router/app_routes.gr.dart'; +import 'package:ion/app/router/providers/route_location_provider.r.dart'; +import 'package:ion/app/services/storage/user_preferences_service.r.dart'; +import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'tokenized_community_onboarding_provider.r.g.dart'; + +class TokenizedCommunityOnboardingService { + TokenizedCommunityOnboardingService({ + required void Function() emitDialog, + required Future Function() setShown, + }) : _emitDialog = emitDialog, + _setShown = setShown; + + bool? _authenticated; + bool? _delegationCompleted; + bool? _userHasBscWallet; + bool? _alreadyShown; + String? _route; + + final void Function() _emitDialog; + final Future Function() _setShown; + + void onAuthenticated({required bool? authenticated}) { + _authenticated = authenticated; + _maybeTrigger(); + } + + void onDelegationCompleted({required bool? delegationCompleted}) { + _delegationCompleted = delegationCompleted; + _maybeTrigger(); + } + + void onUserHasBscWalletChanged({required bool hasBscWallet}) { + _userHasBscWallet = hasBscWallet; + _maybeTrigger(); + } + + void onRouteChanged(String value) { + _route = value; + _maybeTrigger(); + } + + void onShownChanged({required bool? shown}) { + _alreadyShown = shown; + _maybeTrigger(); + } + + Future _maybeTrigger() async { + if (_authenticated != true) return; + if (_delegationCompleted != true) return; + if (_userHasBscWallet != true) return; + if (_alreadyShown ?? true) return; + if (_route != FeedRoute().location) return; + + _emitDialog(); + await _setShown(); + } +} + +@riverpod +TokenizedCommunityOnboardingService? tokenizedCommunityOnboardingService(Ref ref) { + final userPreferencesService = ref.watch(currentUserPreferencesServiceProvider); + + if (userPreferencesService == null) return null; + + final service = TokenizedCommunityOnboardingService( + emitDialog: () { + ref + .read(uiEventQueueNotifierProvider.notifier) + .emit(const TokenizedCommunityOnboardingDialogEvent()); + }, + setShown: () async { + await ref.read(tokenizedCommunityOnboardingShownProvider.notifier).setShown(); + }, + ); + + ref + ..listen>( + authProvider, + fireImmediately: true, + (_, next) { + service.onAuthenticated(authenticated: next.valueOrNull?.isAuthenticated); + }, + ) + ..listen>( + delegationCompleteProvider, + fireImmediately: true, + (_, next) { + service.onDelegationCompleted(delegationCompleted: next.valueOrNull); + }, + ) + ..listen( + routeLocationProvider, + fireImmediately: true, + (_, next) { + service.onRouteChanged(next); + }, + ) + ..listen>( + bscWalletCheckProvider, + fireImmediately: true, + (_, next) { + if (!next.isLoading && next.hasValue) { + service.onUserHasBscWalletChanged(hasBscWallet: next.value!.hasBscWallet); + } + }, + ) + ..listen( + tokenizedCommunityOnboardingShownProvider, + fireImmediately: true, + (_, next) { + service.onShownChanged(shown: next); + }, + ); + + return service; +} + +@riverpod +class TokenizedCommunityOnboardingShown extends _$TokenizedCommunityOnboardingShown { + static const String _key = 'tokenized_community_onboarding_shown'; + + @override + bool? build() { + final userPreferencesService = ref.watch(currentUserPreferencesServiceProvider); + if (userPreferencesService == null) { + return null; + } + return userPreferencesService.getValue(_key) ?? false; + } + + Future setShown() async { + final userPreferencesService = ref.read(currentUserPreferencesServiceProvider); + if (userPreferencesService == null) { + return; + } + await userPreferencesService.setValue(_key, true); + state = true; + } +} diff --git a/lib/app/features/tokenized_communities/views/pages/tokenized_community_onboarding_dialog/tokenized_community_onboarding_dialog.dart b/lib/app/features/tokenized_communities/views/pages/tokenized_community_onboarding_dialog/tokenized_community_onboarding_dialog.dart new file mode 100644 index 0000000000..bf0b8ba9d7 --- /dev/null +++ b/lib/app/features/tokenized_communities/views/pages/tokenized_community_onboarding_dialog/tokenized_community_onboarding_dialog.dart @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: ice License 1.0 + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:ion/app/components/button/button.dart'; +import 'package:ion/app/components/screen_offset/screen_bottom_offset.dart'; +import 'package:ion/app/components/screen_offset/screen_side_offset.dart'; +import 'package:ion/app/extensions/extensions.dart'; +import 'package:ion/app/features/user/pages/profile_page/components/profile_background.dart'; +import 'package:ion/app/hooks/use_avatar_colors.dart'; +import 'package:ion/app/router/utils/show_simple_bottom_sheet.dart'; +import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart'; +import 'package:ion/generated/assets.gen.dart'; + +class TokenizedCommunityOnboardingDialogEvent extends UiEvent { + const TokenizedCommunityOnboardingDialogEvent() + : super(id: 'tokenized_community_onboarding_dialog'); + + static bool shown = false; + + @override + Future performAction(BuildContext context) async { + if (!shown) { + shown = true; + await showSimpleBottomSheet( + context: context, + isDismissible: false, + backgroundColor: context.theme.appColors.forest, + child: const TokenizedCommunityOnboardingDialog(), + ).whenComplete(() => shown = false); + } + } +} + +class TokenizedCommunityOnboardingDialog extends HookConsumerWidget { + const TokenizedCommunityOnboardingDialog({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return ProfileGradientBackground( + colors: useAvatarFallbackColors, + disableDarkGradient: false, + child: const _TokenizedCommunityOnboarding(), + ); + } +} + +class _TokenizedCommunityOnboarding extends HookWidget { + const _TokenizedCommunityOnboarding(); + + @override + Widget build(BuildContext context) { + final currentIndex = useState(0); + final pageController = usePageController(initialPage: currentIndex.value); + + final pages = useMemoized( + () => [ + OnboardingPage( + image: Assets.images.tokenizedCommunities.onboardingRewards, + title: context.i18n.tokenized_community_onboarding_rewards_title, + description: context.i18n.tokenized_community_onboarding_rewards_description, + ), + OnboardingPage( + image: Assets.images.tokenizedCommunities.onboardingAds, + title: context.i18n.tokenized_community_onboarding_ads_title, + description: context.i18n.tokenized_community_onboarding_ads_description, + ), + OnboardingPage( + image: Assets.images.tokenizedCommunities.onboardingSupport, + title: context.i18n.tokenized_community_onboarding_support_title, + description: context.i18n.tokenized_community_onboarding_support_description, + ), + OnboardingPage( + image: Assets.images.tokenizedCommunities.onboardingWeb3, + title: context.i18n.tokenized_community_onboarding_web3_title, + description: context.i18n.tokenized_community_onboarding_web3_description, + ), + ], + ); + + return Stack( + children: [ + PositionedDirectional( + top: -50.0.s, + start: -43.0.s, + end: -43.0.s, + child: Assets.images.tokenizedCommunities.creatorMonetizationLiveRays + .iconWithDimensions(width: 461.s, height: 461.s), + ), + Column( + children: [ + SizedBox(height: 30.0.s), + _OnboardingCarousel( + pageController: pageController, + onPageChanged: (index) => currentIndex.value = index, + pages: pages, + ), + SizedBox(height: 16.0.s), + _OnboardingProgress( + currentIndex: currentIndex.value, + total: pages.length, + ), + SizedBox(height: 32.0.s), + ScreenSideOffset.small( + child: Button( + minimumSize: Size(double.infinity, 56.0.s), + label: Text(context.i18n.button_continue), + onPressed: () { + if (currentIndex.value == pages.length - 1) { + Navigator.of(context).pop(); + } else { + pageController.animateToPage( + currentIndex.value + 1, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + }, + ), + ), + ScreenBottomOffset(), + ], + ), + ], + ); + } +} + +class OnboardingPage { + OnboardingPage({ + required this.image, + required this.title, + required this.description, + }); + + final AssetGenImage image; + final String title; + final String description; +} + +class _OnboardingCarousel extends StatelessWidget { + const _OnboardingCarousel({ + required this.pages, + required this.pageController, + required this.onPageChanged, + }); + + final List pages; + final PageController pageController; + final void Function(int) onPageChanged; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 274.0.s, + child: AnimatedBuilder( + animation: pageController, + builder: (context, child) { + return PageView.builder( + controller: pageController, + onPageChanged: onPageChanged, + itemCount: pages.length, + itemBuilder: (context, index) { + final page = pageController.hasClients ? pageController.page ?? 0.0 : 0.0; + final opacity = (1 - (page - index).abs()).clamp(0.0, 1.0); + return Opacity( + opacity: opacity, + child: _OnboardingCarouselPage(page: pages[index]), + ); + }, + ); + }, + ), + ); + } +} + +class _OnboardingCarouselPage extends StatelessWidget { + const _OnboardingCarouselPage({ + required this.page, + }); + + final OnboardingPage page; + + @override + Widget build(BuildContext context) { + final textStyles = context.theme.appTextThemes; + final colors = context.theme.appColors; + + return ScreenSideOffset.small( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + page.image.iconWithDimensions(width: 295.s, height: 164.s), + const Spacer(), + Text( + page.title, + style: textStyles.title.copyWith(color: colors.secondaryBackground), + textAlign: TextAlign.center, + ), + SizedBox(height: 8.0.s), + Text( + page.description, + style: textStyles.body2.copyWith(color: colors.onTertiaryFill), + textAlign: TextAlign.center, + ), + ], + ), + ); + } +} + +class _OnboardingProgress extends StatelessWidget { + const _OnboardingProgress({ + required this.currentIndex, + required this.total, + }); + + final int currentIndex; + final int total; + + @override + Widget build(BuildContext context) { + final colors = context.theme.appColors; + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + total, + (index) => Padding( + padding: EdgeInsets.symmetric(horizontal: 2.0.s), + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + width: 6.0.s, + height: 6.0.s, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: index == currentIndex ? colors.primaryAccent : colors.onTertiaryFill, + ), + ), + ), + ), + ); + } +} diff --git a/lib/app/features/wallets/providers/bsc_wallet_check_provider.m.dart b/lib/app/features/wallets/providers/bsc_wallet_check_provider.m.dart index 51c3cedf40..b16c60417a 100644 --- a/lib/app/features/wallets/providers/bsc_wallet_check_provider.m.dart +++ b/lib/app/features/wallets/providers/bsc_wallet_check_provider.m.dart @@ -3,8 +3,8 @@ import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:ion/app/features/user/providers/user_metadata_provider.r.dart'; import 'package:ion/app/features/wallets/model/network_data.f.dart'; +import 'package:ion/app/features/wallets/providers/connected_crypto_wallets_provider.r.dart'; import 'package:ion/app/features/wallets/providers/networks_provider.r.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -21,15 +21,16 @@ class BscWalletCheckResult with _$BscWalletCheckResult { @riverpod Future bscWalletCheck(Ref ref) async { - final networks = await ref.watch(networksProvider.future); + final networks = await ref.watch(networksStreamProvider.future); + final mainCryptoWallets = await ref.watch(mainCryptoWalletsProvider.future); + final bscNetwork = networks.firstWhereOrNull((n) => n.isBsc); if (bscNetwork == null) { return const BscWalletCheckResult(hasBscWallet: false); } - final userMetadata = await ref.watch(currentUserMetadataProvider.future); - final hasBscWallet = userMetadata?.data.wallets?[bscNetwork.id] != null; + final hasBscWallet = mainCryptoWallets.any((wallet) => wallet.network == bscNetwork.id); return BscWalletCheckResult( hasBscWallet: hasBscWallet, diff --git a/lib/app/features/wallets/providers/bsc_wallet_check_service.r.dart b/lib/app/features/wallets/providers/bsc_wallet_check_service.r.dart deleted file mode 100644 index 5fa31cbfde..0000000000 --- a/lib/app/features/wallets/providers/bsc_wallet_check_service.r.dart +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-License-Identifier: ice License 1.0 - -import 'dart:async'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:ion/app/features/auth/providers/auth_provider.m.dart'; -import 'package:ion/app/features/auth/providers/onboarding_complete_provider.r.dart'; -import 'package:ion/app/features/auth/views/pages/required_bsc_wallet/creator_monetization_is_live_dialog.dart'; -import 'package:ion/app/features/core/providers/splash_provider.r.dart'; -import 'package:ion/app/features/tokenized_communities/providers/token_action_first_buy_provider.r.dart'; -import 'package:ion/app/features/user/model/user_metadata.f.dart'; -import 'package:ion/app/features/user/providers/user_metadata_provider.r.dart'; -import 'package:ion/app/features/wallets/providers/bsc_wallet_check_provider.m.dart'; -import 'package:ion/app/router/app_routes.gr.dart'; -import 'package:ion/app/router/providers/route_location_provider.r.dart'; -import 'package:ion/app/services/logger/logger.dart'; -import 'package:ion/app/services/storage/user_preferences_service.r.dart'; -import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:synchronized/synchronized.dart'; - -part 'bsc_wallet_check_service.r.g.dart'; - -class BscWalletCheckService { - BscWalletCheckService({ - required void Function() emitDialog, - required Future Function() checkWallet, - required UserPreferencesService? userPreferencesService, - }) : _emitDialog = emitDialog, - _checkWallet = checkWallet, - _userPreferencesService = userPreferencesService; - - static const _shownKey = 'required_bsc_wallet_dialog_bootstrap_shown_v1'; - - bool _authenticated = false; - bool? _onboardingComplete; - bool _splashCompleted = false; - bool? _currentUserHasToken; - String _route = ''; - final _lock = Lock(); - - final void Function() _emitDialog; - final Future Function() _checkWallet; - - UserPreferencesService? _userPreferencesService; - - void onSplashCompleted({required bool splashCompleted}) { - _splashCompleted = splashCompleted; - _maybeTrigger(); - } - - void onAuthenticated({required bool authenticated}) { - _authenticated = authenticated; - _maybeTrigger(); - } - - // ignore: avoid_positional_boolean_parameters, use_setters_to_change_properties - void onOnboardingComplete(bool? onboardingComplete) { - _onboardingComplete = onboardingComplete; - //no need to call _maybeTrigger as right after onboarding we are showing link device and notifications permission modals - } - - void onUserMetadataChanged() { - _maybeTrigger(); - } - - void onCurrentUserHasTokenChanged({required bool? hasToken}) { - _currentUserHasToken = hasToken; - _maybeTrigger(); - } - - void onUserPreferencesServiceChanged(UserPreferencesService? service) { - _userPreferencesService = service; - _maybeTrigger(); - } - - void onRouteChanged(String value) { - _route = value; - _maybeTrigger(); - } - - Future _maybeTrigger() async { - return _lock.synchronized(() async { - if (!_splashCompleted) return; - if (!_authenticated) return; - if (_onboardingComplete != true) return; - if (_route != FeedRoute().location) return; - if (_currentUserHasToken == null || (_currentUserHasToken ?? false)) return; - final prefs = _userPreferencesService; - if (prefs == null) return; - - try { - final alreadyShownForUser = prefs.getValue(_shownKey) ?? false; - final bscWalletCheckResult = await _checkWallet(); - // We show at least once for every user even if already has BSC wallet - // it is just for user who hasn't yet the BSC wallet flow will be different for this pop up - if (!alreadyShownForUser || !bscWalletCheckResult.hasBscWallet) { - _emitDialog(); - await prefs.setValue(_shownKey, true); - return; - } - } catch (e) { - Logger.log('Failed to check BSC wallet', error: e.toString()); - } - }); - } -} - -@Riverpod(keepAlive: true) -BscWalletCheckService bscWalletCheckService(Ref ref) { - final service = BscWalletCheckService( - emitDialog: () { - ref - .read(uiEventQueueNotifierProvider.notifier) - .emit(const CreatorMonetizationIsLiveDialogEvent()); - }, - checkWallet: () => ref.read(bscWalletCheckProvider.future), - userPreferencesService: ref.read(currentUserPreferencesServiceProvider), - ); - - ref - ..listen( - splashProvider, - fireImmediately: true, - (_, bool next) { - service.onSplashCompleted(splashCompleted: next); - }, - ) - ..listen>( - authProvider, - fireImmediately: true, - (_, AsyncValue next) { - service.onAuthenticated(authenticated: next.valueOrNull?.isAuthenticated ?? false); - }, - ) - ..listen>( - onboardingCompleteProvider, - fireImmediately: true, - (_, AsyncValue next) { - service.onOnboardingComplete(next.valueOrNull); - }, - ) - ..listen( - routeLocationProvider, - fireImmediately: true, - (_, String next) { - service.onRouteChanged(next); - }, - ) - ..listen( - currentUserPreferencesServiceProvider, - fireImmediately: true, - (_, UserPreferencesService? next) { - service.onUserPreferencesServiceChanged(next); - }, - ) - ..listen>( - currentUserHasTokenProvider, - fireImmediately: true, - (_, AsyncValue next) { - service.onCurrentUserHasTokenChanged(hasToken: next.valueOrNull); - }, - ) - ..listen>( - currentUserMetadataProvider, - fireImmediately: true, - (AsyncValue? prev, AsyncValue next) { - if (next.hasValue && next.value?.masterPubkey != prev?.value?.masterPubkey) { - service.onUserMetadataChanged(); - } - }, - ); - - return service; -} diff --git a/lib/app/features/wallets/providers/creator_monetization_dialog_provider.r.dart b/lib/app/features/wallets/providers/creator_monetization_dialog_provider.r.dart new file mode 100644 index 0000000000..dd0f797776 --- /dev/null +++ b/lib/app/features/wallets/providers/creator_monetization_dialog_provider.r.dart @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: ice License 1.0 + +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:ion/app/features/auth/providers/auth_provider.m.dart'; +import 'package:ion/app/features/auth/providers/delegation_complete_provider.r.dart'; +import 'package:ion/app/features/auth/views/pages/required_bsc_wallet/creator_monetization_is_live_dialog.dart'; +import 'package:ion/app/features/wallets/providers/bsc_wallet_check_provider.m.dart'; +import 'package:ion/app/router/app_routes.gr.dart'; +import 'package:ion/app/router/providers/route_location_provider.r.dart'; +import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'creator_monetization_dialog_provider.r.g.dart'; + +class CreatorMonetizationDialogService { + CreatorMonetizationDialogService({ + required void Function() emitDialog, + }) : _emitDialog = emitDialog; + + bool? _authenticated; + bool? _delegationCompleted; + bool? _userHasBscWallet; + String? _route; + + final void Function() _emitDialog; + + void onAuthenticated({required bool authenticated}) { + _authenticated = authenticated; + _maybeTrigger(); + } + + void onDelegationCompleted({required bool delegationCompleted}) { + _delegationCompleted = delegationCompleted; + _maybeTrigger(); + } + + void onUserHasBscWalletChanged({required bool hasBscWallet}) { + _userHasBscWallet = hasBscWallet; + _maybeTrigger(); + } + + void onRouteChanged(String value) { + _route = value; + _maybeTrigger(); + } + + Future _maybeTrigger() async { + if (_authenticated != true) return; + if (_delegationCompleted != true) return; + if (_userHasBscWallet ?? true) return; + if (_route != FeedRoute().location) return; + + _emitDialog(); + } +} + +@riverpod +CreatorMonetizationDialogService creatorMonetizationDialogService(Ref ref) { + final service = CreatorMonetizationDialogService( + emitDialog: () { + ref + .read(uiEventQueueNotifierProvider.notifier) + .emit(const CreatorMonetizationIsLiveDialogEvent()); + }, + ); + + ref + ..listen>( + authProvider, + fireImmediately: true, + (_, next) { + service.onAuthenticated(authenticated: next.valueOrNull?.isAuthenticated ?? false); + }, + ) + ..listen>( + delegationCompleteProvider, + fireImmediately: true, + (_, next) { + service.onDelegationCompleted(delegationCompleted: next.valueOrNull ?? false); + }, + ) + ..listen( + routeLocationProvider, + fireImmediately: true, + (_, next) { + service.onRouteChanged(next); + }, + ) + ..listen>( + bscWalletCheckProvider, + fireImmediately: true, + (_, next) { + if (!next.isLoading && next.hasValue) { + service.onUserHasBscWalletChanged(hasBscWallet: next.value!.hasBscWallet); + } + }, + ); + + return service; +} diff --git a/lib/app/features/wallets/providers/networks_provider.r.dart b/lib/app/features/wallets/providers/networks_provider.r.dart index 1744d61968..708a5de1df 100644 --- a/lib/app/features/wallets/providers/networks_provider.r.dart +++ b/lib/app/features/wallets/providers/networks_provider.r.dart @@ -17,6 +17,11 @@ Future> networks(Ref ref) { .then((networks) => networks.sortedBy((a) => a.displayName)); } +@riverpod +Stream> networksStream(Ref ref) { + return ref.watch(networksRepositoryProvider).watchAll().distinct(); +} + @riverpod Future> networksByTier(Ref ref, {required int tier}) { return ref diff --git a/lib/app/services/deep_link/deep_link_navigate_event.dart b/lib/app/services/deep_link/deep_link_navigate_event.dart index c973453817..b64b2ddff8 100644 --- a/lib/app/services/deep_link/deep_link_navigate_event.dart +++ b/lib/app/services/deep_link/deep_link_navigate_event.dart @@ -7,12 +7,12 @@ import 'package:ion/app/router/app_routes.gr.dart'; import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart'; class DeeplinkNavigateEvent extends UiEvent { - const DeeplinkNavigateEvent(this.path); + const DeeplinkNavigateEvent(this.path) : super(id: 'deeplink_navigate_$path'); final String path; @override - void performAction(BuildContext context) { + Future performAction(BuildContext context) async { final context = rootNavigatorKey.currentContext; if (context == null || !context.mounted) { return; @@ -31,7 +31,7 @@ class DeeplinkNavigateEvent extends UiEvent { path == SelfProfileRoute().location) { GoRouter.of(context).go(path); } else { - GoRouter.of(context).push(path); + await GoRouter.of(context).push(path); } } } diff --git a/lib/app/services/ui_event_queue/ui_event_queue_listener.dart b/lib/app/services/ui_event_queue/ui_event_queue_listener.dart index 0b081f137f..0b802296c9 100644 --- a/lib/app/services/ui_event_queue/ui_event_queue_listener.dart +++ b/lib/app/services/ui_event_queue/ui_event_queue_listener.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:ion/app/hooks/use_on_init.dart'; -import 'package:ion/app/router/app_routes.gr.dart'; import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart'; class UiEventQueueListener extends HookConsumerWidget { @@ -17,8 +16,7 @@ class UiEventQueueListener extends HookConsumerWidget { useOnInit( () { - final notifier = ref.read(uiEventQueueNotifierProvider.notifier); - notifier.consume()?.performAction(rootNavigatorKey.currentContext!); + ref.read(uiEventQueueNotifierProvider.notifier).processQueue(); }, uiEvents.toList(), ); diff --git a/lib/app/services/ui_event_queue/ui_event_queue_notifier.r.dart b/lib/app/services/ui_event_queue/ui_event_queue_notifier.r.dart index 70dc93f01c..cafef25915 100644 --- a/lib/app/services/ui_event_queue/ui_event_queue_notifier.r.dart +++ b/lib/app/services/ui_event_queue/ui_event_queue_notifier.r.dart @@ -3,14 +3,27 @@ import 'dart:collection'; import 'package:flutter/material.dart'; +import 'package:ion/app/router/app_routes.gr.dart'; +import 'package:ion/app/services/logger/logger.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'ui_event_queue_notifier.r.g.dart'; +@immutable abstract class UiEvent { - const UiEvent(); + const UiEvent({required this.id}); - void performAction(BuildContext context); + final String id; + + Future performAction(BuildContext context); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is UiEvent && runtimeType == other.runtimeType && id == other.id; + + @override + int get hashCode => id.hashCode; } @Riverpod(keepAlive: true) @@ -20,14 +33,33 @@ class UiEventQueueNotifier extends _$UiEventQueueNotifier { return Queue(); } + bool _processing = false; + void emit(UiEvent event) { - state = Queue.of(state)..add(event); + if (!state.contains(event)) { + state = Queue.of(state)..add(event); + } } - UiEvent? consume() { - if (state.isEmpty) return null; - final nextEvent = state.first; - state = Queue.of(state)..removeFirst(); - return nextEvent; + Future processQueue() async { + if (_processing) return; + _processing = true; + try { + while (state.isNotEmpty) { + final event = state.first; + try { + final context = rootNavigatorKey.currentContext; + if (context != null && context.mounted) { + await event.performAction(context); + } + } catch (error, stackTrace) { + Logger.error(error, stackTrace: stackTrace); + } finally { + state = Queue.of(state)..removeFirst(); + } + } + } finally { + _processing = false; + } } } diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index 0a2632f463..0a3b7893bf 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -937,6 +937,14 @@ "token_comment_holders_only": "التعليقات متاحة فقط لحاملي الرموز.", "tokenized_community_not_available_description": "إنشاء الرموز غير متاح للمنشورات التي تم إنشاؤها مسبقًا.", "tokenized_community_not_available_title": "خطأ في المحتوى المرمز", + "tokenized_community_onboarding_ads_description": "يحقق Online+ إيرادات من الإعلانات والمنشورات المعززة، ويتلقى المبدعون 50٪ من كل ما يتم تحقيقه من خلال هذه الميزات.", + "tokenized_community_onboarding_ads_title": "اكسب من الإعلانات والتعزيزات", + "tokenized_community_onboarding_rewards_description": "تم بناء Online+ من أجل المبدعين، حيث يتم دفع 50٪ من جميع الإيرادات التي تحققها المنصة لهم مباشرة، مما يخلق فرص ربح مستمرة.", + "tokenized_community_onboarding_rewards_title": "مكافآت تركز على المبدعين", + "tokenized_community_onboarding_support_description": "يحصل المستخدمون المميزون على تشفير أقوى وشارات موثقة وتجربة خالية من الإعلانات، بينما يتلقى المبدعون 50٪ من جميع رسوم الاشتراكات المميزة.", + "tokenized_community_onboarding_support_title": "المستخدمون المميزون يدعمون المبدعين", + "tokenized_community_onboarding_web3_description": "كل تفاعل مع رموز المبدعين، بما في ذلك الشراء والبيع وعمليات التحويل من وإلى المنصة، يولد رسومًا للمنصة، ويذهب 50٪ منها إلى المبدعين.", + "tokenized_community_onboarding_web3_title": "اكسب من نشاط Web3", "tokenized_community_token_creator": "رمز المنشئ", "tokenized_community_token_twitter": "رمز X", "tokenized_community_token_content": "رمز المحتوى", diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 3503613724..32facc1a0d 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -923,6 +923,14 @@ "token_comment_holders_only": "Коментарите са достъпни само за притежатели на токени.", "tokenized_community_not_available_description": "Създаването на токени не е налично за вече създадени публикации.", "tokenized_community_not_available_title": "Грешка при токенизирано съдържание", + "tokenized_community_onboarding_ads_description": "Online+ генерира приходи от реклами и подсилени публикации, а създателите получават 50% от всичко, създадено чрез тези функции.", + "tokenized_community_onboarding_ads_title": "Печелете от реклами и подсилвания", + "tokenized_community_onboarding_rewards_description": "Online+ е създадена за създатели, като 50% от всички приходи, които платформата генерира, се изплащат директно на тях, осигурявайки постоянни възможности за печалба.", + "tokenized_community_onboarding_rewards_title": "Награди, фокусирани върху създателите", + "tokenized_community_onboarding_support_description": "Премиум потребителите получават по-силно криптиране, потвърдени значки и изживяване без реклами, докато създателите получават 50% от всички премиум абонаментни такси.", + "tokenized_community_onboarding_support_title": "Премиум потребителите подкрепят създателите", + "tokenized_community_onboarding_web3_description": "Всяко взаимодействие с токени на създатели, включително купуване, продаване и on/off-ramp действия, генерира такси за платформата, като 50% от тях отиват при създателите.", + "tokenized_community_onboarding_web3_title": "Печелете от Web3 активност", "tokenized_community_token_creator": "Токен на създателя", "tokenized_community_token_twitter": "Токен на X", "tokenized_community_token_content": "Токен на съдържанието", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index fdae60d6a1..9fe3fc41a5 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -923,6 +923,14 @@ "token_comment_holders_only": "Kommentare sind nur für Token-Inhaber verfügbar.", "tokenized_community_not_available_description": "Die Tokenerstellung ist für zuvor erstellte Beiträge nicht verfügbar.", "tokenized_community_not_available_title": "Fehler bei tokenisiertem Inhalt", + "tokenized_community_onboarding_ads_description": "Online+ erzielt Einnahmen durch Werbung und hervorgehobene Beiträge, und Creator erhalten 50 % aller über diese Funktionen generierten Erlöse.", + "tokenized_community_onboarding_ads_title": "Verdienen mit Werbung und Boosts", + "tokenized_community_onboarding_rewards_description": "Online+ wurde für Creator entwickelt, und 50 % aller von der Plattform generierten Einnahmen werden direkt an sie ausgezahlt und schaffen kontinuierliche Verdienstmöglichkeiten.", + "tokenized_community_onboarding_rewards_title": "Creator-fokussierte Belohnungen", + "tokenized_community_onboarding_support_description": "Premium-Nutzer erhalten stärkere Verschlüsselung, verifizierte Abzeichen und ein werbefreies Erlebnis, während Creator 50 % aller Premium-Abonnementgebühren erhalten.", + "tokenized_community_onboarding_support_title": "Premium-Nutzer unterstützen Creator", + "tokenized_community_onboarding_web3_description": "Jede Interaktion mit Creator-Tokens, einschließlich Kauf, Verkauf und On-/Off-Ramp-Aktionen, erzeugt Plattformgebühren, von denen 50 % an Creator gehen.", + "tokenized_community_onboarding_web3_title": "Verdienen durch Web3-Aktivitäten", "tokenized_community_token_creator": "Creator-Token", "tokenized_community_token_twitter": "X-Token", "tokenized_community_token_content": "Content-Token", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f23ccfb21f..a040b37208 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -921,6 +921,14 @@ "token_comment_holders_only": "Comments are available only to token holders", "tokenized_community_not_available_description": "Token creation is not available for previously created posts.", "tokenized_community_not_available_title": "Tokenized content error", + "tokenized_community_onboarding_ads_description": "Online+ earns revenue from ads and boosted posts, and creators receive 50% of everything generated through these features.", + "tokenized_community_onboarding_ads_title": "Earn from ads and boosts", + "tokenized_community_onboarding_rewards_description": "Online+ is built for creators, and 50% of all revenue the platform generates is paid directly to them, creating consistent earning opportunities.", + "tokenized_community_onboarding_rewards_title": "Creator focused rewards", + "tokenized_community_onboarding_support_description": "Premium users get stronger encryption, verified badges, and an ad-free experience, while creators receive 50% of all premium subscription fees.", + "tokenized_community_onboarding_support_title": "Premium users support creators", + "tokenized_community_onboarding_web3_description": "Every interaction with creator tokens, including buy, sell, and on/off-ramp actions, generates platform fees, and 50% of all of it goes to creators.", + "tokenized_community_onboarding_web3_title": "Earn from Web3 activity", "tokenized_community_token_creator": "Creator token", "tokenized_community_token_twitter": "X token", "tokenized_community_token_content": "Content token", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 5beee78851..a8dc5171c7 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -923,6 +923,14 @@ "token_comment_holders_only": "Los comentarios están disponibles solo para los tenedores de tokens.", "tokenized_community_not_available_description": "La creación de tokens no está disponible para publicaciones creadas previamente.", "tokenized_community_not_available_title": "Error de contenido tokenizado", + "tokenized_community_onboarding_ads_description": "Online+ genera ingresos a partir de anuncios y publicaciones promocionadas, y los creadores reciben el 50% de todo lo generado a través de estas funciones.", + "tokenized_community_onboarding_ads_title": "Gana con anuncios y promociones", + "tokenized_community_onboarding_rewards_description": "Online+ está diseñada para creadores, y el 50% de todos los ingresos que genera la plataforma se paga directamente a ellos, creando oportunidades de ingresos constantes.", + "tokenized_community_onboarding_rewards_title": "Recompensas centradas en creadores", + "tokenized_community_onboarding_support_description": "Los usuarios premium obtienen un cifrado más fuerte, insignias verificadas y una experiencia sin anuncios, mientras que los creadores reciben el 50% de todas las tarifas de suscripción premium.", + "tokenized_community_onboarding_support_title": "Los usuarios premium apoyan a los creadores", + "tokenized_community_onboarding_web3_description": "Cada interacción con tokens de creadores, incluidas las acciones de compra, venta y entrada/salida, genera tarifas para la plataforma, y el 50% de todo se destina a los creadores.", + "tokenized_community_onboarding_web3_title": "Gana con la actividad Web3", "tokenized_community_token_creator": "Token de creador", "tokenized_community_token_twitter": "Token de X", "tokenized_community_token_content": "Token de contenido", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 733db9a72e..cd2b69a189 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -923,6 +923,14 @@ "token_comment_holders_only": "Les commentaires sont disponibles uniquement pour les détenteurs de tokens.", "tokenized_community_not_available_description": "La création de tokens n'est pas disponible pour les publications déjà créées.", "tokenized_community_not_available_title": "Erreur de contenu tokenisé", + "tokenized_community_onboarding_ads_description": "Online+ génère des revenus grâce aux publicités et aux publications boostées, et les créateurs reçoivent 50 % de tout ce qui est généré par ces fonctionnalités.", + "tokenized_community_onboarding_ads_title": "Gagnez grâce aux publicités et aux boosts", + "tokenized_community_onboarding_rewards_description": "Online+ est conçue pour les créateurs, et 50 % de tous les revenus générés par la plateforme leur sont versés directement, créant des opportunités de gains régulières.", + "tokenized_community_onboarding_rewards_title": "Récompenses axées sur les créateurs", + "tokenized_community_onboarding_support_description": "Les utilisateurs premium bénéficient d’un chiffrement renforcé, de badges vérifiés et d’une expérience sans publicité, tandis que les créateurs reçoivent 50 % de tous les frais d’abonnement premium.", + "tokenized_community_onboarding_support_title": "Les utilisateurs premium soutiennent les créateurs", + "tokenized_community_onboarding_web3_description": "Chaque interaction avec les tokens de créateurs, y compris l’achat, la vente et les actions d’entrée/sortie, génère des frais pour la plateforme, dont 50 % sont reversés aux créateurs.", + "tokenized_community_onboarding_web3_title": "Gagnez grâce à l’activité Web3", "tokenized_community_token_creator": "Token de créateur", "tokenized_community_token_twitter": "Token X", "tokenized_community_token_content": "Token de contenu", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 38528b9ee8..52b78fb80e 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -923,6 +923,14 @@ "token_comment_holders_only": "I commenti sono disponibili solo per i detentori di token.", "tokenized_community_not_available_description": "La creazione di token non è disponibile per i post creati in precedenza.", "tokenized_community_not_available_title": "Errore di contenuto tokenizzato", + "tokenized_community_onboarding_ads_description": "Online+ genera entrate da annunci e post potenziati, e i creator ricevono il 50% di tutto ciò che viene generato tramite queste funzionalità.", + "tokenized_community_onboarding_ads_title": "Guadagna da annunci e boost", + "tokenized_community_onboarding_rewards_description": "Online+ è pensata per i creator e il 50% di tutte le entrate generate dalla piattaforma viene pagato direttamente a loro, creando opportunità di guadagno costanti.", + "tokenized_community_onboarding_rewards_title": "Ricompense incentrate sui creator", + "tokenized_community_onboarding_support_description": "Gli utenti premium ottengono una crittografia più forte, badge verificati e un’esperienza senza annunci, mentre i creator ricevono il 50% di tutte le commissioni di abbonamento premium.", + "tokenized_community_onboarding_support_title": "Gli utenti premium supportano i creator", + "tokenized_community_onboarding_web3_description": "Ogni interazione con i token dei creator, inclusi acquisto, vendita e operazioni di on/off-ramp, genera commissioni per la piattaforma, e il 50% di esse va ai creator.", + "tokenized_community_onboarding_web3_title": "Guadagna dall’attività Web3", "tokenized_community_token_creator": "Token del creatore", "tokenized_community_token_twitter": "Token di X", "tokenized_community_token_content": "Token del contenuto", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index a61df767a7..78e4815881 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -923,6 +923,14 @@ "token_comment_holders_only": "Komentarze są dostępne tylko dla posiadaczy tokenów.", "tokenized_community_not_available_description": "Tworzenie tokenów nie jest dostępne dla wcześniej utworzonych postów.", "tokenized_community_not_available_title": "Błąd tokenizowanej treści", + "tokenized_community_onboarding_ads_description": "Online+ generuje przychody z reklam i promowanych postów, a twórcy otrzymują 50% wszystkiego, co jest generowane dzięki tym funkcjom.", + "tokenized_community_onboarding_ads_title": "Zarabiaj na reklamach i boostach", + "tokenized_community_onboarding_rewards_description": "Online+ zostało stworzone dla twórców, a 50% wszystkich przychodów generowanych przez platformę trafia bezpośrednio do nich, tworząc stałe możliwości zarobku.", + "tokenized_community_onboarding_rewards_title": "Nagrody skoncentrowane na twórcach", + "tokenized_community_onboarding_support_description": "Użytkownicy premium otrzymują silniejsze szyfrowanie, zweryfikowane odznaki i doświadczenie bez reklam, podczas gdy twórcy otrzymują 50% wszystkich opłat za subskrypcje premium.", + "tokenized_community_onboarding_support_title": "Użytkownicy premium wspierają twórców", + "tokenized_community_onboarding_web3_description": "Każda interakcja z tokenami twórców, w tym kupno, sprzedaż oraz operacje on/off-ramp, generuje opłaty dla platformy, z których 50% trafia do twórców.", + "tokenized_community_onboarding_web3_title": "Zarabiaj na aktywności Web3", "tokenized_community_token_creator": "Token twórcy", "tokenized_community_token_twitter": "Token X", "tokenized_community_token_content": "Token treści", diff --git a/lib/l10n/app_ro.arb b/lib/l10n/app_ro.arb index 00eaf66037..6c27279062 100644 --- a/lib/l10n/app_ro.arb +++ b/lib/l10n/app_ro.arb @@ -923,6 +923,14 @@ "token_comment_holders_only": "Comentariile sunt disponibile doar pentru deținătorii de tokenuri.", "tokenized_community_not_available_description": "Crearea de tokenuri nu este disponibilă pentru postările create anterior.", "tokenized_community_not_available_title": "Eroare de conținut tokenizat", + "tokenized_community_onboarding_ads_description": "Online+ generează venituri din reclame și postări promovate, iar creatorii primesc 50% din tot ceea ce este generat prin aceste funcții.", + "tokenized_community_onboarding_ads_title": "Câștigă din reclame și boost-uri", + "tokenized_community_onboarding_rewards_description": "Online+ este construită pentru creatori, iar 50% din toate veniturile generate de platformă sunt plătite direct acestora, creând oportunități constante de câștig.", + "tokenized_community_onboarding_rewards_title": "Recompense axate pe creatori", + "tokenized_community_onboarding_support_description": "Utilizatorii premium beneficiază de criptare mai puternică, insigne verificate și o experiență fără reclame, în timp ce creatorii primesc 50% din toate taxele de abonament premium.", + "tokenized_community_onboarding_support_title": "Utilizatorii premium susțin creatorii", + "tokenized_community_onboarding_web3_description": "Fiecare interacțiune cu tokenurile creatorilor, inclusiv cumpărare, vânzare și acțiuni de on/off-ramp, generează taxe pentru platformă, iar 50% din acestea revin creatorilor.", + "tokenized_community_onboarding_web3_title": "Câștigă din activitatea Web3", "tokenized_community_token_creator": "Token creator", "tokenized_community_token_twitter": "Token X", "tokenized_community_token_content": "Token conținut", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index e3ab61dfd8..4b87496b7f 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -923,6 +923,14 @@ "token_comment_holders_only": "Комментарии доступны только держателям токенов.", "tokenized_community_not_available_description": "Создание токенов недоступно для ранее созданных публикаций.", "tokenized_community_not_available_title": "Ошибка токенизированного контента", + "tokenized_community_onboarding_ads_description": "Online+ получает доход от рекламы и продвигаемых публикаций, а создатели получают 50% всего, что генерируется с помощью этих функций.", + "tokenized_community_onboarding_ads_title": "Зарабатывайте на рекламе и продвижении", + "tokenized_community_onboarding_rewards_description": "Online+ создана для создателей, и 50% всех доходов, генерируемых платформой, выплачиваются им напрямую, создавая стабильные возможности заработка.", + "tokenized_community_onboarding_rewards_title": "Вознаграждения для создателей", + "tokenized_community_onboarding_support_description": "Премиум-пользователи получают более сильное шифрование, подтверждённые значки и отсутствие рекламы, а создатели получают 50% всех премиальных подписочных сборов.", + "tokenized_community_onboarding_support_title": "Премиум-пользователи поддерживают создателей", + "tokenized_community_onboarding_web3_description": "Каждое взаимодействие с токенами создателей, включая покупку, продажу и операции on/off-ramp, приносит платформе комиссии, 50% которых получают создатели.", + "tokenized_community_onboarding_web3_title": "Зарабатывайте на активности Web3", "tokenized_community_token_creator": "Токен создателя", "tokenized_community_token_twitter": "Токен X", "tokenized_community_token_content": "Токен контента", diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index d3f5dfc169..af3137cb5d 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -923,6 +923,14 @@ "token_comment_holders_only": "Yorumlar yalnızca token sahipleri için kullanılabilir.", "tokenized_community_not_available_description": "Önceden oluşturulmuş gönderiler için token oluşturma kullanılamaz.", "tokenized_community_not_available_title": "Tokenlaştırılmış içerik hatası", + "tokenized_community_onboarding_ads_description": "Online+, reklamlardan ve öne çıkarılan gönderilerden gelir elde eder ve içerik üreticileri bu özellikler üzerinden elde edilen her şeyin %50’sini alır.", + "tokenized_community_onboarding_ads_title": "Reklamlar ve boost’lardan kazanın", + "tokenized_community_onboarding_rewards_description": "Online+, içerik üreticiler için tasarlanmıştır ve platformun ürettiği tüm gelirin %50’si doğrudan onlara ödenerek sürekli kazanç fırsatları yaratır.", + "tokenized_community_onboarding_rewards_title": "İçerik üretici odaklı ödüller", + "tokenized_community_onboarding_support_description": "Premium kullanıcılar daha güçlü şifreleme, doğrulanmış rozetler ve reklamsız bir deneyim elde ederken, içerik üreticileri tüm premium abonelik ücretlerinin %50’sini alır.", + "tokenized_community_onboarding_support_title": "Premium kullanıcılar içerik üreticileri destekler", + "tokenized_community_onboarding_web3_description": "İçerik üretici token’larıyla yapılan satın alma, satış ve on/off-ramp işlemleri dahil her etkileşim platform ücretleri oluşturur ve bunların %50’si içerik üreticilerine gider.", + "tokenized_community_onboarding_web3_title": "Web3 aktivitelerinden kazanın", "tokenized_community_token_creator": "Oluşturucu tokenı", "tokenized_community_token_twitter": "X tokenı", "tokenized_community_token_content": "İçerik tokenı", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 2e74a2a6d6..b3f193ea72 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -923,6 +923,14 @@ "token_comment_holders_only": "评论仅对代币持有者开放。", "tokenized_community_not_available_description": "已创建的帖子无法创建代币。", "tokenized_community_not_available_title": "代币化内容错误", + "tokenized_community_onboarding_ads_description": "Online+ 通过广告和推广帖子获得收入,创作者可获得通过这些功能产生的全部收入的 50%。", + "tokenized_community_onboarding_ads_title": "通过广告和推广赚钱", + "tokenized_community_onboarding_rewards_description": "Online+ 专为创作者打造,平台产生的所有收入中有 50% 直接支付给创作者,创造持续的盈利机会。", + "tokenized_community_onboarding_rewards_title": "以创作者为中心的奖励", + "tokenized_community_onboarding_support_description": "高级用户可享受更强的加密、已验证徽章和无广告体验,而创作者可获得所有高级订阅费用的 50%。", + "tokenized_community_onboarding_support_title": "高级用户支持创作者", + "tokenized_community_onboarding_web3_description": "与创作者代币的每一次交互,包括买入、卖出以及出入金操作,都会产生平台费用,其中 50% 归创作者所有。", + "tokenized_community_onboarding_web3_title": "通过 Web3 活动赚钱", "tokenized_community_token_creator": "创作者代币", "tokenized_community_token_twitter": "X 代币", "tokenized_community_token_content": "内容代币",