178 lines
5.9 KiB
Dart
178 lines
5.9 KiB
Dart
// lib/views/notification_time/notification_time_state.dart
|
|
//
|
|
// State for the per-user push-notification schedule preference.
|
|
//
|
|
// Backend: `GET /notification-preferences` and `PATCH /notification-preferences`
|
|
// on api2.didvan.com. The server stores:
|
|
// { enabled, deliverAtHour, deliverAtMinute, timezone }
|
|
// and, when enabled=true, delays content pushes until the next occurrence of
|
|
// deliverAtHour:deliverAtMinute in the user's timezone. When enabled=false
|
|
// (default), pushes fire at the admin-picked time on the content, immediately.
|
|
//
|
|
// UI contract:
|
|
// - selectedTime: current chosen time (backed by deliverAtHour/deliverAtMinute).
|
|
// - enabled: on/off switch. Off means "use admin's time".
|
|
// - When enabled=false the time picker is disabled in the UI.
|
|
// - Saving pushes {enabled, deliverAtHour, deliverAtMinute} — timezone is
|
|
// left server-side (defaults to Asia/Tehran).
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:didvan/models/enums.dart';
|
|
import 'package:didvan/providers/core.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../../models/day_time.dart';
|
|
import '../../models/view/alert_data.dart';
|
|
import '../../services/network/request.dart';
|
|
import '../../services/network/request_helper.dart';
|
|
import '../../utils/action_sheet.dart';
|
|
import '../../utils/date_time.dart';
|
|
|
|
const _timeout = Duration(seconds: 10);
|
|
|
|
class NotificationTimeState extends CoreProvier {
|
|
/// On/off switch. When false (default), notifications fire at the admin's
|
|
/// picked time; when true, they wait for `selectedTime` in the user's TZ.
|
|
bool enabled = false;
|
|
|
|
/// The time the user wants pushes to arrive when `enabled == true`.
|
|
/// Default 21:00 matches the backend's own default for a fresh preference.
|
|
DayTime selectedTime = DateTimeUtils.handleDayTime('21:00');
|
|
|
|
bool fromFav = false;
|
|
|
|
late void Function() onTimeChanged = () {};
|
|
|
|
/// Legacy field kept for the widgets that still read it. Semantically it
|
|
/// now mirrors `!enabled` — "anytime" (دریافت آنی) means the user did NOT
|
|
/// pick a custom time, so pushes fire at the admin's time. When `true`,
|
|
/// `enabled` on the new API is `false`, and vice versa.
|
|
bool get isAnytime => !enabled;
|
|
set isAnytime(bool value) {
|
|
enabled = !value;
|
|
}
|
|
|
|
Future<void> getTime() async {
|
|
appState = AppState.busy;
|
|
|
|
try {
|
|
final service = RequestService(RequestHelper.notificationPreference());
|
|
await service.httpGet().timeout(_timeout);
|
|
if (service.isSuccess) {
|
|
final int hour = _asInt(service.data('deliverAtHour'), 21);
|
|
final int minute = _asInt(service.data('deliverAtMinute'), 0);
|
|
enabled = _asBool(service.data('enabled'), false);
|
|
selectedTime = _dayTimeFromHM(hour, minute);
|
|
} else {
|
|
debugPrint('⚠️ getPreference: status=${service.statusCode}');
|
|
_loadDefault();
|
|
}
|
|
} on TimeoutException {
|
|
debugPrint('⏱️ getPreference timeout');
|
|
_loadDefault();
|
|
} catch (e) {
|
|
debugPrint('❌ getPreference failed: $e');
|
|
_loadDefault();
|
|
}
|
|
|
|
appState = AppState.idle;
|
|
update();
|
|
}
|
|
|
|
void _loadDefault() {
|
|
enabled = false;
|
|
selectedTime = DateTimeUtils.handleDayTime('21:00');
|
|
}
|
|
|
|
/// Convert DayTime (hour string + AM/PM) → 24-hour int.
|
|
int _selectedHour24() {
|
|
int hour24 = int.tryParse(selectedTime.hour) ?? 12;
|
|
if (selectedTime.meridiem == Meridiem.PM && hour24 != 12) hour24 += 12;
|
|
if (selectedTime.meridiem == Meridiem.AM && hour24 == 12) hour24 = 0;
|
|
return hour24;
|
|
}
|
|
|
|
int _selectedMinute() => int.tryParse(selectedTime.minute) ?? 0;
|
|
|
|
DayTime _dayTimeFromHM(int hour24, int minute) {
|
|
final meridiem = hour24 >= 12 ? Meridiem.PM : Meridiem.AM;
|
|
int display = hour24 % 12;
|
|
if (display == 0) display = 12;
|
|
return DayTime(
|
|
hour: display.toString(),
|
|
minute: minute.toString().padLeft(2, '0'),
|
|
meridiem: meridiem,
|
|
);
|
|
}
|
|
|
|
Future<void> putTime(BuildContext context) async {
|
|
appState = AppState.busy;
|
|
|
|
final body = {
|
|
'enabled': enabled,
|
|
'deliverAtHour': _selectedHour24(),
|
|
'deliverAtMinute': _selectedMinute(),
|
|
};
|
|
final service = RequestService(
|
|
RequestHelper.notificationPreference(),
|
|
body: body,
|
|
);
|
|
try {
|
|
// The generic RequestService exposes `.put()` for updates; for a PATCH
|
|
// semantic the server accepts either — the endpoint is idempotent on
|
|
// the (enabled, hour, minute) triple.
|
|
await service.put().timeout(_timeout);
|
|
if (service.isSuccess) {
|
|
onTimeChanged();
|
|
Future.delayed(Duration.zero, () {
|
|
if (fromFav) {
|
|
Navigator.of(context).pop();
|
|
Navigator.of(context).pop();
|
|
} else {
|
|
Navigator.of(context).pop();
|
|
}
|
|
});
|
|
Future.delayed(
|
|
Duration.zero,
|
|
() => ActionSheetUtils(context).showAlert(AlertData(
|
|
message: 'تغییرات شما با موفقیت اعمال شد.',
|
|
aLertType: ALertType.success,
|
|
)),
|
|
);
|
|
appState = AppState.idle;
|
|
return;
|
|
}
|
|
ActionSheetUtils(context).showAlert(AlertData(
|
|
message: service.errorMessage,
|
|
));
|
|
} catch (e) {
|
|
debugPrint('❌ putPreference failed: $e');
|
|
ActionSheetUtils(context).showAlert(AlertData(
|
|
message: 'ذخیرهی تنظیمات اعلان با خطا مواجه شد.',
|
|
));
|
|
}
|
|
|
|
appState = AppState.failed;
|
|
}
|
|
|
|
static int _asInt(dynamic v, int fallback) {
|
|
if (v is int) return v;
|
|
if (v is num) return v.toInt();
|
|
if (v is String) return int.tryParse(v) ?? fallback;
|
|
return fallback;
|
|
}
|
|
|
|
static bool _asBool(dynamic v, bool fallback) {
|
|
if (v is bool) return v;
|
|
if (v is String) {
|
|
final s = v.toLowerCase();
|
|
if (s == 'true' || s == '1') return true;
|
|
if (s == 'false' || s == '0') return false;
|
|
}
|
|
if (v is num) return v != 0;
|
|
return fallback;
|
|
}
|
|
}
|