M1 공유 타겟(U1)+M2 저장 진행 알림: ACTION_SEND 인텐트 필터+ShareTarget/SharePayload(RED 7건)+루트 타이머 큐 전송(am start 인텐트 E2E로 서버 큐 반영 실증), 오프라인 저장 Android ProgressBar 노티(5% 스로틀)→폰 저장 완료 전환(34MB E2E, Report(1) 되덮어쓰기 방지). 154/154 GREEN

This commit is contained in:
Yun Chan 2026-08-15 18:45:51 +09:00
parent 69094595a5
commit 04ff0ff93d
7 changed files with 166 additions and 6 deletions

View file

@ -163,6 +163,21 @@ public sealed class QueuePage : ContentPage
_summary.Text = err != null ? $"❌ {err}" : items.Count == 0 ? "대기 중인 항목이 없습니다" : $"{items.Count}건";
}
/// <summary>U1: 공유받은 링크 처리 결과 표시(루트 타이머가 호출).</summary>
public void ShowSharedResult(string message) => _summary.Text = message;
/// <summary>U1: 공유 타겟으로 받은 텍스트에서 URL 을 추출해 PC 대기열에 추가.</summary>
private async Task HandleSharedUrlAsync()
{
var shared = ShareTarget.Consume();
if (shared == null || !ApiClient.Instance.IsConfigured) return;
var url = SharePayload.ExtractUrl(shared);
if (url == null) { _summary.Text = "🔗 공유받은 텍스트에 URL 이 없습니다"; return; }
_summary.Text = "🔗 공유 링크 전송 중…";
var ok = await ApiClient.Instance.AddAsync(url);
_summary.Text = ok ? $"🔗 공유 링크 전송됨: {url}" : "❌ 공유 링크 전송 실패";
}
/// <summary>U5: 완료 로컬 알림(Android 13+ 는 최초 1회 권한 요청).</summary>
private static async Task NotifyCompletionAsync(string title)
{
@ -234,14 +249,19 @@ public sealed class LibraryPage : ContentPage
if (item == null) return;
if (AppOfflineStore.Default.Exists(item.Id)) { button.Text = "✓ 저장됨"; return; }
button.Text = "0%";
var notifId = 5000 + Math.Abs(item.Id.GetHashCode() % 1000); // M2 진행 알림: 항목별 고정 id 로 갱신
try
{
var dest = AppOfflineStore.Default.PathFor(item.Id);
var progress = new Progress<double>(p => MainThread.BeginInvokeOnMainThread(
() => button.Text = p >= 1 ? "✓ 저장됨" : $"{(int)(p * 100)}%"));
var progress = new Progress<double>(p => MainThread.BeginInvokeOnMainThread(() =>
{
button.Text = p >= 1 ? "✓ 저장됨" : $"{(int)(p * 100)}%";
if (p < 1) ShowSaveProgress(notifId, item.FileName, p); // 100% 는 완료 노티가 대체(되덮어쓰기 방지)
}));
await ApiClient.Instance.DownloadFileAsync(item, dest, progress, CancellationToken.None);
AppOfflineStore.Default.Record(item.Id, item.FileName);
button.Text = "✓ 저장됨";
ShowSaveDone(notifId, item.FileName);
}
catch (Exception e)
{
@ -249,6 +269,53 @@ public sealed class LibraryPage : ContentPage
}
}
/// <summary>M2: 오프라인 저장 진행 알림(같은 id 갱신) — 5% 단위 스로틀.</summary>
private static int _lastNotifiedPercent = -1;
private static void ShowSaveProgress(int notifId, string fileName, double p)
{
var percent = (int)(p * 100);
if (p < 1 && percent / 5 == _lastNotifiedPercent) return; // 알림 스팸 방지
_lastNotifiedPercent = percent / 5;
try
{
Plugin.LocalNotification.LocalNotificationCenter.Current.Show(new Plugin.LocalNotification.NotificationRequest
{
NotificationId = notifId,
Title = "폰에 저장 중",
Description = $"{fileName} — {percent}%",
Android =
{
ChannelId = "vd_save",
Ongoing = true,
AutoCancel = false,
ProgressBar = new Plugin.LocalNotification.AndroidOption.AndroidProgressBar
{
Max = 100,
Progress = percent,
IsIndeterminate = false,
},
},
});
}
catch { }
}
private static void ShowSaveDone(int notifId, string fileName)
{
try
{
Plugin.LocalNotification.LocalNotificationCenter.Current.Show(new Plugin.LocalNotification.NotificationRequest
{
NotificationId = notifId,
Title = "폰 저장 완료",
Description = fileName,
Android = { ChannelId = "vd_save", Ongoing = false, AutoCancel = true },
});
}
catch { }
}
private async void OpenPlayer(LibraryItem item)
{
// 탭 자식엔 NavigationPage 가 없어 PushAsync 불가 → 루트에서 모달 push (닫기는 상단바 뒤로)