Update to use connect/disconnect, better lifecycle tuning, improve debug info UI

This commit is contained in:
franzap
2025-12-16 11:41:04 -03:00
parent d9196cb7d0
commit a8737163e2
2 changed files with 73 additions and 142 deletions
+12 -19
View File
@@ -97,12 +97,12 @@ class ZapstoreApp extends HookConsumerWidget {
// Check initial connectivity state
connectivity.checkConnectivity().then((results) {
notifier.ensureConnected();
notifier.connect();
});
// Listen to connectivity changes
subscription = connectivity.onConnectivityChanged.listen((results) {
notifier.ensureConnected();
notifier.connect();
});
return () => subscription?.cancel();
@@ -273,10 +273,7 @@ Future<void> onSignInSuccess(Ref ref) async {
unawaited(
storage.query(
RequestFilter<AppCatalogRelayList>(authors: {pubkey}).toRequest(),
source: const RemoteSource(
relays: 'bootstrap',
stream: false,
),
source: const RemoteSource(relays: 'bootstrap', stream: false),
),
);
}
@@ -290,14 +287,15 @@ class _AppLifecycleObserver with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
final downloadService = _ref.read(downloadServiceProvider.notifier);
final notifier =
_ref.read(storageNotifierProvider.notifier)
as PurplebaseStorageNotifier;
if (state == AppLifecycleState.resumed) {
// Notify download service that app is in foreground
// This will process any pending installations
// Process any pending installations
unawaited(downloadService.setAppForeground(true));
// App regained focus - re-sync installed packages from Android system
// This will detect any apps installed/uninstalled outside of Zapstore
// Re-sync installed packages from Android system
unawaited(
_ref.read(packageManagerProvider.notifier).syncInstalledPackages(),
);
@@ -306,16 +304,11 @@ class _AppLifecycleObserver with WidgetsBindingObserver {
// Force immediate health check on storage/relay connections
// This detects stale connections after system sleep and triggers reconnection
final notifier =
_ref.read(storageNotifierProvider.notifier)
as PurplebaseStorageNotifier;
notifier.ensureConnected();
} else if (state == AppLifecycleState.paused ||
state == AppLifecycleState.inactive ||
state == AppLifecycleState.detached ||
state == AppLifecycleState.hidden) {
// Notify download service that app is in background
notifier.connect();
} else if (state == AppLifecycleState.paused) {
// Note: paused corresponds to Android's `onStop`
unawaited(downloadService.setAppForeground(false));
notifier.disconnect();
}
}
}
+61 -123
View File
@@ -293,7 +293,6 @@ class _DebugMessagesSection extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final poolState = ref.watch(poolStateProvider);
final isExpanded = useState(true);
final selectedTab = useState(0); // 0=Subscriptions, 1=Logs
final now = useState(DateTime.now());
final expandedSubs = useState<Set<String>>({});
@@ -308,45 +307,6 @@ class _DebugMessagesSection extends HookConsumerWidget {
final subscriptions = poolState?.subscriptions ?? {};
final logs = poolState?.logs ?? const [];
// Aggregate relay status across all subscriptions
final allRelayUrls = <String>[];
final relayDebugEntries =
<
({
String url,
String shortUrl,
RelaySubPhase phase,
String? lastError,
String subscriptionId,
})
>[];
final connectedRelayUrls = <String>{};
for (final subEntry in subscriptions.entries) {
final subscriptionId = subEntry.key;
final sub = subEntry.value;
for (final entry in sub.relays.entries) {
final url = entry.key;
final relay = entry.value;
final phase = relay.phase;
final shortUrl = url
.replaceAll('wss://', '')
.replaceAll('ws://', '')
.replaceAll(RegExp(r'/$'), '');
allRelayUrls.add(url);
relayDebugEntries.add((
url: url,
shortUrl: shortUrl,
phase: phase,
lastError: relay.lastError,
subscriptionId: subscriptionId,
));
if (phase == RelaySubPhase.streaming ||
phase == RelaySubPhase.loading) {
connectedRelayUrls.add(url);
}
}
}
void toggleSubscription(String id) {
final next = {...expandedSubs.value};
if (!next.remove(id)) {
@@ -362,64 +322,53 @@ class _DebugMessagesSection extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
InkWell(
onTap: () => isExpanded.value = !isExpanded.value,
child: Row(
children: [
Icon(
Icons.bug_report,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'Debug Info',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
Row(
children: [
Icon(
Icons.bug_report,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'Debug Info',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 8),
Icon(
isExpanded.value ? Icons.expand_less : Icons.expand_more,
color: Theme.of(context).colorScheme.primary,
),
],
),
),
if (isExpanded.value) ...[
const SizedBox(height: 10),
// Tab selector
Row(
children: [
_TabButton(
label: 'Subscriptions (${subscriptions.length})',
isSelected: selectedTab.value == 0,
onTap: () => selectedTab.value = 0,
),
const SizedBox(width: 8),
_TabButton(
label: 'Log (${logs.length})',
isSelected: selectedTab.value == 1,
onTap: () => selectedTab.value = 1,
),
],
),
const SizedBox(height: 12),
// Tab content
if (selectedTab.value == 0)
_buildSubscriptionsTab(
context,
subscriptions,
now.value,
expandedSubs.value,
toggleSubscription,
),
if (selectedTab.value == 1) _buildLogsTab(context, logs),
],
],
),
const SizedBox(height: 10),
// Tab selector
Row(
children: [
_TabButton(
label: 'Subscriptions (${subscriptions.length})',
isSelected: selectedTab.value == 0,
onTap: () => selectedTab.value = 0,
),
const SizedBox(width: 8),
_TabButton(
label: 'Log (${logs.length})',
isSelected: selectedTab.value == 1,
onTap: () => selectedTab.value = 1,
),
],
),
const SizedBox(height: 12),
// Tab content
if (selectedTab.value == 0)
_buildSubscriptionsTab(
context,
subscriptions,
now.value,
expandedSubs.value,
toggleSubscription,
),
if (selectedTab.value == 1) _buildLogsTab(context, logs),
],
),
),
@@ -428,7 +377,7 @@ class _DebugMessagesSection extends HookConsumerWidget {
Widget _buildSubscriptionsTab(
BuildContext context,
Map<String, Subscription> subscriptions,
Map<String, RelaySubscription> subscriptions,
DateTime now,
Set<String> expandedSubs,
void Function(String id) onToggleSub,
@@ -481,7 +430,7 @@ class _DebugMessagesSection extends HookConsumerWidget {
size: 16,
color: allEose
? Colors.green
: Theme.of(context).colorScheme.primary,
: Colors.amber.shade700,
),
const SizedBox(width: 6),
Expanded(
@@ -495,6 +444,19 @@ class _DebugMessagesSection extends HookConsumerWidget {
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
_StatusChip(
icon: Icons.event,
label: '${sub.eventCount}',
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 6),
_StatusChip(
icon: Icons.cloud_done,
label: '$activeRelays/$totalRelays',
color: allEose ? Colors.green : Colors.amber.shade700,
),
const SizedBox(width: 6),
Icon(
isExpanded ? Icons.expand_less : Icons.expand_more,
size: 16,
@@ -504,30 +466,6 @@ class _DebugMessagesSection extends HookConsumerWidget {
),
],
),
const SizedBox(height: 6),
Wrap(
spacing: 8,
runSpacing: 6,
children: [
_StatusChip(
icon: Icons.cloud_done,
label: '$activeRelays/$totalRelays',
color: allEose ? Colors.green : Colors.blue,
),
if (allEose)
_StatusChip(
icon: Icons.check_circle,
label: 'EOSE',
color: Colors.green,
),
if (sub.stream)
_StatusChip(
icon: Icons.wifi_tethering,
label: 'Streaming',
color: Theme.of(context).colorScheme.primary,
),
],
),
if (relayEntries.isNotEmpty) ...[
const SizedBox(height: 10),
Container(
@@ -792,7 +730,7 @@ class _DebugMessagesSection extends HookConsumerWidget {
return hours > 0 ? '${days}d ${hours}h' : '${days}d';
}
Widget _buildRequestView(BuildContext context, Subscription sub) {
Widget _buildRequestView(BuildContext context, RelaySubscription sub) {
final req = _formatReq(sub);
return Container(
margin: const EdgeInsets.only(top: 10),
@@ -858,7 +796,7 @@ class _DebugMessagesSection extends HookConsumerWidget {
);
}
String _formatReq(Subscription sub) {
String _formatReq(RelaySubscription sub) {
final payload = ['REQ', sub.id, ...sub.request.toMaps()];
try {
return const JsonEncoder.withIndent(' ').convert(payload);