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:
@@ -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);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ class _PushSettingsTileState extends ConsumerState<PushSettingsTile> {
|
||||
String _mode = 'off';
|
||||
String? _distributor;
|
||||
bool _busy = false;
|
||||
bool _registrationFailed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -31,10 +32,12 @@ class _PushSettingsTileState extends ConsumerState<PushSettingsTile> {
|
||||
final push = ref.read(pushServiceProvider);
|
||||
final mode = await push.currentMode();
|
||||
final d = await push.currentDistributor();
|
||||
final failed = await push.registrationFailed();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_mode = mode;
|
||||
_distributor = d;
|
||||
_registrationFailed = failed;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -160,12 +163,17 @@ class _PushSettingsTileState extends ConsumerState<PushSettingsTile> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subtitle = switch (_mode) {
|
||||
'up' => context.l10n.pushSubtitleUnifiedPush(
|
||||
_distributor ?? context.l10n.pushNoDistributorFallback),
|
||||
'fcm' => context.l10n.pushSubtitleFcm,
|
||||
_ => context.l10n.commonOff,
|
||||
};
|
||||
// Selhaná registrace přebíjí popis režimu — „zapnuto" bez budíčků je
|
||||
// horší než vypnuto, protože uživatel čeká zprávy, které nepřijdou.
|
||||
final failed = _registrationFailed && _mode != 'off';
|
||||
final subtitle = failed
|
||||
? context.l10n.pushRegistrationFailed
|
||||
: switch (_mode) {
|
||||
'up' => context.l10n.pushSubtitleUnifiedPush(
|
||||
_distributor ?? context.l10n.pushNoDistributorFallback),
|
||||
'fcm' => context.l10n.pushSubtitleFcm,
|
||||
_ => context.l10n.commonOff,
|
||||
};
|
||||
return ListTile(
|
||||
leading: Container(
|
||||
width: 40,
|
||||
@@ -184,7 +192,10 @@ class _PushSettingsTileState extends ConsumerState<PushSettingsTile> {
|
||||
style: const TextStyle(fontSize: 15)),
|
||||
subtitle: Text(
|
||||
subtitle,
|
||||
style: const TextStyle(fontSize: 13, color: SepotColors.textMuted),
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: failed ? SepotColors.ember : SepotColors.textMuted,
|
||||
),
|
||||
),
|
||||
trailing: _busy
|
||||
? const SizedBox(
|
||||
|
||||
Reference in New Issue
Block a user