保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。 保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。 验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。 包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
254 lines
9.8 KiB
C#
254 lines
9.8 KiB
C#
using System.Collections.Generic;
|
|
using System;
|
|
using UnityEngine;
|
|
using Unity.Notifications;
|
|
using asap.core;
|
|
using GameCore;
|
|
using System.Linq;
|
|
using game;
|
|
using UniRx;
|
|
using Notification = Unity.Notifications.Notification;
|
|
using System.Threading.Tasks;
|
|
|
|
public interface ILocalNotificationService
|
|
{
|
|
public System.Threading.Tasks.Task Init();
|
|
public bool IsNoticeOpen { get; }
|
|
public void OpenSettings();
|
|
public void ScheduleOneTimeNotification(int notificationID, DateTime fireTime, string title, string text);
|
|
public void ScheduleDailyNotification(int notificationID, DateTime fireTime, string title, string text);
|
|
public void ClearAllNotification();
|
|
public void ClearNotification(int notificationID);
|
|
public void ScheduleDefaultNotification();
|
|
}
|
|
|
|
|
|
public class LocalNotificationService : ILocalNotificationService
|
|
{
|
|
private List<cfg.Notification> _notificationList;
|
|
|
|
#if UNITY_ANDROID
|
|
public bool IsNoticeOpen =>
|
|
Unity.Notifications.Android.AndroidNotificationCenter.UserPermissionToPost ==
|
|
Unity.Notifications.Android.PermissionStatus.Allowed;
|
|
#else
|
|
public bool IsNoticeOpen => Unity.Notifications.iOS.iOSNotificationCenter.GetNotificationSettings().AuthorizationStatus ==
|
|
Unity.Notifications.iOS.AuthorizationStatus.Authorized;
|
|
#endif
|
|
bool isInit = false;
|
|
bool initing = false;
|
|
DefaultSettingService promptSettings;
|
|
Task initialization;
|
|
Task opening;
|
|
internal bool HasKVPromptOwner => promptSettings != null;
|
|
public LocalNotificationService()
|
|
{
|
|
_notificationList = GContext.container.Resolve<cfg.Tables>().TbNotification.DataList;
|
|
}
|
|
|
|
internal LocalNotificationService(cfg.Tables tables)
|
|
{
|
|
_notificationList = (tables ?? throw new ArgumentNullException(nameof(tables))).TbNotification.DataList;
|
|
}
|
|
|
|
internal LocalNotificationService(DefaultSettingService settings, cfg.Tables tables)
|
|
{
|
|
if (settings == null || !settings.HasKVPromptOwner)
|
|
throw new ArgumentException("Fixed KV settings required.", nameof(settings));
|
|
promptSettings = settings;
|
|
_notificationList = (tables ?? throw new ArgumentNullException(nameof(tables))).TbNotification.DataList;
|
|
RequirePromptOwner();
|
|
}
|
|
|
|
internal void RequirePromptOwner(ISettingService expected = null)
|
|
{
|
|
if (expected != null && (promptSettings != null || expected is DefaultSettingService setting && setting.HasKVPromptOwner)
|
|
&& !ReferenceEquals(expected, promptSettings))
|
|
throw DefaultSettingService.PromptFailure("ShowNotice: notification/settings owner mismatch.");
|
|
promptSettings?.RequirePromptOwner();
|
|
}
|
|
|
|
internal void PreflightFormalPromptSettings(DefaultSettingService settings)
|
|
{
|
|
if (settings == null || !isInit || initing ||
|
|
(initialization != null && !initialization.IsCompleted) ||
|
|
(opening != null && !opening.IsCompleted) ||
|
|
(promptSettings != null && !ReferenceEquals(promptSettings, settings)))
|
|
throw DefaultSettingService.PromptFailure("ShowNotice: notification handoff is not idle.");
|
|
}
|
|
internal void BindFormalPromptSettings(DefaultSettingService settings)
|
|
{
|
|
PreflightFormalPromptSettings(settings);
|
|
if (!settings.HasKVPromptOwner)
|
|
throw DefaultSettingService.PromptFailure("ShowNotice: notification owner not handed off.");
|
|
settings.RequirePromptOwner();
|
|
if (promptSettings == null) promptSettings = settings;
|
|
RequirePromptOwner(settings);
|
|
}
|
|
internal bool HasFormalPromptSettings(DefaultSettingService settings) =>
|
|
ReferenceEquals(promptSettings, settings);
|
|
|
|
public Task Init()
|
|
{
|
|
RequirePromptOwner();
|
|
if (isInit) return Task.CompletedTask;
|
|
if (initialization != null && !initialization.IsCompleted) return initialization;
|
|
return initialization = InitCoreAsync();
|
|
}
|
|
|
|
async Task InitCoreAsync()
|
|
{
|
|
RequirePromptOwner();
|
|
#if UNITY_EDITOR
|
|
await System.Threading.Tasks.Task.Yield();
|
|
#else
|
|
Debug.Log("[LocalNotificationService::Init]");
|
|
|
|
var args = NotificationCenterArgs.Default;
|
|
args.AndroidChannelId = "default";
|
|
args.AndroidChannelName = "Notifications";
|
|
args.AndroidChannelDescription = "Main notifications";
|
|
NotificationCenter.Initialize(args);
|
|
|
|
Debug.Log("[LocalNotificationService::Init] Channel Inited");
|
|
|
|
await NotificationCenter.RequestPermission();
|
|
RequirePromptOwner();
|
|
ScheduleDefaultNotification();
|
|
GContext.OnEvent<EventRankData>().Subscribe(ScheduleRankNotification);
|
|
#endif
|
|
RequirePromptOwner();
|
|
isInit = true;
|
|
}
|
|
|
|
public void ClearAllNotification()
|
|
{
|
|
if (!isInit) return;
|
|
rankNotifyScheduled = false;
|
|
NotificationCenter.CancelAllScheduledNotifications();
|
|
NotificationCenter.CancelAllDeliveredNotifications();
|
|
}
|
|
|
|
public void ClearNotification(int notificationID)
|
|
{
|
|
if (!IsNoticeOpen || !isInit)
|
|
{
|
|
return;
|
|
}
|
|
|
|
NotificationCenter.CancelScheduledNotification(notificationID);
|
|
NotificationCenter.CancelDeliveredNotification(notificationID);
|
|
}
|
|
|
|
public async void OpenSettings()
|
|
{
|
|
try { await OpenSettingsAsync(); }
|
|
catch (Exception ex) { game.DefaultSettingService.LogPromptFailure(ex); }
|
|
}
|
|
|
|
internal Task OpenSettingsAsync()
|
|
{
|
|
if (promptSettings == null && GContext.container.Resolve<ISettingService>() is DefaultSettingService pending)
|
|
pending.RequirePromptOwner();
|
|
RequirePromptOwner();
|
|
if (initing && opening == null)
|
|
throw DefaultSettingService.PromptFailure("ShowNotice: reentrant permission request.");
|
|
if (opening != null && !opening.IsCompleted) return opening;
|
|
return opening = OpenSettingsCoreAsync();
|
|
}
|
|
|
|
async Task OpenSettingsCoreAsync()
|
|
{
|
|
ISettingService settings = promptSettings ?? GContext.container.Resolve<ISettingService>();
|
|
RequirePromptOwner(settings);
|
|
(settings as DefaultSettingService)?.RequirePromptOwner();
|
|
initing = true;
|
|
try
|
|
{
|
|
if (!isInit) await Init();
|
|
else NotificationCenter.OpenNotificationSettings();
|
|
RequirePromptOwner(settings);
|
|
(settings as DefaultSettingService)?.RequirePromptOwner();
|
|
settings.ShowNotice = true;
|
|
}
|
|
finally { initing = false; }
|
|
}
|
|
|
|
public void ScheduleDailyNotification(int notificationID, DateTime fireTime, string title, string text)
|
|
{
|
|
if (!IsNoticeOpen || !isInit)
|
|
{
|
|
Debug.Log("[LocalNotificationService::ScheduleDailyNotification] Not Open");
|
|
return;
|
|
}
|
|
|
|
NotificationCenter.CancelScheduledNotification(notificationID);
|
|
NotificationCenter.CancelDeliveredNotification(notificationID);
|
|
var notification = new Notification()
|
|
{
|
|
Title = title,
|
|
Text = text,
|
|
Identifier = notificationID
|
|
};
|
|
NotificationCenter.ScheduleNotification(notification,
|
|
new NotificationDateTimeSchedule(fireTime, NotificationRepeatInterval.Daily));
|
|
}
|
|
|
|
public void ScheduleOneTimeNotification(int notificationID, DateTime fireTime, string title, string text)
|
|
{
|
|
if (!IsNoticeOpen || !isInit) return;
|
|
NotificationCenter.CancelScheduledNotification(notificationID);
|
|
NotificationCenter.CancelDeliveredNotification(notificationID);
|
|
var notification = new Notification()
|
|
{
|
|
Title = title,
|
|
Text = text,
|
|
Identifier = notificationID
|
|
};
|
|
NotificationCenter.ScheduleNotification(notification,
|
|
new NotificationDateTimeSchedule(fireTime, NotificationRepeatInterval.OneTime));
|
|
}
|
|
|
|
public void ScheduleDefaultNotification()
|
|
{
|
|
if (!IsNoticeOpen || !isInit) return;
|
|
Debug.Log("[LocalNotificationService::SendNotification] Daily");
|
|
var dailyNotification = _notificationList.Where(x => x.Type == 0);
|
|
foreach (var item in dailyNotification)
|
|
{
|
|
var dateTime = DateTime.SpecifyKind(DateTime.Parse(item.NotTime), DateTimeKind.Utc);
|
|
ScheduleDailyNotification(item.ID, dateTime.ToLocalTime(), LocalizationMgr.GetText(item.Title_l10n_key),
|
|
LocalizationMgr.GetText(item.Content_l10n_key));
|
|
}
|
|
|
|
Debug.Log("[LocalNotificationService::SendNotification] Daily Done");
|
|
}
|
|
|
|
private bool rankNotifyScheduled = false;
|
|
|
|
private void ScheduleRankNotification(EventRankData evt)
|
|
{
|
|
try { RequirePromptOwner(); }
|
|
catch (InvalidOperationException ex) { Debug.LogError(ex.Message); return; }
|
|
if (rankNotifyScheduled || !IsNoticeOpen || !isInit) return;
|
|
|
|
Debug.Log("[LocalNotificationService::SendNotification] RankEvent");
|
|
|
|
var notification = _notificationList.FirstOrDefault(x => x.Type == 1);
|
|
if (notification == null) return;
|
|
|
|
// var fireTime = ConvertTools.GetDateTimeYMD(ZZTimeHelper.UtcNow().UtcNowOffset());
|
|
// var dayTime = DateTime.Parse(notification.NotTime);
|
|
// fireTime = fireTime.AddDays(1).AddHours(dayTime.Hour).AddMinutes(dayTime.Minute).AddSeconds(dayTime.Second);
|
|
var now = ZZTimeHelper.UtcNow();
|
|
var fireTime = DateTime.SpecifyKind(DateTime.Parse(notification.NotTime), DateTimeKind.Utc);
|
|
fireTime = fireTime.AddDays(now <= fireTime ? 0 : 1);
|
|
Debug.Log($"<color=#00d6b9>Try schedule Rank Notification at {fireTime}.</color>");
|
|
ScheduleOneTimeNotification(notification.ID, fireTime.ToLocalTime(),
|
|
LocalizationMgr.GetText(notification.Title_l10n_key),
|
|
LocalizationMgr.GetText(notification.Content_l10n_key));
|
|
rankNotifyScheduled = true;
|
|
Debug.Log("[LocalNotificationService::SendRankEventNotification] ");
|
|
}
|
|
}
|