mobile: fullscreen (immersive Android + PWA) + auto-hide reported nudity avatars
Fullscreen on mobile: - Android (Capacitor): MainActivity now runs edge-to-edge and hides the status + navigation bars (immersive, transient-on-swipe), re-asserted on focus. - PWA: manifest display -> "fullscreen" with display_override fallback chain; viewport gains viewport-fit: cover for proper safe-area/edge-to-edge handling. Moderation auto-hide: - ProfileService.ReportUser now de-dupes nudity reports per reporter and, once NudityHideThreshold (3) distinct players flag a target's avatar as nudity, auto-removes their custom photo (reverts to default avatar). Counted from the ledger, so still no schema change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -54,19 +54,52 @@ public class ProfileService
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>Distinct players who must flag an avatar as nudity before it auto-hides.</summary>
|
||||
public const int NudityHideThreshold = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Record a moderation report (inappropriate avatar / insulting chat). Stored
|
||||
/// in the write-only ledger as kind="report" so no schema change is needed;
|
||||
/// Ref encodes "{targetId}|{reason}|{details}".
|
||||
/// Ref encodes "{targetId}|{reason}|{details}". Once enough *distinct* players
|
||||
/// flag a target's avatar as nudity, the custom photo is auto-removed.
|
||||
/// </summary>
|
||||
public async Task ReportUser(string reporterUid, string targetId, string? reason, string? details)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(targetId)) return;
|
||||
if (string.IsNullOrWhiteSpace(targetId) || targetId == reporterUid) return;
|
||||
var safeReason = reason is "nudity" or "insult" or "other" ? reason : "other";
|
||||
var safeDetails = (details ?? "").Replace("\n", " ").Trim();
|
||||
var @ref = $"{targetId}|{safeReason}|{safeDetails}";
|
||||
if (@ref.Length > 480) @ref = @ref[..480];
|
||||
|
||||
var nudityPrefix = targetId + "|nudity";
|
||||
// De-dupe nudity reports so a single player can't nuke an avatar alone.
|
||||
if (safeReason == "nudity")
|
||||
{
|
||||
var already = await _db.Ledger.AnyAsync(
|
||||
l => l.Kind == "report" && l.UserId == reporterUid && l.Ref!.StartsWith(nudityPrefix));
|
||||
if (already) return;
|
||||
}
|
||||
|
||||
await Ledger(reporterUid, "report", 0, @ref);
|
||||
|
||||
// Auto-hide a custom avatar once enough distinct players flag it.
|
||||
if (safeReason == "nudity")
|
||||
{
|
||||
var reporters = await _db.Ledger
|
||||
.Where(l => l.Kind == "report" && l.Ref!.StartsWith(nudityPrefix))
|
||||
.Select(l => l.UserId)
|
||||
.Distinct()
|
||||
.CountAsync();
|
||||
if (reporters >= NudityHideThreshold)
|
||||
{
|
||||
var target = await GetOrCreate(targetId, null);
|
||||
if (!string.IsNullOrEmpty(target.AvatarImage))
|
||||
{
|
||||
target.AvatarImage = null; // revert to their default avatar
|
||||
await SaveInternal(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ProfileDto> Update(string uid, JsonElement patch)
|
||||
|
||||
Reference in New Issue
Block a user