Skip to content
Open
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
22 changes: 22 additions & 0 deletions assets/svg/icon_token_fire.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ Future<TradeCommunityTokenService> tradeCommunityTokenService(
);
}

@riverpod
Future<String> bondingCurveAddress(Ref ref) async {
final repository = await ref.watch(tradeCommunityTokenRepositoryProvider.future);
return repository.fetchBondingCurveAddress();
}

@riverpod
Future<List<CoinData>> supportedSwapTokens(Ref ref) async {
final api = await ref.watch(tradeCommunityTokenApiProvider.future);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import 'package:ion/app/utils/num.dart';
import 'package:ion/generated/assets.gen.dart';
import 'package:ion_token_analytics/ion_token_analytics.dart';

enum RankBadgeType {
regular,
burning,
bondingCurve,
}

class BondingCurveHolderTile extends StatelessWidget {
const BondingCurveHolderTile({
required this.holder,
Expand All @@ -26,6 +32,31 @@ class BondingCurveHolderTile extends StatelessWidget {
displayName: context.i18n.tokenized_community_bonding_curve,
supplyShare: holder.position.supplyShare,
avatarUrl: Assets.svg.iconBondingCurveAvatar,
badgeType: RankBadgeType.bondingCurve,
);
}
}

class BurningHolderTile extends StatelessWidget {
const BurningHolderTile({
required this.holder,
super.key,
});

final TopHolder holder;

@override
Widget build(BuildContext context) {
final holderAddress = holder.position.holder?.addresses?.ionConnect ?? '';

return HolderTile(
rank: holder.position.rank,
amountText: formatAmountCompactFromRaw(holder.position.amount),
displayName: holder.position.holder?.display ?? context.i18n.tokenized_community_burned,
supplyShare: holder.position.supplyShare,
avatarUrl: holder.position.holder?.avatar,
badgeType: RankBadgeType.burning,
address: holderAddress,
);
}
}
Expand Down Expand Up @@ -77,6 +108,8 @@ class HolderTile extends StatelessWidget {
this.avatarUrl,
this.holderAddress,
this.isXUser = false,
this.badgeType = RankBadgeType.regular,
this.address,
super.key,
});

Expand All @@ -90,6 +123,8 @@ class HolderTile extends StatelessWidget {
final String? avatarUrl;
final String? holderAddress;
final bool isXUser;
final RankBadgeType badgeType;
final String? address;

@override
Widget build(BuildContext context) {
Expand All @@ -111,7 +146,7 @@ class HolderTile extends StatelessWidget {
Expanded(
child: Row(
children: [
_RankBadge(rank: rank),
_RankBadge(rank: rank, type: badgeType),
SizedBox(width: 12.0.s),
HolderAvatar(
imageUrl: avatarUrl,
Expand All @@ -123,6 +158,7 @@ class HolderTile extends StatelessWidget {
child: _NameAndAmount(
name: displayName,
handle: username,
address: address,
isCreator: isCreator,
verified: verified,
amountText: amountText,
Expand Down Expand Up @@ -150,22 +186,30 @@ class HolderTile extends StatelessWidget {
}
}

///
/// rank 1 -> bonding curve
/// rank 2 -> 1st medal badge
/// rank 3 -> 2nd medal badge
/// rank 4 -> 3rd medal badge
/// rank n -> n-1 text badge
///
class _RankBadge extends StatelessWidget {
const _RankBadge({required this.rank});
const _RankBadge({
required this.rank,
required this.type,
});

final int rank;
final RankBadgeType type;

@override
Widget build(BuildContext context) {
final colors = context.theme.appColors;
final isMedal = rank <= 4;

final child = switch (type) {
RankBadgeType.burning => Assets.svg.iconTokenFire.icon(),
RankBadgeType.bondingCurve => Assets.svg.iconMemeBondingcurve.icon(),
RankBadgeType.regular => rank <= 3
? _MedalIcon(rank: rank)
: Text(
'$rank',
style: context.theme.appTextThemes.body.copyWith(color: colors.primaryAccent),
),
};

return Container(
width: 30.0.s,
height: 30.0.s,
Expand All @@ -174,12 +218,7 @@ class _RankBadge extends StatelessWidget {
borderRadius: BorderRadius.circular(10.0.s),
),
alignment: Alignment.center,
child: isMedal
? _MedalIcon(rank: rank)
: Text(
'${rank - 1}',
style: context.theme.appTextThemes.body.copyWith(color: colors.primaryAccent),
),
child: child,
);
}
}
Expand All @@ -192,10 +231,9 @@ class _MedalIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
return switch (rank) {
1 => Assets.svg.iconMemeBondingcurve,
2 => Assets.svg.iconMeme1stplace,
3 => Assets.svg.iconMeme2ndtplace,
4 => Assets.svg.iconMeme3rdplace,
1 => Assets.svg.iconMeme1stplace,
2 => Assets.svg.iconMeme2ndtplace,
3 => Assets.svg.iconMeme3rdplace,
_ => throw UnimplementedError(),
}
.icon();
Expand All @@ -205,15 +243,17 @@ class _MedalIcon extends StatelessWidget {
class _NameAndAmount extends StatelessWidget {
const _NameAndAmount({
required this.name,
required this.handle,
required this.amountText,
required this.verified,
required this.isCreator,
this.handle,
this.address,
this.isXUser = true,
});

final String name;
final String? handle;
final String? address;
final String amountText;
final bool verified;
final bool isCreator;
Expand Down Expand Up @@ -256,7 +296,11 @@ class _NameAndAmount extends StatelessWidget {
],
),
Text(
handle != null ? '$handle • $amountText' : amountText,
address != null
? shortenAddress(address!)
: handle != null
? '$handle • $amountText'
: amountText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: texts.caption.copyWith(color: colors.quaternaryText),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/tokenized_communities/providers/trade_infrastructure_providers.r.dart';
import 'package:ion/app/features/tokenized_communities/views/pages/holders/components/holder_tile.dart';
import 'package:ion/app/features/tokenized_communities/views/pages/holders/components/top_holders/components/top_holders_empty.dart';
import 'package:ion/app/features/tokenized_communities/views/pages/holders/components/top_holders/components/top_holders_skeleton.dart';
import 'package:ion/app/features/tokenized_communities/views/pages/holders/providers/token_top_holders_provider.r.dart';
import 'package:ion/app/router/app_routes.gr.dart';
import 'package:ion/generated/assets.gen.dart';
import 'package:ion_token_analytics/ion_token_analytics.dart';

const int holdersCountLimit = 5;

Expand Down Expand Up @@ -141,6 +143,7 @@ class _TopHolderList extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final holdersAsync =
ref.watch(tokenTopHoldersProvider(externalAddress, limit: holdersCountLimit));
final boundingCurveAddress = ref.watch(bondingCurveAddressProvider).valueOrNull;
return holdersAsync.when(
data: (holders) {
if (holders.isEmpty) {
Expand All @@ -153,13 +156,18 @@ class _TopHolderList extends ConsumerWidget {
padding: EdgeInsets.zero,
itemCount: holders.length,
itemBuilder: (context, index) {
if (index == 0) {
final holder = holders[index];

if (boundingCurveAddress != null && holder.isBoundingCurve(boundingCurveAddress)) {
return BondingCurveHolderTile(
holder: holders[index],
holder: holder,
);
}

final holder = holders[index];
if (holder.isBurning) {
return BurningHolderTile(holder: holder);
}

return TopHolderTile(holder: holder);
},
separatorBuilder: (context, index) => SizedBox(height: 4.0.s),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:ion/app/components/scroll_view/load_more_builder.dart';
import 'package:ion/app/components/scroll_view/pull_to_refresh_builder.dart';
import 'package:ion/app/components/separated/separator.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/tokenized_communities/providers/trade_infrastructure_providers.r.dart';
import 'package:ion/app/features/tokenized_communities/views/pages/holders/components/holder_tile.dart';
import 'package:ion/app/features/tokenized_communities/views/pages/holders/components/top_holders/components/top_holders_skeleton.dart';
import 'package:ion/app/features/tokenized_communities/views/pages/holders/providers/token_top_holders_provider.r.dart';
Expand All @@ -28,7 +29,7 @@ class HoldersPage extends HookConsumerWidget {
final topHoldersProvider = tokenTopHoldersProvider(externalAddress, limit: 20);
final topHoldersAsync = ref.watch(topHoldersProvider);
final topHolders = topHoldersAsync.valueOrNull ?? const <TopHolder>[];

final boundingCurveAddress = ref.watch(bondingCurveAddressProvider).valueOrNull;
return Scaffold(
appBar: NavigationAppBar.screen(
title: Text(context.i18n.holders, style: context.theme.appTextThemes.subtitle2),
Expand All @@ -53,22 +54,32 @@ class HoldersPage extends HookConsumerWidget {
itemBuilder: (context, index) {
final topPadding = index == 0 ? 12.s : 7.s;
final bottomPadding = 7.s;
final holder = topHolders[index];

if (index == 0) {
if (boundingCurveAddress != null &&
holder.isBoundingCurve(boundingCurveAddress)) {
return _HoldersListPadding(
topPadding: topPadding,
bottomPadding: bottomPadding,
child: BondingCurveHolderTile(
holder: topHolders[index],
holder: holder,
),
);
}

if (holder.isBurning) {
return _HoldersListPadding(
topPadding: topPadding,
bottomPadding: bottomPadding,
child: BurningHolderTile(holder: holder),
);
}

return _HoldersListPadding(
topPadding: topPadding,
bottomPadding: bottomPadding,
child: TopHolderTile(
holder: topHolders[index],
holder: holder,
),
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,21 +223,16 @@ class TokenTopHolders extends _$TokenTopHolders {
}

final rank = item.position.rank;
final insertAt = (rank - 1).clamp(0, list.length);
list.insert(insertAt, item);

_normalizeRanks(list);
}

void _normalizeRanks(List<TopHolder> list) {
for (var i = 0; i < list.length; i++) {
final desiredRank = i + 1;
final current = list[i];
if (current.position.rank != desiredRank) {
list[i] = current.copyWith(
position: current.position.copyWith(rank: desiredRank),
);
}
if (rank == 0) {
// Count existing rank 0 items to insert after them
final rankZeroCount = list.where((h) => h.position.rank == 0).length;
list.insert(rankZeroCount, item);
} else {
// Count rank 0 items, then insert after them using rank - 1
final rankZeroCount = list.where((h) => h.position.rank == 0).length;
final insertAt = (rankZeroCount + rank - 1).clamp(0, list.length);
list.insert(insertAt, item);
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_ar.arb
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,7 @@
"tokenized_community_comments_empty": "كن أول من ينضم إلى المحادثة",
"tokenized_community_comments_tab": "التعليقات",
"tokenized_community_holders_tab": "الحائزون",
"tokenized_community_burned": "محروق",
"token_comment_holders_only": "التعليقات متاحة فقط لحاملي الرموز.",
"tokenized_community_not_available_description": "إنشاء الرموز غير متاح للمنشورات التي تم إنشاؤها مسبقًا.",
"tokenized_community_not_available_title": "خطأ في المحتوى المرمز",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_bg.arb
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@
"tokenized_community_creator_token_live_title": "Токенът на създателя е АКТИВЕН!",
"tokenized_community_creator_token_live_subtitle": "Поздравления, вашият токен на създателя вече е активен и достъпен за търговия от всички",
"tokenized_community_holders_tab": "Притежатели",
"tokenized_community_burned": "Изгорени",
"token_comment_holders_only": "Коментарите са достъпни само за притежатели на токени.",
"tokenized_community_not_available_description": "Създаването на токени не е налично за вече създадени публикации.",
"tokenized_community_not_available_title": "Грешка при токенизирано съдържание",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@
"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",
"tokenized_community_burned": "Verbrannt",
"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",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,7 @@
"tokenized_community_comments_empty": "Be the first to join the conversation",
"tokenized_community_comments_tab": "Comments",
"tokenized_community_holders_tab": "Holders",
"tokenized_community_burned": "Burned",
"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",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@
"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",
"tokenized_community_burned": "Quemado",
"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",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@
"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",
"tokenized_community_burned": "Brûlé",
"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é",
Expand Down
Loading