mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-04 00:47:00 +01:00
feat(core,app,android,cli,ios): sync the library and settings, verify a share before adding it
Sync carried only watch positions, so a second device started empty. The per-installation history file now also carries per-key registers (pstr-core::profile): every share with its link and custom password, the playback, appearance and metadata settings, the TMDB key, hand-picked matches and per-title track choices. Newer updated_at wins per key, a removal travels as an empty register, and a first sync stamps at 0 so a fresh install takes the library the other devices have instead of overwriting it with defaults. Volume, mute and the device-only settings (cache budget, Wi-Fi only, background audio, hardware decoding) stay out. The secrets live only in the end-to-end encrypted Drive file, never in shares.json. FORMAT_VERSION stays 1; an older build ignores the registers. Adding a share stored it before opening the link, so a first failure left a broken entry and every retry said "already configured". add_verified opens the link first and stores nothing on failure, and only the new share is crawled afterwards, so another broken share cannot fail the add. Android drops picture-in-picture: the window sized the video wrongly and background audio already covers leaving the app. iOS sizes the Metal drawable on every layout, so the picture no longer stretches after a rotation or a Split View resize. Settings show a save at once rather than after the match run, and a failed save reverts and says why. Both phone clients gain an activity banner naming what is running, and the add-share, metadata and change-match forms show progress and errors in place.
This commit is contained in:
1 parent
dc70cafbb7
commit
8e42906a7f
37 files changed
+2018
-594
No files matched your search
@@ -57,7 +57,7 @@ struct SharesView: View {
|
||||
}
|
||||
.sheet(isPresented: Binding(get: { showAdd || incomingLink != nil }, set: { if !$0 { showAdd = false; incomingLink = nil } })) {
|
||||
AddShareSheet(initialUrl: incomingLink ?? "") { name, url, password in
|
||||
model.addShare(name: name, url: url, password: password)
|
||||
await model.addShare(name: name, url: url, password: password)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: Binding(
|
||||
@@ -166,18 +166,29 @@ private struct RepairShareSheet: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a link. The form stays up while the link is opened, and with the
|
||||
/// reason under it when the link is refused, so fixing a typo is a retry
|
||||
/// rather than starting over.
|
||||
private struct AddShareSheet: View {
|
||||
let onAdd: (String, String, String?) -> Void
|
||||
/// Resolves to why the link was refused, or nil once it is added.
|
||||
let onAdd: (String, String, String?) async -> String?
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.scheme) private var scheme
|
||||
@State private var name = ""
|
||||
@State private var url: String
|
||||
@State private var password = ""
|
||||
@State private var adding = false
|
||||
@State private var refusal: String?
|
||||
|
||||
init(initialUrl: String, onAdd: @escaping (String, String, String?) -> Void) {
|
||||
init(initialUrl: String, onAdd: @escaping (String, String, String?) async -> String?) {
|
||||
self.onAdd = onAdd
|
||||
_url = State(initialValue: initialUrl)
|
||||
}
|
||||
|
||||
private var ready: Bool {
|
||||
!name.trimmingCharacters(in: .whitespaces).isEmpty && !url.trimmingCharacters(in: .whitespaces).isEmpty
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
DialogSheet("Add Proton Drive share") {
|
||||
Field(label: "Library name", text: $name)
|
||||
@@ -195,15 +206,29 @@ private struct AddShareSheet: View {
|
||||
.buttonStyle(.quiet)
|
||||
}
|
||||
Field(label: "Custom password (optional)", text: $password, secret: true)
|
||||
if adding {
|
||||
HStack(spacing: 12) {
|
||||
ProgressView().controlSize(.small).tint(scheme.primary)
|
||||
Text("Opening the link…")
|
||||
}
|
||||
} else if let refusal {
|
||||
Text(refusal).foregroundStyle(scheme.error)
|
||||
}
|
||||
} buttons: {
|
||||
Button("Cancel") { dismiss() }.buttonStyle(.tonal)
|
||||
Button("Add") {
|
||||
onAdd(name.trimmingCharacters(in: .whitespaces), url.trimmingCharacters(in: .whitespaces), password)
|
||||
dismiss()
|
||||
Button(adding ? "Adding…" : "Add") {
|
||||
adding = true
|
||||
refusal = nil
|
||||
Task {
|
||||
refusal = await onAdd(name.trimmingCharacters(in: .whitespaces), url.trimmingCharacters(in: .whitespaces), password)
|
||||
adding = false
|
||||
if refusal == nil { dismiss() }
|
||||
}
|
||||
}
|
||||
.buttonStyle(.accent)
|
||||
.disabled(name.trimmingCharacters(in: .whitespaces).isEmpty || url.trimmingCharacters(in: .whitespaces).isEmpty)
|
||||
.disabled(!ready || adding)
|
||||
}
|
||||
.interactiveDismissDisabled(adding)
|
||||
.secureContent()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user