fix(app): say it out loud when wake-ups do not work

Registration failures from the distributor were swallowed, so push looked
enabled while nothing arrived. Without wake-ups Android suspends the app
and messages only show up once the user opens it — which looks like broken
delivery even though the relay has them queued.

The failure is now remembered and surfaced in Settings, and the chat list
carries a dismissible line explaining the state.
This commit is contained in:
Julius Joska
2026-08-13 12:23:30 +02:00
parent 9985b79aa6
commit 923a6a01bd
18 changed files with 305 additions and 10 deletions
@@ -5,6 +5,7 @@ import 'package:sepot/core/l10n/error_messages.dart';
import 'package:sepot/core/l10n/l10n.dart';
import 'package:sepot/core/theme/app_theme.dart';
import 'package:sepot/features/chat_list/chat_list_tile.dart';
import 'package:sepot/features/chat_list/push_hint_banner.dart';
import 'package:sepot/models/conversation.dart';
import 'package:sepot/providers/contacts_provider.dart';
import 'package:sepot/providers/conversations_provider.dart';
@@ -62,7 +63,13 @@ class ChatListScreen extends ConsumerWidget {
}
final hasArchive = archived.isNotEmpty;
return ListView.builder(
return Column(
children: [
// Bez budíčků appka na pozadí spí — ať uživatel ví, proč
// mu zprávy chodí, až když Šepot otevře.
const PushHintBanner(),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: active.length + (hasArchive ? 1 : 0),
itemBuilder: (context, index) {
@@ -95,7 +102,10 @@ class ChatListScreen extends ConsumerWidget {
onTap: () => context.push('/conversation/${conv.contactId}'),
onLongPress: () => _showChatActions(context, ref, conv, name),
);
},
},
),
),
],
);
},
loading: () => const Center(
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:sepot/core/l10n/l10n.dart';
import 'package:sepot/core/theme/app_theme.dart';
import 'package:sepot/providers/push_provider.dart';
import 'package:sepot/providers/storage_provider.dart';
/// Upozornění nad seznamem chatů, když budíčky nefungují.
///
/// Bez pushe Android appku na pozadí uspí a zprávy dorazí, až ji uživatel
/// sám otevře — vypadá to jako výpadek doručování, i když relay má všechno
/// připravené ve frontě. Proužek je proto informace o stavu, ne reklama:
/// zmizí, jakmile si push nastaví, a jde odmítnout natrvalo.
class PushHintBanner extends ConsumerWidget {
const PushHintBanner({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final health = ref.watch(pushHealthProvider).valueOrNull;
final dismissed = ref.watch(pushHintDismissedProvider).valueOrNull ?? true;
if (dismissed || health == null || health == PushHealth.ok) {
return const SizedBox.shrink();
}
final text = health == PushHealth.failed
? context.l10n.pushHintFailed
: context.l10n.pushHintOff;
return Material(
color: SepotColors.surfaceVariant,
child: InkWell(
onTap: () => context.push('/settings'),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 10, 8, 10),
child: Row(
children: [
Icon(
Icons.notifications_off_outlined,
size: 18,
color: health == PushHealth.failed
? SepotColors.ember
: SepotColors.textMuted,
),
const SizedBox(width: 12),
Expanded(
child: Text(
text,
style: const TextStyle(
fontFamily: kFontSans,
fontSize: 13,
color: SepotColors.textSecondary,
),
),
),
IconButton(
icon: const Icon(Icons.close, size: 18),
color: SepotColors.textMuted,
tooltip: context.l10n.commonClose,
onPressed: () async {
await ref
.read(storageProvider)
.setSetting('push_hint_dismissed', true);
ref.invalidate(pushHintDismissedProvider);
},
),
],
),
),
),
);
}
}