Skip to content
Draft
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
4 changes: 3 additions & 1 deletion src/Aetherphone/Apps/Aethergram/AethergramApp.Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,14 @@ private void EnsureMessagePolicyLoaded()
{
messagePolicy = me.MessagePolicy;
privateAccount = me.IsPrivate;
messagePolicyLoaded = true;
}

messagePolicyLoaded = true;
}
catch (Exception exception)
{
AepLog.Warning($"Aethergram message privacy load failed: {exception.Message}");
messagePolicyLoaded = true;
}
finally
{
Expand Down
2 changes: 1 addition & 1 deletion src/Aetherphone/Apps/Clock/ClockApp.Alarms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private void DrawAlarmRow(Rect row, AlarmEntry alarm)
{
var scale = UiScale.Current;
var timeInk = alarm.Enabled ? ui.TitleInk : ui.MutedInk;
var time = $"{alarm.Hour:D2}:{alarm.Minute:D2}";
var time = TimeText.Clock(new DateTime(1, 1, 1, alarm.Hour, alarm.Minute, 0));
var timeSize = Typography.Measure(time, TextStyles.Title1);
Typography.Draw(new Vector2(row.Min.X, row.Center.Y - timeSize.Y * 0.5f), time, timeInk, TextStyles.Title1);

Expand Down
3 changes: 3 additions & 0 deletions src/Aetherphone/Apps/Collections/CollectionsApp.Browse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,9 @@ private bool DrawPagerButton(Vector2 center, bool left, bool enabled, float scal

private void DrawOwnershipSegments(Rect bar)
{
ownershipLabels[0] = Loc.T(L.Collections.FilterAll);
ownershipLabels[1] = Loc.T(L.Collections.FilterOwned);
ownershipLabels[2] = Loc.T(L.Collections.FilterMissing);
var selected = SegmentStrip.Draw("collections.ownership", bar, ownershipLabels, (int)ownership, ui.Palette);
if (selected != (int)ownership)
{
Expand Down
3 changes: 0 additions & 3 deletions src/Aetherphone/Apps/Collections/CollectionsApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,6 @@ public void OnOpened()
{
router.Reset();
ResetFilters();
ownershipLabels[0] = Loc.T(L.Collections.FilterAll);
ownershipLabels[1] = Loc.T(L.Collections.FilterOwned);
ownershipLabels[2] = Loc.T(L.Collections.FilterMissing);
lodestoneId = ResolveLocalId();
catalog.ResetOwned();
catalog.ResetSummaries();
Expand Down
4 changes: 3 additions & 1 deletion src/Aetherphone/Apps/Settings/Pages/AccountPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,9 @@ private void DrawAccountsSection(PhoneTheme theme, float scale, bool signedIn)
AddAccount();
}

if (SettingsRow.Bool(actions.NextRow(), Loc.T(L.Account.FollowCharacter), session.FollowsCharacter, theme))
var followCharacter = SettingsRow.Bool(actions.NextRow(), Loc.T(L.Account.FollowCharacter),
session.FollowsCharacter, theme);
if (followCharacter != session.FollowsCharacter)
{
ToggleFollowCharacter();
}
Expand Down
10 changes: 5 additions & 5 deletions src/Aetherphone/Apps/Settings/Pages/AppearancePage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void Draw(in PhoneContext context, Rect body)
{
SettingsSection.Header(Loc.T(L.Settings.Theme), theme);
var accentLabel = Loc.T(L.Settings.Accent);
var cardWidth = ImGui.GetContentRegionAvail().X - 2f * Metrics.Space.Lg * UiScale.Current;
var cardWidth = ScrollLayout.StableContentWidth() - 2f * Metrics.Space.Lg * UiScale.Current;
var accentStacked = SwatchStrip.NeedsTwoRows(accentLabel, ThemeCatalog.Accents.Count + 1, cardWidth);
var card = GroupCard.Begin(theme, accentStacked ? 5 : 4);
var modeIndex = SegmentStrip.Draw("settings.themeMode", card.NextRow(), ModeLabels(), CurrentModeIndex(),
Expand Down Expand Up @@ -179,12 +179,12 @@ private void DrawHomeSection(PhoneTheme theme)
{
SettingsSection.Header(Loc.T(L.Home.HomeScreen), theme);
var card = GroupCard.Begin(theme, 3);
var previousDensityIndex = DensityIndex(configuration.HomeGridRows);
var densityIndex = SegmentStrip.Draw("settings.homeGrid", card.NextRow(), DensityLabels(),
DensityIndex(configuration.HomeGridRows), theme);
var rows = GridRowOptions[densityIndex];
if (rows != configuration.HomeGridRows)
previousDensityIndex, theme);
if (densityIndex != previousDensityIndex)
{
configuration.HomeGridRows = rows;
configuration.HomeGridRows = GridRowOptions[densityIndex];
configuration.Save();
}

Expand Down
16 changes: 8 additions & 8 deletions src/Aetherphone/Apps/Settings/Pages/NamePage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal sealed class NamePage : ISettingsPage, IDisposable
private readonly CancellationTokenSource cancellation = new();
private string editDisplay = string.Empty;
private string editHandle = string.Empty;
private string editStatus = string.Empty;
private LocString? editStatusKey;
private string? loadedFor;
private volatile bool busy;
private volatile int outcome;
Expand Down Expand Up @@ -69,21 +69,21 @@ public void Draw(in PhoneContext context, Rect body)
if (outcome == 2)
{
outcome = 0;
editStatus = Loc.T(L.Account.HandleTaken);
editStatusKey = L.Account.HandleTaken;
}

if (outcome == 3)
{
outcome = 0;
editStatus = Loc.T(L.Account.CannotReach);
editStatusKey = L.Account.CannotReach;
}

if (loadedFor != user.Id)
{
loadedFor = user.Id;
editDisplay = user.DisplayName;
editHandle = user.Handle;
editStatus = string.Empty;
editStatusKey = null;
}

using (AppSurface.Begin(body))
Expand All @@ -101,12 +101,12 @@ public void Draw(in PhoneContext context, Rect body)
Save();
}

if (editStatus.Length > 0)
if (editStatusKey is { } statusKey)
{
ImGui.Dummy(new Vector2(0f, 10f * scale));
using (ImRaii.PushColor(ImGuiCol.Text, theme.Danger))
{
Typography.Wrapped(editStatus);
Typography.Wrapped(Loc.T(statusKey));
}
}

Expand Down Expand Up @@ -188,12 +188,12 @@ private void Save()

if (editDisplay.Trim().Length == 0 || !SocialProfilePages.IsHandleValid(editHandle))
{
editStatus = Loc.T(L.Account.HandleRules);
editStatusKey = L.Account.HandleRules;
return;
}

busy = true;
editStatus = string.Empty;
editStatusKey = null;
var request = new UpdateProfileRequest(editDisplay.Trim(), editHandle.Trim(), null);
var token = cancellation.Token;
_ = Task.Run(async () =>
Expand Down
5 changes: 4 additions & 1 deletion src/Aetherphone/Apps/Settings/Pages/PrivacyPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,15 @@ private void EnsureLoaded()
{
shareReadReceipts = me.ShareReadReceipts;
sharePresence = me.SharePresence;
chatPrivacyLoaded = true;
}

// Latch loaded even on null/failure so a dead endpoint cannot refetch every frame.
chatPrivacyLoaded = true;
}
catch (Exception exception)
{
AepLog.Warning($"Chat privacy load failed: {exception.Message}");
chatPrivacyLoaded = true;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: latching on failure kills the refetch storm but makes the failure permanent for the session. The page then renders the shareReadReceipts/sharePresence defaults (both true) as authoritative, and Push sends both booleans, so flipping one silently overwrites the other server-side. Latch only when me is not null, and give the failure path a retry cooldown instead.

}
finally
{
Expand Down
6 changes: 5 additions & 1 deletion src/Aetherphone/Apps/Settings/Pages/ProfilePage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ public void Draw(in PhoneContext context, Rect body)
var theme = context.Theme;
using (AppSurface.Begin(body))
{
if (session.IsSignedIn && session.CurrentUser is not null && !initialSynced)
if (!session.IsSignedIn)
{
initialSynced = false;
}
else if (session.CurrentUser is not null && !initialSynced)
{
initialSynced = true;
PushTimeZone(null);
Expand Down
3 changes: 2 additions & 1 deletion src/Aetherphone/Apps/Settings/Pages/RootSettingsPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ private static void DrawVersion(PhoneTheme theme)
var size = Typography.Measure(label, 0.78f);
var origin = ImGui.GetCursorScreenPos();
var avail = ImGui.GetContentRegionAvail().X;
Typography.Draw(new Vector2(origin.X + (avail - size.X) * 0.5f, origin.Y), label, theme.TextMuted, 0.78f);
Typography.Draw(ImGui.GetWindowDrawList(), new Vector2(origin.X + (avail - size.X) * 0.5f, origin.Y), label,
theme.TextMuted, 0.78f);
ImGui.Dummy(new Vector2(avail, size.Y));
}
}
4 changes: 3 additions & 1 deletion src/Aetherphone/Apps/Settings/Pages/TagsMentionsPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,14 @@ private void EnsureLoaded()
mentionPolicy = me.MentionPolicy;
tagPolicy = me.TagPolicy;
requireTagApproval = me.RequireTagApproval;
loaded = true;
}

loaded = true;
}
catch (Exception exception)
{
AepLog.Warning($"Tag privacy load failed: {exception.Message}");
loaded = true;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: same shape as PrivacyPage. After a failed load the page shows default policies as real values, and PushTags sends tagPolicy and requireTagApproval together, so touching one overwrites the other with a default. Same fix: cooldown rather than a permanent latch.

}
finally
{
Expand Down
4 changes: 2 additions & 2 deletions src/Aetherphone/Core/Aethernet/SignInFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public void VerifyLodestone()
if (result.Auth is { } auth)
{
session.SignIn(auth.Token, auth.User);
signedIn?.Invoke();
_ = Plugin.Framework.RunOnFrameworkThread(() => signedIn?.Invoke());

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Reset() now runs before signedIn fires, so handlers observe the cleared flow state instead of the post-verify state. Also Plugin.Framework is unguarded here while ConfirmService gained a null guard in ab98f8f; worth keeping the two consistent.

Reset();
return;
}
Expand Down Expand Up @@ -188,7 +188,7 @@ private async Task PollXivLoopAsync(string flowId, int intervalSeconds, int expi
if (result.Auth is { } auth)
{
session.SignIn(auth.Token, auth.User);
signedIn?.Invoke();
_ = Plugin.Framework.RunOnFrameworkThread(() => signedIn?.Invoke());
Reset();
return;
}
Expand Down
1 change: 0 additions & 1 deletion src/Aetherphone/Core/Aethernet/StoreWork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,5 @@ public void Run(
public void Dispose()
{
cancellation.Cancel();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: dropping cancellation.Dispose() is the right call since in-flight tasks still hold the token, but it reads as an accidental leak. A one-line why comment would stop someone re-adding it.

cancellation.Dispose();
}
}
29 changes: 24 additions & 5 deletions src/Aetherphone/Core/Confirm/ConfirmService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ internal sealed class ConfirmService

public void Ask(ConfirmRequest request)
{
// Plugin.Framework is null in unit tests - run inline there. In-game, hop onto the
// Framework thread when Ask arrives from a websocket / worker callback.
if (Plugin.Framework is { } framework && !framework.IsInFrameworkUpdateThread)
{
_ = framework.RunOnFrameworkThread(() => Ask(request));
return;
}

if (Active is not null)
{
queued.Enqueue(request);
Expand Down Expand Up @@ -64,15 +72,26 @@ public void Proceed()
Status = null;
handler(ok =>
{
Busy = false;
if (ok)
void Finish()
{
Advance();
Busy = false;
if (ok)
{
Advance();
}
else
{
Status = request.FailedMessage;
}
}
else

if (Plugin.Framework is { } framework && !framework.IsInFrameworkUpdateThread)
{
Status = request.FailedMessage;
_ = framework.RunOnFrameworkThread(Finish);
return;
}

Finish();
});
return;
}
Expand Down
6 changes: 5 additions & 1 deletion src/Aetherphone/Core/Crypto/KeyVault.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,26 @@ internal sealed class KeyVault : IDisposable
private EcPrivateKey? privateKey;
private MyKeysDto? serverBundle;
private volatile bool refreshing;
private string? lastUserId;

public KeyVault(Configuration configuration, AethernetSession session, KeysClient client)
{
this.configuration = configuration;
this.session = session;
this.client = client;
lastUserId = session.CurrentUser?.Id;
session.Changed += OnSessionChanged;
}

private void OnSessionChanged()
{
if (session.IsSignedIn)
var userId = session.CurrentUser?.Id;
if (string.Equals(userId, lastUserId, StringComparison.Ordinal))
{
return;
}

lastUserId = userId;
_ = RefreshAsync(CancellationToken.None);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Aetherphone/Core/Message/ChatThreadStoreBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ protected ChatThreadStoreBase(string logTag, AethernetSession session, SafetyCli
private void OnSessionAccountChanged()
{
var accountId = session.CurrentUser?.Id;
if (accountId is null || string.Equals(accountId, lastAccountId, StringComparison.Ordinal))
if (string.Equals(accountId, lastAccountId, StringComparison.Ordinal))
{
return;
}
Expand Down
10 changes: 6 additions & 4 deletions src/Aetherphone/Core/Telephony/CallAudioController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ public bool EnsureStartedLocked(Guid callId, int localSlot)
return false;
}

if (localSlot < 0)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: CallHub.HandleRoster still sets state = CallState.Active when this returns false, so a roster that omits the local slot leaves the call Active with no audio session and no elapsed timer, and nothing retries until the next roster frame.

{
return false;
}

var input = AudioDevices.ResolveInput(configuration.CallInputDevice);
var output = AudioDevices.ResolveOutput(configuration.CallOutputDevice);
var created = new CallSession(callId, connection, input, output, volume) { Muted = muted, };
if (localSlot >= 0)
{
created.SetLocalSlot(localSlot);
}
created.SetLocalSlot(localSlot);

session = created;
remoteSlots.Clear();
Expand Down
20 changes: 19 additions & 1 deletion src/Aetherphone/Core/Telephony/CallHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,25 @@ private void HandleDeclined(Guid id, CallControl message)
return;
}

var pending = 0;
var fromId = message.From?.UserId;
if (fromId is not null)
{
if (dialingTo?.UserId == fromId)
{
dialingTo = null;
}

for (var index = 0; index < roster.Length; index++)
{
if (roster[index].UserId == fromId)
{
var participant = roster[index];
roster[index] = participant with { State = ParticipantState.Left };
}
}
}

var pending = dialingTo is not null ? 1 : 0;
for (var index = 0; index < roster.Length; index++)
{
var participant = roster[index];
Expand Down
Loading