Уведомления, Real-time синхронизация, фотки, reply and forward
This commit is contained in:
@@ -383,44 +383,56 @@ private struct ToolbarTitleView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Desktop parity: circular spinner + status text (Mantine `<Loader size={12}>` equivalent).
|
||||
/// Status text label without spinner (spinner is in ToolbarStoriesAvatar).
|
||||
private struct ToolbarStatusLabel: View {
|
||||
let title: String
|
||||
@State private var isSpinning = false
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 5) {
|
||||
Circle()
|
||||
.trim(from: 0.05, to: 0.75)
|
||||
.stroke(RosettaColors.Adaptive.text, style: StrokeStyle(lineWidth: 1.5, lineCap: .round))
|
||||
.frame(width: 12, height: 12)
|
||||
.rotationEffect(.degrees(isSpinning ? 360 : 0))
|
||||
.animation(.linear(duration: 1.2).repeatForever(autoreverses: false), value: isSpinning)
|
||||
|
||||
Text(title)
|
||||
.font(.system(size: 17, weight: .semibold))
|
||||
.foregroundStyle(RosettaColors.Adaptive.text)
|
||||
}
|
||||
.onAppear { isSpinning = true }
|
||||
Text(title)
|
||||
.font(.system(size: 17, weight: .semibold))
|
||||
.foregroundStyle(RosettaColors.Adaptive.text)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Toolbar Stories Avatar (observation-isolated)
|
||||
|
||||
/// Reads `AccountManager` and `SessionManager` in its own observation scope.
|
||||
/// Changes to these `@Observable` singletons only re-render this small view,
|
||||
/// not the parent ChatListView / NavigationStack.
|
||||
/// Reads `AccountManager`, `SessionManager`, and `ProtocolManager` in its own observation scope.
|
||||
/// Shows a spinning arc loader during connecting/syncing, then crossfades to avatar.
|
||||
private struct ToolbarStoriesAvatar: View {
|
||||
@State private var isSpinning = false
|
||||
|
||||
var body: some View {
|
||||
let pk = AccountManager.shared.currentAccount?.publicKey ?? ""
|
||||
let state = ProtocolManager.shared.connectionState
|
||||
let isSyncing = SessionManager.shared.syncBatchInProgress
|
||||
let isLoading = state != .authenticated || isSyncing
|
||||
|
||||
let initials = RosettaColors.initials(
|
||||
name: SessionManager.shared.displayName, publicKey: pk
|
||||
)
|
||||
let colorIdx = RosettaColors.avatarColorIndex(for: SessionManager.shared.displayName, publicKey: pk)
|
||||
// Reading avatarVersion triggers observation — re-renders when any avatar is saved/removed.
|
||||
let _ = AvatarRepository.shared.avatarVersion
|
||||
let avatar = AvatarRepository.shared.loadAvatar(publicKey: pk)
|
||||
ZStack { AvatarView(initials: initials, colorIndex: colorIdx, size: 28, image: avatar) }
|
||||
|
||||
ZStack {
|
||||
// Avatar — visible when loaded
|
||||
AvatarView(initials: initials, colorIndex: colorIdx, size: 28, image: avatar)
|
||||
.opacity(isLoading ? 0 : 1)
|
||||
|
||||
// Spinning arc loader — visible during connecting/syncing
|
||||
Circle()
|
||||
.trim(from: 0.05, to: 0.78)
|
||||
.stroke(
|
||||
RosettaColors.figmaBlue,
|
||||
style: StrokeStyle(lineWidth: 2, lineCap: .round)
|
||||
)
|
||||
.frame(width: 20, height: 20)
|
||||
.rotationEffect(.degrees(isSpinning ? 360 : 0))
|
||||
.opacity(isLoading ? 1 : 0)
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.3), value: isLoading)
|
||||
.onAppear { isSpinning = true }
|
||||
.animation(.linear(duration: 1).repeatForever(autoreverses: false), value: isSpinning)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user