Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions examples/demo/Assets/Resources/Theme.uss
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,39 @@
color: #34A853;
}

.language-dropdown-icon {
font-size: 18px;
color: var(--os-grey-600);
margin-left: 4px;
}

.dialog-overlay.dropdown-overlay {
background-color: rgba(0, 0, 0, 0);
padding-top: 0;
}

.dialog-container.dropdown-menu {
position: absolute;
width: 240px;
margin: 0;
padding: 8px 0;
border-radius: 8px;
border-width: 1px;
border-color: var(--os-card-border);
}

.dropdown-item {
height: 48px;
padding: 0 16px;
-unity-text-align: middle-left;
font-size: 16px;
}

.dropdown-item--selected {
color: var(--os-primary);
-unity-font-style: bold;
}

.guidance-banner {
background-color: var(--os-warning-background);
}
Expand Down
7 changes: 7 additions & 0 deletions examples/demo/Assets/Scripts/Services/PreferencesService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class PreferencesService
private const string KeyExternalUserId = "external_user_id";
private const string KeyLocationShared = "location_shared";
private const string KeyIamPaused = "iam_paused";
private const string KeyLanguage = "language";

public bool ConsentRequired
{
Expand Down Expand Up @@ -40,6 +41,12 @@ public bool IamPaused
set => SetBool(KeyIamPaused, value);
}

public string Language
{
get => GetString(KeyLanguage, "");
set => SetString(KeyLanguage, value);
}

private bool GetBool(string key, bool defaultValue) =>
PlayerPrefs.GetInt(key, defaultValue ? 1 : 0) == 1;

Expand Down
2 changes: 2 additions & 0 deletions examples/demo/Assets/Scripts/UI/Dialogs/DialogBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public abstract class DialogBase
protected VisualElement Container { get; private set; }
private VisualElement _parent;

protected VisualElement Parent => _parent;

public void Show(VisualElement parent)
{
if (parent?.Q(className: "dialog-overlay") != null)
Expand Down
189 changes: 189 additions & 0 deletions examples/demo/Assets/Scripts/UI/Dialogs/DropdownMenuDialog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using OneSignalDemo.Services;
using UnityEngine;
using UnityEngine.UIElements;

namespace OneSignalDemo.UI.Dialogs
{
/// <summary>
/// Menu anchored under the element that opened it, dismissed by tapping anywhere outside it.
/// </summary>
public class DropdownMenuDialog : DialogBase
{
private const float MenuMaxHeight = 320f;
private const float ScreenMargin = 16f;
private const float ItemHeight = 48f;
private const float ListPadding = 8f;
private const float MouseDragThreshold = 8f;

private readonly VisualElement _anchor;
private readonly IReadOnlyList<KeyValuePair<string, string>> _options;
private readonly string _selectedValue;
private readonly string _namePrefix;
private readonly Action<string> _onSelected;

/// <param name="options">Display label to value pairs.</param>
public DropdownMenuDialog(
VisualElement anchor,
IReadOnlyList<KeyValuePair<string, string>> options,
string selectedValue,
string namePrefix,
Action<string> onSelected
)
{
_anchor = anchor;
_options = options;
_selectedValue = selectedValue ?? "";
_namePrefix = namePrefix;
_onSelected = onSelected;
}

protected override void BuildContent(VisualElement container)
{
Overlay.AddToClassList("dropdown-overlay");
container.AddToClassList("dropdown-menu");

var list = new ScrollView(ScrollViewMode.Vertical);
list.name = $"{_namePrefix}_list";
list.style.maxHeight = MenuMaxHeight - ListPadding * 2;

VisualElement selectedItem = null;
foreach (var option in _options)
{
var value = option.Value;
var item = new Label(option.Key);
item.name = $"{_namePrefix}_{(value.Length == 0 ? "default" : value)}";
item.AddToClassList("dropdown-item");
item.AddToClassList("text-card-label");
if (value == _selectedValue)
{
item.AddToClassList("dropdown-item--selected");
selectedItem = item;
}

void Select() => OnSelected(value);
item.AddManipulator(new Clickable(Select));
AccessibilityBridge.RegisterE2ETapTarget(item, () => true, Select);
list.Add(item);
}
container.Add(list);
EnableMouseDragScroll(list);

if (selectedItem != null)
CenterOnceLaidOut(list, selectedItem);

Position(container);
}

private void Position(VisualElement container)
{
var bounds = Parent.worldBound;
var anchor = _anchor.worldBound;
float anchorTop = anchor.yMin - bounds.yMin;
float anchorBottom = anchor.yMax - bounds.yMin;
float menuHeight = Mathf.Min(
MenuMaxHeight,
_options.Count * ItemHeight + ListPadding * 2
);

container.style.right = Mathf.Max(ScreenMargin, bounds.xMax - anchor.xMax);
if (anchorBottom + menuHeight > bounds.height - ScreenMargin)
container.style.bottom = bounds.height - anchorTop;
else
container.style.top = anchorBottom;
}

private static void CenterOnceLaidOut(ScrollView list, VisualElement item)
{
void Center(GeometryChangedEvent _)
{
list.UnregisterCallback<GeometryChangedEvent>(Center);
float viewport = list.contentViewport.layout.height;
float maxOffset = Mathf.Max(0, list.contentContainer.layout.height - viewport);
float offset = item.layout.y - (viewport - item.layout.height) / 2;
list.scrollOffset = new Vector2(0, Mathf.Clamp(offset, 0, maxOffset));
}
list.RegisterCallback<GeometryChangedEvent>(Center);
}

// ScrollView only drag-scrolls touch and pen, and Android emulators report clicks as mouse.
private static void EnableMouseDragScroll(ScrollView list)
{
int pointerId = PointerId.invalidPointerId;
float startY = 0;
float startOffset = 0;
bool dragging = false;

void Reset()
{
pointerId = PointerId.invalidPointerId;
dragging = false;
}

list.RegisterCallback<PointerDownEvent>(
e =>
{
if (e.pointerType != UnityEngine.UIElements.PointerType.mouse || e.button != 0)
return;
pointerId = e.pointerId;
startY = e.position.y;
startOffset = list.scrollOffset.y;
dragging = false;
},
TrickleDown.TrickleDown
);
list.RegisterCallback<PointerMoveEvent>(
e =>
{
if (e.pointerId != pointerId)
return;
float delta = e.position.y - startY;
if (!dragging)
{
if (Mathf.Abs(delta) < MouseDragThreshold)
return;
dragging = true;
// Taking capture from the item's Clickable cancels its pending selection.
list.CapturePointer(pointerId);
}
float maxOffset = Mathf.Max(
0,
list.contentContainer.layout.height - list.contentViewport.layout.height
);
list.scrollOffset = new Vector2(
0,
Mathf.Clamp(startOffset - delta, 0, maxOffset)
);
e.StopPropagation();
},
TrickleDown.TrickleDown
);
list.RegisterCallback<PointerUpEvent>(
e =>
{
if (e.pointerId != pointerId)
return;
if (dragging)
{
list.ReleasePointer(pointerId);
e.StopPropagation();
}
Reset();
},
TrickleDown.TrickleDown
);
list.RegisterCallback<PointerCaptureOutEvent>(e =>
{
if (e.target == list)
Reset();
});
}

private void OnSelected(string value)
{
Dismiss();
_onSelected?.Invoke(value);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/demo/Assets/Scripts/UI/MaterialIcons.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ public static class MaterialIcons
public const string Fullscreen = "\uE5D0";
public const string ExpandMore = "\uE5CF";
public const string ExpandLess = "\uE5CE";
public const string Check = "\uE5CA";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class PushSectionController
private readonly AppViewModel _viewModel;
private readonly VisualElement _root;
private Label _pushIdLabel;
private Label _statusLabel;
private SwitchToggle _enabledToggle;
private Button _promptButton;

Expand Down Expand Up @@ -43,8 +44,19 @@ private VisualElement BuildSection()

card.Add(SectionBuilder.CreateDivider());

var statusRow = SectionBuilder.CreateInlineKeyValue(
"Status",
FormatStatus(_viewModel.IsPushSubscribed)
);
// The shared E2E ID names the status text itself, not the row.
_statusLabel = statusRow.Q<Label>(className: "key-value-inline-value");
_statusLabel.name = "push_subscription_status";
card.Add(statusRow);

card.Add(SectionBuilder.CreateDivider());

var toggleRow = SectionBuilder.CreateToggleRow(
"Enabled",
"Opted In",
null,
"push_enabled_toggle",
_viewModel.PushOptedIn,
Expand Down Expand Up @@ -72,6 +84,7 @@ private VisualElement BuildSection()
public void Refresh()
{
_pushIdLabel.text = FormatIdValue(_viewModel.PushSubscriptionId);
_statusLabel.text = FormatStatus(_viewModel.IsPushSubscribed);
_enabledToggle.SetValueWithoutNotify(_viewModel.PushOptedIn);
_enabledToggle.SetEnabled(_viewModel.HasPermission);
_promptButton.style.display = _viewModel.HasPermission
Expand All @@ -82,6 +95,9 @@ public void Refresh()
private static string FormatIdValue(string value) =>
string.IsNullOrEmpty(value) ? "—" : value;

private static string FormatStatus(bool subscribed) =>
subscribed ? "Subscribed" : "Unsubscribed";

private void OnEnabledChanged(bool value) => _viewModel.SetPushEnabled(value);
}
}
Loading
Loading