mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +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
@@ -99,8 +99,12 @@ struct RootView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
Snackbar()
|
||||
.padding(.bottom, (wide ? 16 : 72) + (host.isOpen && host.minimized ? miniTransportInset : 0))
|
||||
VStack(spacing: 8) {
|
||||
ActivityBanner()
|
||||
Snackbar()
|
||||
}
|
||||
.padding(.bottom, (wide ? 16 : 72) + (host.isOpen && host.minimized ? miniTransportInset : 0))
|
||||
.animation(.easeInOut(duration: 0.2), value: model.activity)
|
||||
}
|
||||
.background(scheme.background.ignoresSafeArea())
|
||||
}
|
||||
@@ -140,6 +144,7 @@ struct RootView: View {
|
||||
model.syncInBackground()
|
||||
case .active:
|
||||
host.enteredForeground()
|
||||
model.enteredForeground()
|
||||
DownloadCoordinator.shared.resumeQueued()
|
||||
default:
|
||||
break
|
||||
@@ -301,6 +306,33 @@ private struct NavigationRail: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// What the app is busy with, for as long as it is: a spinner and one line.
|
||||
/// Not dismissible and not timed — it goes when the work does.
|
||||
private struct ActivityBanner: View {
|
||||
@Environment(AppModel.self) private var model
|
||||
@Environment(\.scheme) private var scheme
|
||||
|
||||
var body: some View {
|
||||
if let activity = model.activity {
|
||||
HStack(spacing: 12) {
|
||||
ProgressView().controlSize(.small).tint(scheme.primary)
|
||||
Text(activity)
|
||||
.textStyle(.bodyMedium)
|
||||
.foregroundStyle(scheme.onSurface)
|
||||
.lineLimit(2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 12)
|
||||
.background(scheme.surfaceContainerHighest, in: RoundedRectangle(cornerRadius: Corner.tight))
|
||||
.padding(.horizontal, 12)
|
||||
.transition(.move(edge: .bottom).combined(with: .opacity))
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityAddTraits(.updatesFrequently)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The app's one line of feedback: what just happened, with Undo where it can
|
||||
/// be taken back.
|
||||
private struct Snackbar: View {
|
||||
|
||||
Reference in new issue
Block a user