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 @@ -82,7 +82,6 @@ class ProfileTokenHeader extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final creatorPubkey = MasterPubkeyResolver.resolve(
externalAddress,
eventReference: null,
);

final isCurrentUserProfile = ref.watch(isCurrentUserSelectorProvider(creatorPubkey));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Stream<CommunityToken?> tokenMarketInfo(
);
}

yield currentToken;
yield currentToken ?? cachedToken;

// 2. Subscribe to real-time updates
final subscription = await client.communityTokens.subscribeToTokenInfo(externalAddress);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ class TokenOperationProtectedAccountsService {
}

// Checks if the account associated with the given external address is protected from token operations.
// for x tokens external address has not a master pubkey so catch exception and return false
bool isProtectedAccountFromExternalAddress(String externalAddress) {
final masterPubkey = MasterPubkeyResolver.resolve(
externalAddress,
eventReference: null,
);
return isProtectedAccount(masterPubkey);
try {
final masterPubkey = MasterPubkeyResolver.resolve(
externalAddress,
);
return isProtectedAccount(masterPubkey);
} catch (_) {
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:ion/app/features/ion_connect/model/event_reference.f.dart';
class MasterPubkeyResolver {
MasterPubkeyResolver._();

static String resolve(String externalAddress, {required EventReference? eventReference}) {
static String resolve(String externalAddress, {EventReference? eventReference}) {
return eventReference?.masterPubkey ??
ReplaceableEventReference.fromString(externalAddress).masterPubkey;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class CreatorTokensHeader extends ConsumerWidget {
required this.backButtonIcon,
required this.onPop,
required this.onSearchToggle,
this.carouselKey,
this.scrollController,
super.key,
});
Expand All @@ -38,6 +39,7 @@ class CreatorTokensHeader extends ConsumerWidget {
final Widget backButtonIcon;
final VoidCallback onPop;
final VoidCallback onSearchToggle;
final Key? carouselKey;
final ScrollController? scrollController;

@override
Expand Down Expand Up @@ -65,19 +67,24 @@ class CreatorTokensHeader extends ConsumerWidget {
),
Opacity(
opacity: 1 - opacity,
child: featuredTokensAsync.when(
data: (tokens) {
if (tokens.isEmpty) return const SizedBox.shrink();
child: SizedBox(
key: carouselKey,
width: double.infinity,
height: CreatorTokensCarousel.carouselHeight.s,
child: featuredTokensAsync.when(
data: (tokens) {
if (tokens.isEmpty) return const SizedBox.shrink();

return CreatorTokensCarousel(
tokens: tokens,
onItemChanged: (token) {
selectedToken.value = token;
},
);
},
loading: () => const CreatorTokensCarouselSkeleton(),
error: (_, __) => const SizedBox.shrink(),
return CreatorTokensCarousel(
tokens: tokens,
onItemChanged: (token) {
selectedToken.value = token;
},
);
},
loading: () => const CreatorTokensCarouselSkeleton(),
error: (_, __) => const SizedBox.shrink(),
),
),
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import 'package:ion/app/hooks/use_avatar_colors.dart';
import 'package:ion/app/hooks/use_on_init.dart';
import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart';
import 'package:ion/app/router/components/navigation_app_bar/navigation_back_button.dart';
import 'package:ion/app/router/utils/back_gesture_exclusion.dart';
import 'package:ion/generated/assets.gen.dart';
import 'package:ion_token_analytics/ion_token_analytics.dart';

Expand Down Expand Up @@ -64,6 +65,60 @@ class CreatorTokensPage extends HookConsumerWidget {
final isGlobalSearchVisible = useState<bool>(false);
final lastSearchQuery = useRef<String?>(null);

final carouselKey = useMemoized(GlobalKey.new);
final carouselRect = useMemoized(() => ValueNotifier<Rect?>(null));
final route = ModalRoute.of(context);

void updateCarouselRect() {
final carouselContext = carouselKey.currentContext;
if (carouselContext == null) {
if (carouselRect.value != null) {
carouselRect.value = null;
}
return;
}

final renderObject = carouselContext.findRenderObject();
if (renderObject is! RenderBox || !renderObject.hasSize) {
return;
}

final origin = renderObject.localToGlobal(Offset.zero);
final rect = origin & renderObject.size;
if (carouselRect.value != rect) {
carouselRect.value = rect;
}
}

useEffect(
() {
if (route == null) {
return null;
}

BackGestureExclusionRegistry.register(route, carouselRect);
return () => BackGestureExclusionRegistry.unregister(route, carouselRect);
},
[route, carouselRect],
);

useEffect(
() {
void listener() => updateCarouselRect();
scrollController.addListener(listener);
WidgetsBinding.instance.addPostFrameCallback((_) => updateCarouselRect());
return () => scrollController.removeListener(listener);
},
[scrollController],
);

useEffect(
() {
return carouselRect.dispose;
},
const [],
);

// Collapse header when search field is focused
useOnInit(
() {
Expand Down Expand Up @@ -200,6 +255,7 @@ class CreatorTokensPage extends HookConsumerWidget {
resetGlobalSearch();
}
},
carouselKey: carouselKey,
),
const SliverToBoxAdapter(
child: SectionSeparator(),
Expand Down
16 changes: 14 additions & 2 deletions lib/app/router/custom_routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/router/utils/back_gesture_exclusion.dart';

const double _kMinFlingVelocity = 1; // Screen widths per second.

Expand Down Expand Up @@ -238,6 +239,8 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
linearTransition: linearTransition,
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => route.popGestureEnabled,
shouldStartPopGesture: (event) =>
!BackGestureExclusionRegistry.isExcluded(route, event.position),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
Expand Down Expand Up @@ -687,13 +690,16 @@ class _CupertinoBackGestureDetector<T> extends StatefulWidget {
required this.enabledCallback,
required this.onStartPopGesture,
required this.child,
this.shouldStartPopGesture,
super.key,
});

final Widget child;

final ValueGetter<bool> enabledCallback;

final bool Function(PointerDownEvent event)? shouldStartPopGesture;

final ValueGetter<_CupertinoBackGestureController<T>> onStartPopGesture;

@override
Expand Down Expand Up @@ -761,9 +767,15 @@ class _CupertinoBackGestureDetectorState<T> extends State<_CupertinoBackGestureD
}

void _handlePointerDown(PointerDownEvent event) {
if (widget.enabledCallback()) {
_recognizer.addPointer(event);
if (!widget.enabledCallback()) {
return;
}

if (widget.shouldStartPopGesture != null && !widget.shouldStartPopGesture!(event)) {
return;
}

_recognizer.addPointer(event);
}

double _convertToLogical(double value) {
Expand Down
1 change: 1 addition & 0 deletions lib/app/router/profile_routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ class CreatorTokensRoute extends BaseRouteData with _$CreatorTokensRoute {
CreatorTokensRoute()
: super(
child: const CreatorTokensPage(),
canPop: true,
);
}

Expand Down
40 changes: 40 additions & 0 deletions lib/app/router/utils/back_gesture_exclusion.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

class BackGestureExclusionRegistry {
static final Map<Route<dynamic>, Set<ValueListenable<Rect?>>> _entries = {};

static void register(Route<dynamic> route, ValueListenable<Rect?> rectListenable) {
_entries.putIfAbsent(route, () => <ValueListenable<Rect?>>{}).add(rectListenable);
}

static void unregister(Route<dynamic> route, ValueListenable<Rect?> rectListenable) {
final entries = _entries[route];
if (entries == null) {
return;
}

entries.remove(rectListenable);
if (entries.isEmpty) {
_entries.remove(route);
}
}

static bool isExcluded(Route<dynamic> route, Offset globalPosition) {
final entries = _entries[route];
if (entries == null) {
return false;
}

for (final entry in entries) {
final rect = entry.value;
if (rect != null && rect.contains(globalPosition)) {
return true;
}
}

return false;
}
}