Files
Qlyra/lib/frontend/screens/profile/notifications_screen.dart
T

307 lines
10 KiB
Dart

import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../core/push/fkm_bridge.dart';
import '../../../core/push/fkm_controller.dart';
import '../../../core/utils/haptics.dart';
import '../../../l10n/app_localizations.dart';
import '../../../main.dart' show accountModule, isOnemeFlavor;
import '../../widgets/confirm_dialog.dart';
import '../../widgets/connection_status.dart';
import '../../widgets/reload_on_reconnect.dart';
import '../../widgets/custom_notification.dart';
import '../../widgets/section_header.dart';
import '../../widgets/settings_card.dart';
import '../../widgets/small_spinner.dart';
import 'web_push_screen.dart';
class NotificationsScreen extends StatefulWidget {
const NotificationsScreen({super.key});
@override
State<NotificationsScreen> createState() => _NotificationsScreenState();
}
class _NotificationsScreenState extends State<NotificationsScreen>
with ReloadOnReconnect {
bool _loading = true;
bool _saving = false;
bool _fkmBusy = false;
bool _allNotifications = true;
bool _messagePreview = true;
bool _sound = true;
bool _callNotifications = true;
bool _newContacts = false;
bool _hapticsEnabled = Haptics.enabled;
@override
void initState() {
super.initState();
_load();
}
@override
void reloadAfterReconnect() => _load();
Future<void> _load() async {
final config = await accountModule.getPrivacyConfig();
if (!mounted) return;
setState(() {
_allNotifications = config.chatsPushNotification == 'ON';
_messagePreview = config.pushDetails;
_sound = config.pushSound.isNotEmpty || config.chatsPushSound.isNotEmpty;
_callNotifications = config.mCallPushNotification == 'ON';
_newContacts = config.pushNewContacts;
_loading = false;
});
}
Future<void> _apply(
bool value,
Future<void> Function() action,
ValueChanged<bool> assign,
) async {
if (_saving) return;
setState(() {
assign(value);
_saving = true;
});
try {
await action();
} catch (e) {
if (mounted) {
setState(() => assign(!value));
showCustomNotification(
context,
AppLocalizations.of(context)!.notificationsSaveFailed(e.toString()),
);
}
} finally {
if (mounted) setState(() => _saving = false);
}
}
Future<void> _setHaptics(bool value) async {
await Haptics.setEnabled(value);
if (value) Haptics.success();
if (mounted) setState(() => _hapticsEnabled = value);
}
void _openWebPush() {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const WebPushScreen()),
);
}
Future<void> _onFkmChanged(bool value) async {
final l10n = AppLocalizations.of(context)!;
if (!FkmController.instance.isSupported) {
showCustomNotification(
context,
Platform.isIOS
? l10n.notificationsFkmIosUnsupported
: l10n.notificationsFkmUnsupported,
);
return;
}
if (_fkmBusy) return;
if (value && isOnemeFlavor) {
final confirmed = await showConfirmDialog(
context,
title: l10n.notificationsFkmAlreadyHasFcm,
message: l10n.notificationsFkmConfirmMessage,
confirmLabel: l10n.notificationsFkmConfirmAction,
);
if (!confirmed) return;
}
setState(() => _fkmBusy = true);
try {
final applied = await FkmController.instance.setEnabled(value);
if (!mounted) return;
if (!applied) {
showCustomNotification(context, l10n.notificationsFkmPermissionDenied);
return;
}
if (value) await _offerBatteryExemption();
} finally {
if (mounted) setState(() => _fkmBusy = false);
}
}
Future<void> _offerBatteryExemption() async {
if (await FkmBridge.instance.isIgnoringBatteryOptimizations()) return;
if (!mounted) return;
final l10n = AppLocalizations.of(context)!;
final confirmed = await showConfirmDialog(
context,
title: l10n.notificationsFkmBatteryTitle,
message: l10n.notificationsFkmBatteryMessage,
confirmLabel: l10n.notificationsFkmBatteryAction,
);
if (!confirmed) return;
await FkmBridge.instance.requestIgnoreBatteryOptimizations();
}
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final l10n = AppLocalizations.of(context)!;
return Scaffold(
backgroundColor: cs.surface,
appBar: ConnectionTitleBar(
titleText: l10n.notificationsTitle,
backgroundColor: cs.surface,
),
body: SafeArea(
top: false,
child: _loading
? const Center(child: SmallSpinner(size: 36))
: ListView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 120),
children: [
if (Platform.isIOS) ...[
SettingsCard(
children: [
SettingsNavTile(
icon: Symbols.install_mobile,
label: l10n.webPushTitle,
onTap: _openWebPush,
isLast: true,
),
],
),
const SizedBox(height: 20),
],
SectionHeader(
l10n.notificationsFkmSectionTitle,
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
fontSize: 14,
),
SettingsCard(
children: [
ValueListenableBuilder<bool>(
valueListenable: FkmController.instance.enabled,
builder: (context, fkmEnabled, _) => SettingsToggleTile(
icon: Symbols.notifications_active,
label: l10n.notificationsFkmEnableLabel,
subtitle: l10n.notificationsFkmEnableSubtitle,
value: fkmEnabled,
enabled: !_fkmBusy,
onChanged: _onFkmChanged,
),
),
],
),
const SizedBox(height: 20),
SectionHeader(
l10n.notificationsMainSectionTitle,
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
fontSize: 14,
),
SettingsCard(
children: [
SettingsToggleTile(
icon: Symbols.notifications,
label: l10n.notificationsAllLabel,
value: _allNotifications,
onChanged: (v) => _apply(
v,
() => accountModule.setChatsPushNotification(v),
(b) => _allNotifications = b,
),
),
],
),
const SizedBox(height: 20),
SectionHeader(
l10n.notificationsNewSectionTitle,
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
fontSize: 14,
),
SettingsCard(
children: [
SettingsToggleTile(
icon: Symbols.chat,
label: l10n.notificationsPreviewLabel,
value: _messagePreview,
enabled: _allNotifications,
onChanged: (v) => _apply(
v,
() => accountModule.setMessagePreview(v),
(b) => _messagePreview = b,
),
),
SettingsToggleTile(
icon: Symbols.music_note,
label: l10n.notificationsSoundLabel,
value: _sound,
enabled: _allNotifications,
onChanged: (v) => _apply(
v,
() => accountModule.setNotificationSound(v),
(b) => _sound = b,
),
),
],
),
const SizedBox(height: 20),
SectionHeader(
l10n.notificationsAdditionalSectionTitle,
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
fontSize: 14,
),
SettingsCard(
children: [
SettingsToggleTile(
icon: Symbols.call,
label: l10n.notificationsCallsLabel,
value: _callNotifications,
onChanged: (v) => _apply(
v,
() => accountModule.setCallNotifications(v),
(b) => _callNotifications = b,
),
),
SettingsToggleTile(
icon: Symbols.person_add,
label: l10n.notificationsNewContactsLabel,
value: _newContacts,
onChanged: (v) => _apply(
v,
() => accountModule.setNewContacts(v),
(b) => _newContacts = b,
),
),
],
),
const SizedBox(height: 20),
SectionHeader(
l10n.notificationsHapticsSectionTitle,
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
fontSize: 14,
),
SettingsCard(
children: [
SettingsToggleTile(
icon: Symbols.vibration,
label: l10n.notificationsHapticsLabel,
subtitle: l10n.notificationsHapticsSubtitle,
value: _hapticsEnabled,
onChanged: _setHaptics,
),
],
),
],
),
),
);
}
}