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
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,35 @@ class GradientBorderPainter extends CustomPainter {
required this.gradient,
this.strokeWidth = 2,
this.cornerRadius = 12,
this.backgroundColor,
});

final Gradient gradient;
final double strokeWidth;
final double cornerRadius;
final Color? backgroundColor;

@override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
final paint = Paint()
final rrect = RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius));

// Draw background if color is provided
if (backgroundColor != null) {
final backgroundPaint = Paint()
..color = backgroundColor!
..style = PaintingStyle.fill;

canvas.drawRRect(rrect, backgroundPaint);
}

// Draw gradient border
final borderPaint = Paint()
..shader = gradient.createShader(rect)
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth;

final rrect = RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius));
canvas.drawRRect(rrect, paint);
canvas.drawRRect(rrect, borderPaint);
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:ion/app/features/feed/notifications/providers/notifications/repo
import 'package:ion/app/features/feed/notifications/providers/notifications/token_launch_notification_handler.r.dart';
import 'package:ion/app/features/ion_connect/ion_connect.dart';
import 'package:ion/app/features/ion_connect/model/global_subscription_event_handler.dart';
import 'package:ion/app/features/tokenized_communities/providers/community_token_definition_handler.r.dart';
import 'package:ion/app/features/user/providers/badge_award_handler.r.dart';
import 'package:ion/app/features/user/providers/user_delegation_handler.r.dart';
import 'package:ion/app/services/logger/logger.dart';
Expand All @@ -37,6 +38,7 @@ Future<EventsManagementService> eventsManagementService(Ref ref) async {
ref.watch(badgeAwardHandlerProvider),
ref.watch(userDelegationHandlerProvider),
ref.watch(tokenLaunchNotificationHandlerProvider),
ref.watch(communityTokenDefinitionHandlerProvider),
];

final manager = EventsManagementService(handlers);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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/ion_connect/ion_connect.dart';
import 'package:ion/app/features/ion_connect/model/event_reference.f.dart';
import 'package:ion/app/features/ion_connect/model/global_subscription_event_handler.dart';
import 'package:ion/app/features/ion_connect/providers/ion_connect_cache.r.dart';
import 'package:ion/app/features/tokenized_communities/models/entities/community_token_definition.f.dart';
import 'package:ion/app/features/tokenized_communities/views/creator_token_is_live_dialog.dart';
import 'package:ion/app/features/user/model/user_metadata.f.dart';
import 'package:ion/app/services/storage/local_storage.r.dart';
import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'community_token_definition_handler.r.g.dart';

class CommunityTokenDefinitionHandler extends GlobalSubscriptionEventHandler {
CommunityTokenDefinitionHandler({
required this.localStorage,
required this.ionConnectCache,
required this.uiEventQueueCallback,
required this.currentUserMasterPubkey,
});

final LocalStorage localStorage;
final IonConnectCache ionConnectCache;
final String? currentUserMasterPubkey;
final void Function(ReplaceableEventReference tokenDefinitionEventReference) uiEventQueueCallback;

String get localStorageKey => 'creator_token_is_live_dialog_shown_$currentUserMasterPubkey';

@override
bool canHandle(EventMessage eventMessage) {
return eventMessage.kind == CommunityTokenDefinitionEntity.kind;
}

@override
Future<void> handle(EventMessage eventMessage) async {
final entity = CommunityTokenDefinitionEntity.fromEventMessage(eventMessage);

if (entity
case CommunityTokenDefinitionEntity(
data: CommunityTokenDefinitionIon(
eventReference: ReplaceableEventReference(
masterPubkey: final originalEventMasterPubkey,
kind: UserMetadataEntity.kind
),
type: CommunityTokenDefinitionIonType.firstBuyAction
)
)
when originalEventMasterPubkey == currentUserMasterPubkey &&
!(localStorage.getBool(localStorageKey) ?? false)) {
uiEventQueueCallback(entity.toEventReference());
await localStorage.setBool(key: localStorageKey, value: true);
}

await ionConnectCache.cache(entity);
}
}

@riverpod
CommunityTokenDefinitionHandler communityTokenDefinitionHandler(Ref ref) {
final localStorage = ref.watch(localStorageProvider);
final cache = ref.watch(ionConnectCacheProvider.notifier);
final currentUserMasterPubkey = ref.watch(currentPubkeySelectorProvider);

void uiEventQueueCallback(ReplaceableEventReference tokenDefinitionEventReference) {
ref
.read(uiEventQueueNotifierProvider.notifier)
.emit(CreatorTokenIsLiveDialogEvent(tokenDefinitionEventReference));
}

return CommunityTokenDefinitionHandler(
ionConnectCache: cache,
localStorage: localStorage,
uiEventQueueCallback: uiEventQueueCallback,
currentUserMasterPubkey: currentUserMasterPubkey,
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/components/avatar/avatar.dart';
import 'package:ion/app/components/button/button.dart';
import 'package:ion/app/components/gradient_border_painter/gradient_border_painter.dart';
import 'package:ion/app/components/progress_bar/ion_loading_indicator.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/auth/providers/auth_provider.m.dart';
import 'package:ion/app/features/feed/views/pages/feed_page/components/stories/mock.dart';
import 'package:ion/app/features/ion_connect/model/event_reference.f.dart';
import 'package:ion/app/features/tokenized_communities/providers/token_market_info_provider.r.dart';
import 'package:ion/app/features/user/model/profile_mode.dart';
import 'package:ion/app/features/user/pages/profile_page/components/profile_background.dart';
import 'package:ion/app/features/user/pages/profile_page/components/profile_details/user_name_tile/user_name_tile.dart';
import 'package:ion/app/features/user/providers/user_metadata_provider.r.dart';
import 'package:ion/app/hooks/use_avatar_colors.dart';
import 'package:ion/app/router/app_routes.gr.dart';
import 'package:ion/app/router/components/navigation_app_bar/navigation_close_button.dart';
import 'package:ion/app/router/utils/show_simple_bottom_sheet.dart';
import 'package:ion/app/services/logger/logger.dart';
import 'package:ion/app/services/ui_event_queue/ui_event_queue_notifier.r.dart';
import 'package:ion/generated/assets.gen.dart';

class CreatorTokenIsLiveDialogEvent extends UiEvent {
CreatorTokenIsLiveDialogEvent(this.tokenDefinitionEventReference);

static bool shown = false;
final ReplaceableEventReference tokenDefinitionEventReference;

@override
void performAction(BuildContext context) {
if (!shown) {
shown = true;
showSimpleBottomSheet<void>(
context: context,
backgroundColor: context.theme.appColors.forest,
child:
CreatorTokenIsLiveDialog(tokenDefinitionEventReference: tokenDefinitionEventReference),
).whenComplete(() => shown = false);
}
}
}

class CreatorTokenIsLiveDialog extends HookConsumerWidget {
const CreatorTokenIsLiveDialog({required this.tokenDefinitionEventReference, super.key});

final ReplaceableEventReference tokenDefinitionEventReference;

@override
Widget build(BuildContext context, WidgetRef ref) {
final avatarUrl = ref.watch(currentUserMetadataProvider).value?.data.avatarUrl;
final imageColors = useImageColors(avatarUrl);

return ProfileGradientBackground(
colors: imageColors ?? useAvatarFallbackColors,
disableDarkGradient: false,
child: _ContentState(tokenDefinitionEventReference),
);
}
}

class _ContentState extends HookConsumerWidget {
const _ContentState(this.tokenDefinitionEventReference);

final ReplaceableEventReference tokenDefinitionEventReference;

@override
Widget build(BuildContext context, WidgetRef ref) {
final isLoading = useState(false);
final currentUserMasterPubkey = ref.watch(currentPubkeySelectorProvider) ?? '';
final userMetadata = ref.watch(currentUserMetadataProvider).valueOrNull;
final avatarUrl = userMetadata?.data.avatarUrl ?? '';

final eventReferenceString = userMetadata?.toEventReference().toString();

final token = eventReferenceString != null
? ref.watch(tokenMarketInfoProvider(eventReferenceString)).valueOrNull
: null;

return Stack(
children: [
PositionedDirectional(
bottom: 0,
start: 0,
end: 0,
child: Assets.images.tokenizedCommunities.creatorMonetizationLiveRays
.iconWithDimensions(width: 461.s, height: 461.s),
),
PositionedDirectional(
end: 8,
child: NavigationCloseButton(color: context.theme.appColors.onPrimaryAccent),
),
ScreenSideOffset.medium(
child: Column(
children: [
SizedBox(height: 30.0.s),
CustomPaint(
painter: GradientBorderPainter(
strokeWidth: 2.0.s,
cornerRadius: 26.0.s,
gradient: storyBorderGradients[3],
backgroundColor: context.theme.appColors.forest.withAlpha(125),
),
child: Padding(
padding: EdgeInsets.all(18.0.s),
child: Avatar(
size: 64.0.s,
fit: BoxFit.cover,
imageUrl: avatarUrl,
borderRadius: BorderRadius.all(Radius.circular(16.0.s)),
),
),
),
SizedBox(height: 8.0.s),
UserNameTile(
showProfileTokenPrice: true,
profileMode: ProfileMode.dark,
pubkey: currentUserMasterPubkey,
priceUsd: token?.marketData.priceUSD,
),
SizedBox(height: 18.0.s),
Text(
context.i18n.tokenized_community_creator_token_live_title,
textAlign: TextAlign.center,
style: context.theme.appTextThemes.title
.copyWith(color: context.theme.appColors.onPrimaryAccent),
),
SizedBox(height: 8.0.s),
Text(
context.i18n.tokenized_community_creator_token_live_subtitle,
textAlign: TextAlign.center,
style: context.theme.appTextThemes.body2
.copyWith(color: context.theme.appColors.secondaryBackground),
),
SizedBox(height: 24.0.s),
Button(
disabled: isLoading.value,
label: Text(context.i18n.button_share),
minimumSize: Size(double.infinity, 56.0.s),
trailingIcon:
isLoading.value ? const IONLoadingIndicator() : const SizedBox.shrink(),
onPressed: token != null
? () async {
isLoading.value = true;
try {
if (context.mounted) {
context.pop();

await ShareViaMessageModalRoute(
eventReference: tokenDefinitionEventReference.encode(),
).push<void>(context);
}
} catch (e, st) {
Logger.error(e, stackTrace: st);
} finally {
isLoading.value = false;
}
}
: null,
),
ScreenBottomOffset(),
],
),
),
],
);
}
}
3 changes: 3 additions & 0 deletions lib/l10n/app_ar.arb
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,9 @@
"tokenized_communities_trending_tokens": "التوكنات الرائجة",
"tokenized_community_bonding_curve": "منحنى الترابط",
"tokenized_community_chart_tab": "المخطط",
"tokenized_community_creator_token_live_title": "توكن المنشئ مباشر الآن!",
"tokenized_community_creator_token_live_subtitle": "تهانينا، توكن المنشئ الخاص بك مباشر الآن ومتاح للجميع للتداول",
"tokenized_community_comments_tab": "التعليقات",
"tokenized_community_comments_empty": "كن أول من ينضم إلى المحادثة",
"tokenized_community_comments_tab": "التعليقات",
"tokenized_community_holders_tab": "الحائزون",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/app_bg.arb
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,8 @@
"tokenized_community_chart_tab": "Графика",
"tokenized_community_comments_empty": "Бъдете първият, който се присъединява към разговора",
"tokenized_community_comments_tab": "Коментари",
"tokenized_community_creator_token_live_title": "Токенът на създателя е АКТИВЕН!",
"tokenized_community_creator_token_live_subtitle": "Поздравления, вашият токен на създателя вече е активен и достъпен за търговия от всички",
"tokenized_community_holders_tab": "Притежатели",
"token_comment_holders_only": "Коментарите са достъпни само за притежатели на токени.",
"tokenized_community_not_available_description": "Създаването на токени не е налично за вече създадени публикации.",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,8 @@
"tokenized_community_chart_tab": "Chart",
"tokenized_community_comments_empty": "Sei der Erste, der sich an der Unterhaltung beteiligt",
"tokenized_community_comments_tab": "Kommentare",
"tokenized_community_creator_token_live_title": "Creator-Token ist LIVE!",
"tokenized_community_creator_token_live_subtitle": "Herzlichen Glückwunsch, Ihr Creator-Token ist jetzt live und für jeden handelbar",
"tokenized_community_holders_tab": "Inhaber",
"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.",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,8 @@
"tokenized_community_token_twitter": "X token",
"tokenized_community_token_content": "Content token",
"tokenized_community_trades_tab": "Trades",
"tokenized_community_creator_token_live_title": "Creator token is LIVE!",
"tokenized_community_creator_token_live_subtitle": "Congratulations, your creator token is now live and available for everyone to trade it",
"toolbar_link_placeholder": "https://example.com",
"toolbar_link_title": "Add a link",
"top_holders_empty": "Become the first holder",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/app_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,8 @@
"tokenized_community_chart_tab": "Gráfico",
"tokenized_community_comments_empty": "Sé el primero en unirse a la conversación",
"tokenized_community_comments_tab": "Comentarios",
"tokenized_community_creator_token_live_title": "¡El token del creador está EN VIVO!",
"tokenized_community_creator_token_live_subtitle": "Felicidades, tu token de creador ya está en vivo y disponible para que todos lo intercambien",
"tokenized_community_holders_tab": "Tenedores",
"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.",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,8 @@
"tokenized_community_chart_tab": "Graphique",
"tokenized_community_comments_empty": "Soyez le premier à rejoindre la conversation",
"tokenized_community_comments_tab": "Commentaires",
"tokenized_community_creator_token_live_title": "Le token du créateur est EN DIRECT !",
"tokenized_community_creator_token_live_subtitle": "Félicitations, votre token de créateur est maintenant en direct et disponible pour que tout le monde puisse l'échanger",
"tokenized_community_holders_tab": "Détenteurs",
"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.",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/app_it.arb
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,8 @@
"tokenized_community_chart_tab": "Grafico",
"tokenized_community_comments_empty": "Sii il primo a unirti alla conversazione",
"tokenized_community_comments_tab": "Commenti",
"tokenized_community_creator_token_live_title": "Il token del creatore è LIVE!",
"tokenized_community_creator_token_live_subtitle": "Congratulazioni, il tuo token creatore è ora attivo e disponibile per tutti per scambiarlo",
"tokenized_community_holders_tab": "Detentori",
"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.",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/app_pl.arb
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,8 @@
"tokenized_community_chart_tab": "Wykres",
"tokenized_community_comments_empty": "Bądź pierwszym, który dołączy do rozmowy",
"tokenized_community_comments_tab": "Komentarze",
"tokenized_community_creator_token_live_title": "Token twórcy jest NA ŻYWO!",
"tokenized_community_creator_token_live_subtitle": "Gratulacje, Twój token twórcy jest teraz aktywny i dostępny dla wszystkich do handlu",
"tokenized_community_holders_tab": "Posiadacze",
"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.",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/app_ro.arb
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,8 @@
"tokenized_community_chart_tab": "Grafic",
"tokenized_community_comments_empty": "Fii primul care se alătură conversației",
"tokenized_community_comments_tab": "Comentarii",
"tokenized_community_creator_token_live_title": "Tokenul de creator este LIVE!",
"tokenized_community_creator_token_live_subtitle": "Felicitări, tokenul tău de creator este acum activ și disponibil pentru toată lumea să îl tranzacționeze",
"tokenized_community_holders_tab": "Deținători",
"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.",
Expand Down
Loading