2025-05-18 01:04:31 +08:00

377 lines
16 KiB
C#

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
using Unity.Notifications.iOS;
using UnityEngine.Assertions;
using UnityEngine.UIElements;
namespace Unity.Notifications
{
internal class NotificationSettingsProvider : SettingsProvider
{
private const int k_SlotSize = 64;
private const int k_IconSpacing = 8;
private const float k_Padding = 12f;
private const float k_ToolbarHeight = 20f;
private const int k_VerticalSeparator = 2;
private const int k_LabelLineHeight = 18;
private readonly GUIContent k_IdentifierLabelText = new GUIContent("Identifier");
private readonly GUIContent k_TypeLabelText = new GUIContent("Type");
private readonly string[] k_ToolbarStrings = { "Android", "iOS" };
private const string k_InfoStringAndroid =
"Only icons added to this list or manually added to the 'res/drawable' folder can be used for notifications.\n" +
"Note, that not all devices support colored icons.\n\n" +
"Small icons must be at least 48x48px and only composed of white pixels on a transparent background.\n" +
"Large icons must be no smaller than 192x192px and may contain colors.";
private NotificationSettingsManager m_SettingsManager;
private SerializedObject m_SettingsManagerObject;
private SerializedProperty m_DrawableResources;
private Vector2 m_ScrollViewStart;
private ReorderableList m_ReorderableList;
private NotificationSettingsProvider(string path, SettingsScope scopes)
: base(path, scopes)
{
Initialize();
}
[SettingsProvider]
static SettingsProvider CreateMobileNotificationsSettingsProvider()
{
return new NotificationSettingsProvider("Project/Mobile Notifications", SettingsScope.Project);
}
public override void OnActivate(string searchContext, VisualElement rootElement)
{
base.OnActivate(searchContext, rootElement);
// in case of domain reload (enter-exit play mode, this gets lost)
if (m_SettingsManager == null)
Initialize();
}
public override void OnDeactivate()
{
base.OnDeactivate();
m_SettingsManager.SaveSettings(false);
}
private void Initialize()
{
label = "Mobile Notifications";
m_SettingsManager = NotificationSettingsManager.Initialize();
// These two are for ReorderableList.
m_SettingsManagerObject = new SerializedObject(m_SettingsManager);
m_DrawableResources = m_SettingsManagerObject.FindProperty("DrawableResources");
// ReorderableList is only used to draw the drawable resources for Android settings.
InitReorderableList();
Undo.undoRedoPerformed += () =>
{
m_SettingsManagerObject.UpdateIfRequiredOrScript();
Repaint();
};
}
private void InitReorderableList()
{
m_ReorderableList = new ReorderableList(m_SettingsManagerObject, m_DrawableResources, false, false, true, true);
m_ReorderableList.elementHeight = k_SlotSize + k_IconSpacing;
m_ReorderableList.showDefaultBackground = true;
m_ReorderableList.headerHeight = 1;
// Register all the necessary callbacks on ReorderableList.
m_ReorderableList.onAddCallback = (list) =>
{
Undo.RegisterCompleteObjectUndo(m_SettingsManager, "Add a new icon element");
m_SettingsManager.AddDrawableResource(string.Format("icon_{0}", m_SettingsManager.DrawableResources.Count), null, NotificationIconType.Small);
};
m_ReorderableList.onRemoveCallback = (list) =>
{
m_SettingsManager.RemoveDrawableResourceByIndex(list.index);
};
m_ReorderableList.onCanAddCallback = (list) =>
{
var trackedAssets = m_SettingsManager.DrawableResources;
if (trackedAssets.Count <= 0)
return true;
return trackedAssets.All(asset => asset.Initialized());
};
m_ReorderableList.drawElementCallback = (rect, index, active, focused) => DrawIconDataElement(rect, index, active, focused);
m_ReorderableList.drawElementBackgroundCallback = (rect, index, active, focused) =>
{
if (Event.current.type != EventType.Repaint)
return;
var background = index % 2 == 0 ? NotificationStyles.k_EvenRow : NotificationStyles.k_OddRow;
background.Draw(rect, false, false, false, false);
ReorderableList.defaultBehaviours.DrawElementBackground(rect, index, active, focused, true);
};
m_ReorderableList.elementHeightCallback = (index) =>
{
var data = GetDrawableResource(index);
if (data == null)
return k_SlotSize;
return m_ReorderableList.elementHeight + (data.Asset != null && !data.IsValid ? k_SlotSize : 0);
};
}
private void DrawIconDataElement(Rect rect, int index, bool active, bool focused)
{
var drawableResource = GetDrawableResource(index);
if (drawableResource == null)
return;
var elementRect = AddPadding(rect, k_Padding, k_Padding / 2);
// Calculate and draw id and type.
var idLabelRect = new Rect(elementRect.x, elementRect.y, k_SlotSize, k_ToolbarHeight);
var idTextFieldRect = new Rect(idLabelRect.x + k_SlotSize, idLabelRect.y, k_SlotSize, k_ToolbarHeight);
var typeLabelRect = new Rect(elementRect.x, elementRect.y + 25, k_SlotSize, k_ToolbarHeight);
var typeEnumPopupRect = new Rect(typeLabelRect.x + k_SlotSize, typeLabelRect.y, k_SlotSize, k_ToolbarHeight);
EditorGUI.LabelField(idLabelRect, k_IdentifierLabelText);
var newId = EditorGUI.TextField(idTextFieldRect, drawableResource.Id);
EditorGUI.LabelField(typeLabelRect, k_TypeLabelText);
var newType = (NotificationIconType)EditorGUI.EnumPopup(typeEnumPopupRect, drawableResource.Type);
// Calculate and draw texture and preview.
var textureX = Mathf.Max(elementRect.width - (k_SlotSize * 2 - k_IconSpacing * 5), k_SlotSize * 3);
var textureRect = new Rect(textureX, elementRect.y, k_SlotSize, k_SlotSize);
var previewTextureRect = new Rect(textureRect.x + k_SlotSize, textureRect.y - 6, k_SlotSize, k_SlotSize);
var newAsset = (Texture2D)EditorGUI.ObjectField(textureRect, drawableResource.Asset, typeof(Texture2D), false);
// Check if the texture has been updated.
bool updatePreviewTexture = (newId != drawableResource.Id || newType != drawableResource.Type || newAsset != drawableResource.Asset);
if (updatePreviewTexture)
{
Undo.RegisterCompleteObjectUndo(m_SettingsManager, "Update icon data");
drawableResource.Id = newId;
drawableResource.Type = newType;
drawableResource.Asset = newAsset;
drawableResource.Clean();
drawableResource.Verify();
m_SettingsManager.SaveSettings();
}
if (drawableResource.Asset != null && !drawableResource.Verify())
{
var errorMsgWidth = rect.width - k_Padding * 2;
var errorRect = AddPadding(new Rect(elementRect.x, elementRect.y + k_SlotSize, errorMsgWidth, k_LabelLineHeight * 3), 4, 4);
var errorMsg = "Specified texture can't be used because: \n" + drawableResource.GenerateErrorString();
EditorGUI.HelpBox(errorRect, errorMsg, MessageType.Error);
GUI.Box(previewTextureRect, "Preview not available", NotificationStyles.k_PreviewMessageTextStyle);
}
else
{
var previewTexture = drawableResource.GetPreviewTexture(updatePreviewTexture);
if (previewTexture != null)
{
previewTexture.alphaIsTransparency = false;
EditorGUI.LabelField(previewTextureRect, "Preview", NotificationStyles.k_PreviewLabelTextStyle);
var previewTextureRectPadded = AddPadding(previewTextureRect, 6f, 6f);
previewTextureRectPadded.y += 8;
GUI.DrawTexture(previewTextureRectPadded, previewTexture);
}
}
}
private DrawableResourceData GetDrawableResource(int index)
{
var resourceAssets = m_SettingsManager.DrawableResources;
if (index < resourceAssets.Count)
return resourceAssets[index];
return null;
}
private static Rect AddPadding(Rect rect, float horizontal, float vertical)
{
Rect paddingRect = rect;
paddingRect.xMin += horizontal;
paddingRect.xMax -= horizontal;
paddingRect.yMin += vertical;
paddingRect.yMax -= vertical;
return paddingRect;
}
public override void OnGUI(string searchContext)
{
if (m_SettingsManager == null)
Initialize();
// This has to be called to sync all the changes between m_SettingsManager and m_SettingsManagerObject.
if (m_SettingsManagerObject.targetObject != null)
m_SettingsManagerObject.Update();
var noHeightRect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.label, GUILayout.ExpandWidth(true), GUILayout.Height(0));
var width = noHeightRect.width;
var totalRect = new Rect(k_Padding, 0f, width - k_Padding, Screen.height);
// Draw the toolbar for Android/iOS.
var toolBarRect = new Rect(totalRect.x, totalRect.y, totalRect.width, k_ToolbarHeight);
var toolbarIndex = GUI.Toolbar(toolBarRect, m_SettingsManager.ToolbarIndex, k_ToolbarStrings);
if (toolbarIndex != m_SettingsManager.ToolbarIndex)
{
m_SettingsManager.ToolbarIndex = toolbarIndex;
m_SettingsManager.SaveSettings();
}
var notificationSettingsRect = new Rect(totalRect.x, k_ToolbarHeight + 2, totalRect.width, totalRect.height - k_ToolbarHeight - k_Padding);
// Draw the notification settings.
int drawnSettingsCount = DrawNotificationSettings(notificationSettingsRect, m_SettingsManager.ToolbarIndex);
if (drawnSettingsCount <= 0)
return;
float heightPlaceHolder = k_SlotSize;
// Draw drawable resources list for Android.
if (m_SettingsManager.ToolbarIndex == 0)
{
var iconListlabelRect = new Rect(notificationSettingsRect.x, notificationSettingsRect.y + 85, 180, k_LabelLineHeight);
GUI.Label(iconListlabelRect, "Notification Icons", EditorStyles.label);
// Draw the help message for setting the icons.
float labelHeight;
if (notificationSettingsRect.width > 510)
labelHeight = k_LabelLineHeight * 4;
else if (notificationSettingsRect.width > 500)
labelHeight = k_LabelLineHeight * 5;
else
labelHeight = k_LabelLineHeight * 6;
var iconListMsgRect = new Rect(iconListlabelRect.x, iconListlabelRect.y + iconListlabelRect.height + k_VerticalSeparator, notificationSettingsRect.width, labelHeight);
EditorGUI.SelectableLabel(iconListMsgRect, k_InfoStringAndroid, NotificationStyles.k_IconHelpMessageStyle);
// Draw the reorderable list for the icon list.
var iconListRect = new Rect(iconListMsgRect.x, iconListMsgRect.y + iconListMsgRect.height + k_VerticalSeparator, iconListMsgRect.width, notificationSettingsRect.height - 55f);
m_ReorderableList.DoList(iconListRect);
heightPlaceHolder += iconListMsgRect.height + m_ReorderableList.GetHeight();
}
// We have to do this to occupy the space that ScrollView can set the scrollbars correctly.
EditorGUILayout.GetControlRect(true, heightPlaceHolder, GUILayout.MinWidth(width));
}
private int DrawNotificationSettings(Rect rect, int toolbarIndex)
{
Assert.IsTrue(toolbarIndex == 0 || toolbarIndex == 1);
List<NotificationSetting> settings;
BuildTargetGroup buildTarget;
int labelWidthMultiplier;
if (toolbarIndex == 0)
{
settings = m_SettingsManager.AndroidNotificationSettings;
buildTarget = BuildTargetGroup.Android;
labelWidthMultiplier = 3;
}
else
{
settings = m_SettingsManager.iOSNotificationSettings;
buildTarget = BuildTargetGroup.iOS;
labelWidthMultiplier = 5;
}
if (settings == null)
return 0;
GUI.BeginGroup(rect);
var drawnSettingsCount = DrawSettingElements(rect, buildTarget, settings, false, 0, labelWidthMultiplier);
GUI.EndGroup();
return drawnSettingsCount;
}
private int DrawSettingElements(Rect rect, BuildTargetGroup buildTarget, List<NotificationSetting> settings, bool disabled, int layer, int labelWidthMultiplier)
{
var spaceOffset = layer * 13;
var labelStyle = new GUIStyle(NotificationStyles.k_LabelStyle);
labelStyle.fixedWidth = k_SlotSize * labelWidthMultiplier - spaceOffset;
var toggleStyle = NotificationStyles.k_ToggleStyle;
var dropdownStyle = NotificationStyles.k_DropwDownStyle;
int elementCount = settings.Count;
foreach (var setting in settings)
{
EditorGUI.BeginDisabledGroup(disabled);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(spaceOffset);
GUILayout.Label(new GUIContent(setting.Label, setting.Tooltip), labelStyle);
bool dependenciesDisabled = false;
EditorGUI.BeginChangeCheck();
if (setting.Value.GetType() == typeof(bool))
{
setting.Value = EditorGUILayout.Toggle((bool)setting.Value, toggleStyle);
dependenciesDisabled = !(bool)setting.Value;
}
else if (setting.Value.GetType() == typeof(string))
{
setting.Value = EditorGUILayout.TextField((string)setting.Value);
}
else if (setting.Value.GetType() == typeof(PresentationOption))
{
setting.Value = (PresentationOption)EditorGUILayout.EnumFlagsField((iOSPresentationOption)setting.Value, dropdownStyle);
}
else if (setting.Value.GetType() == typeof(AuthorizationOption))
{
setting.Value = (AuthorizationOption)EditorGUILayout.EnumFlagsField((iOSAuthorizationOption)setting.Value, dropdownStyle);
if ((iOSAuthorizationOption)setting.Value == 0)
setting.Value = (AuthorizationOption)(iOSAuthorizationOption.Badge | iOSAuthorizationOption.Sound | iOSAuthorizationOption.Alert);
}
else if (setting.Value.GetType() == typeof(AndroidExactSchedulingOption))
{
setting.Value = (AndroidExactSchedulingOption)EditorGUILayout.EnumFlagsField((AndroidExactSchedulingOption)setting.Value, dropdownStyle);
}
else
Debug.LogError("Unsupported setting type: " + setting.Value.GetType());
if (EditorGUI.EndChangeCheck())
{
m_SettingsManager.SaveSetting(setting, buildTarget);
}
EditorGUILayout.EndHorizontal();
EditorGUI.EndDisabledGroup();
if (setting.Dependencies != null)
{
elementCount += DrawSettingElements(rect, buildTarget, setting.Dependencies, dependenciesDisabled, layer + 1, labelWidthMultiplier);
}
}
return elementCount;
}
}
}