update: 新增 Notification 服务和对一个的 noti 查询功能
Signed-off-by: huyufei <yufei.hu@castbox.fm>main
							parent
							
								
									9024b8171c
								
							
						
					
					
						commit
						4e24169b25
					
				|  | @ -17,7 +17,9 @@ | |||
|         "Google.Play.Review", | ||||
|         "Google.Play.Common", | ||||
|         "Guru.LitJson", | ||||
|         "Unity.Advertisement.IosSupport" | ||||
|         "Unity.Advertisement.IosSupport", | ||||
|         "Unity.Notifications.Android", | ||||
|         "Unity.Notifications.iOS" | ||||
|     ], | ||||
|     "includePlatforms": [], | ||||
|     "excludePlatforms": [], | ||||
|  |  | |||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 1fef034d48ac449fb99531b40139954e | ||||
| timeCreated: 1718844748 | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 8c0dd2d4b63445c2828e05c10274d672 | ||||
| timeCreated: 1718845536 | ||||
|  | @ -0,0 +1,6 @@ | |||
| { | ||||
|   "name": "GuruNotification.Editor", | ||||
|   "includePlatforms": [ | ||||
|     "Editor" | ||||
|   ] | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: e75025463d0c436482bf4c3dab674315 | ||||
| timeCreated: 1718845553 | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: d6dd827f370441718ca2e49f3f603e4e | ||||
| timeCreated: 1718845525 | ||||
|  | @ -0,0 +1,15 @@ | |||
| namespace Guru.Notification | ||||
| { | ||||
|     using System; | ||||
|     public interface INotificationAgent | ||||
|     { | ||||
|         void Init(); | ||||
| 
 | ||||
|         string GetStatus(); | ||||
| 
 | ||||
|         bool IsAllowed(); | ||||
| 
 | ||||
|         void RequestPermission(Action<string> callback = null); | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 43e051a65e9b469b8b9e8a9f6b4be944 | ||||
| timeCreated: 1718844775 | ||||
|  | @ -0,0 +1,247 @@ | |||
| namespace Guru.Notification | ||||
| { | ||||
|     using System; | ||||
|     using UnityEngine; | ||||
| #if UNITY_ANDROID | ||||
|     using UnityEngine.Android; | ||||
|     using Unity.Notifications.Android; | ||||
| #endif | ||||
|      | ||||
|     public class NotificationAgentAndroid : INotificationAgent | ||||
|     { | ||||
|         public const string FCM_DEFAULT_CHANNEL_ID = "fcm_default_channel"; | ||||
|         private const string STATUS_GRANTED = "granted"; | ||||
|         private const string STATUS_DENIDED = "denied"; | ||||
|         private const int REQUEST_PERMISSION_SDK_VERSION = 33; | ||||
|         private const string PERMISSION_POST_NOTIFICATION = "android.permission.POST_NOTIFICATIONS"; | ||||
|          | ||||
|         private bool _initOnce = false; | ||||
|         private string _notiStatus; | ||||
|         private bool _isPluginReady = false; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 初始化 | ||||
|         /// </summary> | ||||
|         public void Init() | ||||
|         { | ||||
|             if (!_initOnce) return; | ||||
| 
 | ||||
|             SetGrantStatus(false); | ||||
| #if UNITY_ANDROID | ||||
|             InitPlugins(); | ||||
| #endif | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 获取状态 | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         public string GetStatus() | ||||
|         { | ||||
|             if (!_initOnce) Init(); | ||||
| #if UNITY_ANDROID | ||||
|             UpdateNotiStatus(); | ||||
| #endif | ||||
|             return _notiStatus; | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 设置授权状态 | ||||
|         /// </summary> | ||||
|         /// <param name="flag"></param> | ||||
|         private void SetGrantStatus(bool flag = false) | ||||
|         { | ||||
|             _notiStatus = flag ? STATUS_GRANTED : STATUS_DENIDED; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         public bool IsAllowed() | ||||
|         { | ||||
|             return _notiStatus == STATUS_GRANTED; | ||||
|         } | ||||
| 
 | ||||
|         public void RequestPermission(Action<string> callback = null) | ||||
|         { | ||||
| #if UNITY_ANDROID | ||||
|             RequestAndroidPermission(callback); | ||||
| #endif | ||||
|         } | ||||
| 
 | ||||
|         // -------------------- Android 获取状态逻辑 -------------------- | ||||
|          | ||||
| #if UNITY_ANDROID | ||||
| 
 | ||||
|         private PermissionStatus _permissionStatus; | ||||
|          | ||||
|         private void TryExecute(Action handler) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 handler?.Invoke(); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 Debug.LogError(ex); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 初始化插件 | ||||
|         /// </summary> | ||||
|         private void InitPlugins() | ||||
|         { | ||||
|             _isPluginReady = AndroidNotificationCenter.Initialize(); | ||||
| 
 | ||||
|             if (!_isPluginReady) | ||||
|             { | ||||
|                 Debug.LogError($"[Noti][AND] --- AndroidNotificationCenter init failed!"); | ||||
|                 // TODO: 处理初始化失败的情况 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             UpdateNotiStatus(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 更新 Notification 状态码 | ||||
|         /// </summary> | ||||
|         private void UpdateNotiStatus() | ||||
|         { | ||||
|             if (!_isPluginReady) return; | ||||
|              | ||||
|             TryExecute(() => | ||||
|             { | ||||
|                 _permissionStatus = AndroidNotificationCenter.UserPermissionToPost; | ||||
|                 switch (_permissionStatus) | ||||
|                 { | ||||
|                     case PermissionStatus.Allowed: | ||||
|                         SetGrantStatus(true); | ||||
|                         break; | ||||
|                     default: | ||||
|                         _notiStatus = STATUS_DENIDED; | ||||
|                         break; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         private Action<string> _onPermissionCallback; | ||||
|         private PermissionCallbacks _permissionCallbacks; | ||||
|         private void RequestAndroidPermission(Action<string> callback = null) | ||||
|         { | ||||
|             if (!_isPluginReady) return; | ||||
|              | ||||
|             _onPermissionCallback = callback; | ||||
|             UpdateNotiStatus(); | ||||
| 
 | ||||
|             TryExecute(() => | ||||
|             { | ||||
|                 bool hasPerm = Permission.HasUserAuthorizedPermission(PERMISSION_POST_NOTIFICATION); | ||||
|                 var sdkInt = GetAndroidSDKVersion(); | ||||
|                 if (sdkInt < REQUEST_PERMISSION_SDK_VERSION) | ||||
|                 { | ||||
|                     // 低版本处理方式 | ||||
|                     if (_notiStatus == STATUS_GRANTED) | ||||
|                     { | ||||
|                         SetGrantStatus(true); | ||||
|                         // 已经允许了 | ||||
|                         callback?.Invoke(_notiStatus); | ||||
|                          | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         if (_permissionStatus == PermissionStatus.NotRequested || | ||||
|                             AndroidNotificationCenter.ShouldShowPermissionToPostRationale) | ||||
|                         { | ||||
|                             AndroidNotificationCenter.OpenNotificationSettings(FCM_DEFAULT_CHANNEL_ID); // 打开ChannelID | ||||
|                         } | ||||
|                     } | ||||
|                     | ||||
|                 } | ||||
|                 else if (hasPerm) | ||||
|                 { | ||||
|                     SetGrantStatus(true); | ||||
|                     // 已经允许了 | ||||
|                     callback?.Invoke(_notiStatus); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     // 未允许 | ||||
|                     // 则请求弹窗 | ||||
|                     Permission.RequestUserPermission(PERMISSION_POST_NOTIFICATION, SetupPermissionCallbacks()); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|          | ||||
|         private PermissionCallbacks SetupPermissionCallbacks() | ||||
|         { | ||||
|             if(_permissionCallbacks != null) DisposePermissionCallbacks(); | ||||
|             _permissionCallbacks = new PermissionCallbacks(); | ||||
|             _permissionCallbacks.PermissionDenied += OnPermissionGranted; | ||||
|             _permissionCallbacks.PermissionDeniedAndDontAskAgain += OnPermissionGranted; | ||||
|             _permissionCallbacks.PermissionGranted += OnPermissionGranted; | ||||
|             return _permissionCallbacks; | ||||
|         } | ||||
|          | ||||
|         private void DisposePermissionCallbacks() | ||||
|         { | ||||
|             if (_permissionCallbacks != null) | ||||
|             { | ||||
|                 _permissionCallbacks.PermissionGranted -= OnPermissionGranted; | ||||
|                 _permissionCallbacks.PermissionDenied -= PermissionDenied; | ||||
|                 _permissionCallbacks.PermissionDeniedAndDontAskAgain -= PermissionDenied; | ||||
|                 _permissionCallbacks = null; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 请求通过 | ||||
|         /// </summary> | ||||
|         /// <param name="permissionName"></param> | ||||
|         private void OnPermissionGranted(string permissionName) | ||||
|         { | ||||
|             if (permissionName == PERMISSION_POST_NOTIFICATION) | ||||
|             { | ||||
|                 _notiStatus = STATUS_GRANTED; | ||||
|                 _onPermissionCallback?.Invoke(_notiStatus); | ||||
|             } | ||||
| 
 | ||||
|             DisposePermissionCallbacks(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 请求拒绝 | ||||
|         /// </summary> | ||||
|         /// <param name="permissionName"></param> | ||||
|         private void PermissionDenied(string permissionName) | ||||
|         { | ||||
|             if (permissionName == PERMISSION_POST_NOTIFICATION) | ||||
|             { | ||||
|                 _notiStatus = STATUS_DENIDED; | ||||
|                 _onPermissionCallback?.Invoke(_notiStatus); | ||||
|             } | ||||
| 
 | ||||
|             DisposePermissionCallbacks(); | ||||
|         } | ||||
|          | ||||
|         private int GetAndroidSDKVersion() | ||||
|         { | ||||
|             int sdkInt = 999; | ||||
|             TryExecute(() => | ||||
|             { | ||||
|                 using (AndroidJavaClass jc = new AndroidJavaClass("android.os.Build$VERSION")) | ||||
|                 { | ||||
|                     sdkInt = jc.GetStatic<int>("SDK_INT"); | ||||
|                     Debug.LogWarning($"[SDK] --- Android SDK Version:{sdkInt}"); | ||||
|                 }  | ||||
|             }); | ||||
|             return sdkInt; | ||||
|         } | ||||
| 
 | ||||
|          | ||||
| #endif | ||||
|          | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 554fcea56ce74a80a2424e5c037ce6c0 | ||||
| timeCreated: 1718844764 | ||||
|  | @ -0,0 +1,129 @@ | |||
| 
 | ||||
| 
 | ||||
| namespace Guru.Notification | ||||
| { | ||||
|     using System; | ||||
|     using UnityEngine; | ||||
| #if UNITY_IOS | ||||
|     using System.Threading.Tasks; | ||||
|     using Unity.Notifications.iOS; | ||||
| #endif | ||||
| 
 | ||||
|     public class NotificationAgentIOS : INotificationAgent | ||||
|     { | ||||
| 
 | ||||
|         private const string STATUS_GRANTED = "granted"; | ||||
|         private const string STATUS_DENIDED = "denied"; | ||||
|         private const string STATUS_PROVISIONAL = "provisional"; | ||||
|         private const string STATUS_NOT_DETERMINED = "not_determined"; | ||||
| 
 | ||||
|         private static bool _initOnce; | ||||
|         private static int _waitSeconds = 30; | ||||
|          | ||||
| 
 | ||||
|         private string _notiStatus; | ||||
|          | ||||
|         public void Init() | ||||
|         { | ||||
|             if (_initOnce) return; | ||||
|             _initOnce = true; | ||||
|              | ||||
|             _notiStatus = STATUS_NOT_DETERMINED; | ||||
| #if UNITY_IOS | ||||
|             InitPlugins(); | ||||
| #endif | ||||
|         } | ||||
| 
 | ||||
|         public string GetStatus() | ||||
|         { | ||||
|             if (!_initOnce) Init(); | ||||
| #if UNITY_IOS | ||||
|             UpdateStatus(); | ||||
| #endif | ||||
|             return _notiStatus; | ||||
|         } | ||||
| 
 | ||||
|         public bool IsAllowed() | ||||
|         { | ||||
|             return _notiStatus == STATUS_GRANTED; | ||||
|         } | ||||
| 
 | ||||
|         public void RequestPermission(Action<string> callback = null) | ||||
|         { | ||||
|             if (!_initOnce) Init(); | ||||
| #if UNITY_IOS | ||||
|             RequestIOSPermission(callback); | ||||
| #endif | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| #if UNITY_IOS | ||||
| 
 | ||||
|         private void InitPlugins() | ||||
|         { | ||||
|             UpdateStatus(); | ||||
|              | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// 更新状态 | ||||
|         /// </summary> | ||||
|         private void UpdateStatus() | ||||
|         { | ||||
|             var status = iOSNotificationCenter.GetNotificationSettings().AuthorizationStatus; | ||||
|             switch (status) | ||||
|             { | ||||
|                 case AuthorizationStatus.Authorized: | ||||
|                     _notiStatus = STATUS_GRANTED; | ||||
|                     break; | ||||
|                 case AuthorizationStatus.Denied: | ||||
|                     _notiStatus = STATUS_DENIDED; | ||||
|                     break; | ||||
|                 case AuthorizationStatus.NotDetermined: | ||||
|                     _notiStatus = STATUS_NOT_DETERMINED; | ||||
|                     break; | ||||
|                 case AuthorizationStatus.Provisional: | ||||
|                     _notiStatus = STATUS_PROVISIONAL; | ||||
|                     break; | ||||
|                 default: | ||||
|                     Debug.Log($"[SDK][Noti][iOS] --- Unmarked AuthorizationStatus: {status}"); | ||||
|                     break; | ||||
|             } | ||||
|              | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 请求 IOS 的推送 | ||||
|         /// </summary> | ||||
|         /// <param name="callback"></param> | ||||
|         private async void RequestIOSPermission(Action<string> callback = null) | ||||
|         { | ||||
|             Debug.Log($"[SDK][Noti][iOS] --- RequestIOSPermission start"); | ||||
|             int timePassed = 0; | ||||
|             using (var req = new AuthorizationRequest(AuthorizationOption.Alert | AuthorizationOption.Badge, true)) | ||||
|             { | ||||
|                 while (!req.IsFinished && timePassed < _waitSeconds) | ||||
|                 { | ||||
|                     timePassed++; | ||||
|                     await Task.Delay(1000); | ||||
|                 }; | ||||
| 
 | ||||
|                 if (timePassed >= _waitSeconds) | ||||
|                 { | ||||
|                     Debug.LogWarning($"[SDK][Noti][iOS] --- RequestIOSPermission timeout"); | ||||
|                 } | ||||
| 
 | ||||
|                 UpdateStatus(); | ||||
|                 callback?.Invoke(_notiStatus); | ||||
|                 Debug.Log($"[SDK][Noti][iOS] --- User Selected: {_notiStatus}"); | ||||
|             } | ||||
|         } | ||||
|          | ||||
| #endif | ||||
|          | ||||
|          | ||||
|          | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 5068c6e238e74251955320761c96182b | ||||
| timeCreated: 1718871877 | ||||
|  | @ -0,0 +1,58 @@ | |||
| 
 | ||||
| 
 | ||||
| namespace Guru.Notification | ||||
| { | ||||
|      | ||||
|     using System; | ||||
|     using System.Threading.Tasks; | ||||
|     using UnityEngine; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// For Editor to use Notifications | ||||
|     /// </summary> | ||||
|     public class NotificationAgentStub: INotificationAgent | ||||
|     { | ||||
|         private const string STATUS_GRANTED = "granted"; | ||||
|         private const string STATUS_DENIDED = "denied"; | ||||
|         private const string STATUS_NOT_DETERMINED = "not_determined"; | ||||
|          | ||||
|         private Action<string> _onPermissionCallback; | ||||
|         private float _delaySeconds = 1.0f; | ||||
| 
 | ||||
|         private string EditorGrantedStatus | ||||
|         { | ||||
|             get => PlayerPrefs.GetString(nameof(EditorGrantedStatus), STATUS_NOT_DETERMINED); | ||||
|             set => PlayerPrefs.SetString(nameof(EditorGrantedStatus), value); | ||||
|         } | ||||
|          | ||||
|         public void Init() | ||||
|         { | ||||
|             Debug.Log($"[SDK][Noti][EDT] --- NotificationAgentStub Init: {EditorGrantedStatus}"); | ||||
|         } | ||||
| 
 | ||||
|         public string GetStatus() => EditorGrantedStatus; | ||||
| 
 | ||||
|         public bool IsAllowed() | ||||
|         { | ||||
|             return EditorGrantedStatus == STATUS_GRANTED; | ||||
|         } | ||||
| 
 | ||||
|         public void RequestPermission(Action<string> callback = null) | ||||
|         { | ||||
|             Debug.Log($"[SDK][Noti][EDT] --- RequestPermission ---"); | ||||
|             _onPermissionCallback = callback; | ||||
|             DelayCallPermissionHandle(); | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 延迟模拟回调 | ||||
|         /// </summary> | ||||
|         private async void DelayCallPermissionHandle() | ||||
|         { | ||||
|             await Task.Delay((int)(1000 * _delaySeconds)); | ||||
|             EditorGrantedStatus = STATUS_GRANTED; | ||||
|             _onPermissionCallback?.Invoke(EditorGrantedStatus); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: 6550e85d25634c46b7a57c490dcea173 | ||||
| timeCreated: 1718850440 | ||||
|  | @ -0,0 +1,98 @@ | |||
| namespace Guru.Notification | ||||
| { | ||||
|     using UnityEngine; | ||||
|     using System; | ||||
|      | ||||
|      | ||||
|      | ||||
|     /// <summary> | ||||
|     /// 消息管理器 | ||||
|     /// </summary> | ||||
|     public class NotificationService | ||||
|     { | ||||
|         // 服务版本号 | ||||
|         public const string Version = "0.0.1"; | ||||
| 
 | ||||
| 
 | ||||
|         // 初始化标志位 | ||||
|         private static bool _initOnce; | ||||
| 
 | ||||
|         private static string DEFAULT_USER_STATUS = "not_determined"; | ||||
| 
 | ||||
| 
 | ||||
|         #region 初始化 | ||||
| 
 | ||||
|         private static INotificationAgent _agent; | ||||
| 
 | ||||
|         internal static INotificationAgent Agent | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (_agent == null) | ||||
|                 { | ||||
|                     _agent = GetAgent(); | ||||
|                 } | ||||
|                 return _agent; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private static INotificationAgent GetAgent() | ||||
|         { | ||||
| #if UNITY_EDITOR | ||||
|             return new NotificationAgentStub(); | ||||
| #elif UNITY_ANDROID | ||||
|             return new NotificationAgentAndroid(); | ||||
| #elif UNITY_IOS | ||||
|             return new NotificationAgentIOS(); | ||||
| #endif | ||||
|             return null; | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 初始化 | ||||
|         /// </summary> | ||||
|         public static void Initialize() | ||||
|         { | ||||
|             if (_initOnce) return; | ||||
|             _initOnce = true; | ||||
|             Agent?.Init(); // 初始化代理 | ||||
|         } | ||||
|          | ||||
|          | ||||
|          | ||||
|         #endregion | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         #region 接口 | ||||
|          | ||||
|         /// <summary> | ||||
|         /// 拉起 Noti 请求 | ||||
|         /// </summary> | ||||
|         /// <param name="callback"></param> | ||||
|         public static void RequestPermission(Action<string> callback = null) | ||||
|         { | ||||
|             if (Agent != null) | ||||
|             { | ||||
|                 Agent.RequestPermission(callback); | ||||
|                 return; | ||||
|             } | ||||
|             callback?.Invoke(DEFAULT_USER_STATUS); | ||||
|         } | ||||
|          | ||||
|         public static bool IsPermissionGranted() | ||||
|         { | ||||
|             return Agent?.IsAllowed() ?? false; | ||||
|         } | ||||
| 
 | ||||
|         public static string GetStatus() | ||||
|         { | ||||
|             return Agent?.GetStatus() ?? DEFAULT_USER_STATUS; | ||||
|         } | ||||
| 
 | ||||
|         #endregion | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| fileFormatVersion: 2 | ||||
| guid: e0f31750e8bc48a6a5e4f56ee2d5e1cc | ||||
| timeCreated: 1718846076 | ||||
|  | @ -12,6 +12,7 @@ | |||
|   "dependencies": { | ||||
|     "com.unity.ads.ios-support": "1.2.0", | ||||
|     "com.unity.editorcoroutines": "1.0.0", | ||||
|     "com.unity.mobile.notifications": "2.2.2", | ||||
|     "com.unity.mobile.android-logcat": "1.3.2", | ||||
|     "com.unity.purchasing": "4.10.0" | ||||
|   } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue