Add iOS media placeholders
This commit is contained in:
@@ -61,6 +61,7 @@ public actor FakeSessionBridge: SessionBridge {
|
|||||||
senderName: "Alice",
|
senderName: "Alice",
|
||||||
text: "Hello from fake TDLib",
|
text: "Hello from fake TDLib",
|
||||||
date: Self.baseMessageDate,
|
date: Self.baseMessageDate,
|
||||||
|
media: .photo(PhotoMedia(fileId: 100, width: 1280, height: 720)),
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
isRead: false
|
isRead: false
|
||||||
)
|
)
|
||||||
@@ -72,6 +73,7 @@ public actor FakeSessionBridge: SessionBridge {
|
|||||||
senderName: "Mikhail",
|
senderName: "Mikhail",
|
||||||
text: "Bridge smoke is green",
|
text: "Bridge smoke is green",
|
||||||
date: Self.baseMessageDate + 60,
|
date: Self.baseMessageDate + 60,
|
||||||
|
media: .voice(VoiceMedia(fileId: 200, duration: 12, mimeType: "audio/ogg")),
|
||||||
isOutgoing: true
|
isOutgoing: true
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -91,12 +91,67 @@ public struct Reaction: Equatable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum MediaDownloadState: Equatable, Sendable {
|
||||||
|
case notDownloaded
|
||||||
|
case downloading
|
||||||
|
case downloaded(path: String)
|
||||||
|
case error(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PhotoMedia: Equatable, Sendable {
|
||||||
|
public var fileId: Int32
|
||||||
|
public var width: Int32
|
||||||
|
public var height: Int32
|
||||||
|
public var downloadState: MediaDownloadState
|
||||||
|
|
||||||
|
public init(
|
||||||
|
fileId: Int32,
|
||||||
|
width: Int32,
|
||||||
|
height: Int32,
|
||||||
|
downloadState: MediaDownloadState = .notDownloaded
|
||||||
|
) {
|
||||||
|
self.fileId = fileId
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.downloadState = downloadState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct VoiceMedia: Equatable, Sendable {
|
||||||
|
public var fileId: Int32
|
||||||
|
public var duration: Int32
|
||||||
|
public var mimeType: String?
|
||||||
|
public var waveform: String?
|
||||||
|
public var downloadState: MediaDownloadState
|
||||||
|
|
||||||
|
public init(
|
||||||
|
fileId: Int32,
|
||||||
|
duration: Int32,
|
||||||
|
mimeType: String? = nil,
|
||||||
|
waveform: String? = nil,
|
||||||
|
downloadState: MediaDownloadState = .notDownloaded
|
||||||
|
) {
|
||||||
|
self.fileId = fileId
|
||||||
|
self.duration = duration
|
||||||
|
self.mimeType = mimeType
|
||||||
|
self.waveform = waveform
|
||||||
|
self.downloadState = downloadState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MessageMedia: Equatable, Sendable {
|
||||||
|
case photo(PhotoMedia)
|
||||||
|
case voice(VoiceMedia)
|
||||||
|
}
|
||||||
|
|
||||||
public struct Message: Identifiable, Equatable, Sendable {
|
public struct Message: Identifiable, Equatable, Sendable {
|
||||||
public var id: Int64
|
public var id: Int64
|
||||||
public var chatId: Int64
|
public var chatId: Int64
|
||||||
public var senderName: String
|
public var senderName: String
|
||||||
public var text: String
|
public var text: String
|
||||||
public var date: Int32
|
public var date: Int32
|
||||||
|
public var mediaAlbumId: Int64?
|
||||||
|
public var media: MessageMedia?
|
||||||
public var isOutgoing: Bool
|
public var isOutgoing: Bool
|
||||||
public var isRead: Bool
|
public var isRead: Bool
|
||||||
public var editDate: Int32?
|
public var editDate: Int32?
|
||||||
@@ -110,6 +165,8 @@ public struct Message: Identifiable, Equatable, Sendable {
|
|||||||
senderName: String,
|
senderName: String,
|
||||||
text: String,
|
text: String,
|
||||||
date: Int32 = 0,
|
date: Int32 = 0,
|
||||||
|
mediaAlbumId: Int64? = nil,
|
||||||
|
media: MessageMedia? = nil,
|
||||||
isOutgoing: Bool,
|
isOutgoing: Bool,
|
||||||
isRead: Bool = true,
|
isRead: Bool = true,
|
||||||
editDate: Int32? = nil,
|
editDate: Int32? = nil,
|
||||||
@@ -122,6 +179,8 @@ public struct Message: Identifiable, Equatable, Sendable {
|
|||||||
self.senderName = senderName
|
self.senderName = senderName
|
||||||
self.text = text
|
self.text = text
|
||||||
self.date = date
|
self.date = date
|
||||||
|
self.mediaAlbumId = mediaAlbumId
|
||||||
|
self.media = media
|
||||||
self.isOutgoing = isOutgoing
|
self.isOutgoing = isOutgoing
|
||||||
self.isRead = isRead
|
self.isRead = isRead
|
||||||
self.editDate = editDate
|
self.editDate = editDate
|
||||||
|
|||||||
@@ -199,6 +199,41 @@ public actor UniFfiSessionBridge: SessionBridge {
|
|||||||
Reaction(emoji: reaction.emoji, count: reaction.count, isChosen: reaction.isChosen)
|
Reaction(emoji: reaction.emoji, count: reaction.count, isChosen: reaction.isChosen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func mapDownloadState(_ state: IosDownloadState) -> MediaDownloadState {
|
||||||
|
switch state {
|
||||||
|
case .notDownloaded:
|
||||||
|
.notDownloaded
|
||||||
|
case .downloading:
|
||||||
|
.downloading
|
||||||
|
case let .downloaded(path):
|
||||||
|
.downloaded(path: path)
|
||||||
|
case let .error(message):
|
||||||
|
.error(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func mapMedia(_ media: IosMedia) -> MessageMedia? {
|
||||||
|
switch media.kind {
|
||||||
|
case "photo":
|
||||||
|
.photo(PhotoMedia(
|
||||||
|
fileId: media.fileId,
|
||||||
|
width: media.width ?? 0,
|
||||||
|
height: media.height ?? 0,
|
||||||
|
downloadState: mapDownloadState(media.downloadState)
|
||||||
|
))
|
||||||
|
case "voice":
|
||||||
|
.voice(VoiceMedia(
|
||||||
|
fileId: media.fileId,
|
||||||
|
duration: media.duration ?? 0,
|
||||||
|
mimeType: media.mimeType,
|
||||||
|
waveform: media.waveform,
|
||||||
|
downloadState: mapDownloadState(media.downloadState)
|
||||||
|
))
|
||||||
|
default:
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func mapMessage(_ message: IosMessage, chatId: Int64) -> Message {
|
private static func mapMessage(_ message: IosMessage, chatId: Int64) -> Message {
|
||||||
Message(
|
Message(
|
||||||
id: message.id,
|
id: message.id,
|
||||||
@@ -206,6 +241,8 @@ public actor UniFfiSessionBridge: SessionBridge {
|
|||||||
senderName: message.senderName,
|
senderName: message.senderName,
|
||||||
text: message.text,
|
text: message.text,
|
||||||
date: message.date,
|
date: message.date,
|
||||||
|
mediaAlbumId: message.mediaAlbumId,
|
||||||
|
media: message.media.flatMap(mapMedia),
|
||||||
isOutgoing: message.isOutgoing,
|
isOutgoing: message.isOutgoing,
|
||||||
isRead: message.isRead,
|
isRead: message.isRead,
|
||||||
editDate: message.editDate,
|
editDate: message.editDate,
|
||||||
|
|||||||
@@ -657,6 +657,9 @@ public struct MessageRow: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
if let media = message.media {
|
||||||
|
MediaPlaceholderView(media: media, mediaAlbumId: message.mediaAlbumId)
|
||||||
|
}
|
||||||
Text(renderedText)
|
Text(renderedText)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
if !message.reactions.isEmpty {
|
if !message.reactions.isEmpty {
|
||||||
@@ -706,6 +709,81 @@ public struct MessageRow: View {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct MediaPlaceholderView: View {
|
||||||
|
public var media: MessageMedia
|
||||||
|
public var mediaAlbumId: Int64?
|
||||||
|
|
||||||
|
public init(media: MessageMedia, mediaAlbumId: Int64? = nil) {
|
||||||
|
self.media = media
|
||||||
|
self.mediaAlbumId = mediaAlbumId
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Image(systemName: iconName)
|
||||||
|
.frame(width: 22, height: 22)
|
||||||
|
.foregroundStyle(.blue)
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Text(title)
|
||||||
|
.font(.subheadline)
|
||||||
|
if mediaAlbumId != nil {
|
||||||
|
Image(systemName: "square.stack.3d.up.fill")
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(detail)
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
Spacer(minLength: 0)
|
||||||
|
}
|
||||||
|
.padding(8)
|
||||||
|
.background(Color.gray.opacity(0.10), in: RoundedRectangle(cornerRadius: 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
private var iconName: String {
|
||||||
|
switch media {
|
||||||
|
case .photo:
|
||||||
|
"photo"
|
||||||
|
case .voice:
|
||||||
|
"waveform"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var title: String {
|
||||||
|
switch media {
|
||||||
|
case .photo:
|
||||||
|
"Photo"
|
||||||
|
case .voice:
|
||||||
|
"Voice"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var detail: String {
|
||||||
|
switch media {
|
||||||
|
case let .photo(photo):
|
||||||
|
"\(photo.width)x\(photo.height) · \(downloadLabel(photo.downloadState))"
|
||||||
|
case let .voice(voice):
|
||||||
|
"\(voice.duration)s · \(downloadLabel(voice.downloadState))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func downloadLabel(_ state: MediaDownloadState) -> String {
|
||||||
|
switch state {
|
||||||
|
case .notDownloaded:
|
||||||
|
"not downloaded"
|
||||||
|
case .downloading:
|
||||||
|
"downloading"
|
||||||
|
case .downloaded:
|
||||||
|
"downloaded"
|
||||||
|
case .error:
|
||||||
|
"error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct ComposeBar: View {
|
public struct ComposeBar: View {
|
||||||
@Binding public var text: String
|
@Binding public var text: String
|
||||||
public var replyTo: Message?
|
public var replyTo: Message?
|
||||||
|
|||||||
@@ -70,6 +70,13 @@ struct TeleTuiIOSSmokeTests {
|
|||||||
precondition(viewModel.messages.count == 1)
|
precondition(viewModel.messages.count == 1)
|
||||||
precondition(viewModel.messages[0].date == 1_700_000_000)
|
precondition(viewModel.messages[0].date == 1_700_000_000)
|
||||||
precondition(viewModel.pinnedMessages.map(\.id) == [1])
|
precondition(viewModel.pinnedMessages.map(\.id) == [1])
|
||||||
|
if case let .photo(photo) = viewModel.messages[0].media {
|
||||||
|
precondition(photo.fileId == 100)
|
||||||
|
precondition(photo.width == 1280)
|
||||||
|
precondition(photo.height == 720)
|
||||||
|
} else {
|
||||||
|
preconditionFailure("fake saved message should contain photo media")
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.composeText = "Hi from SwiftUI"
|
viewModel.composeText = "Hi from SwiftUI"
|
||||||
await viewModel.send()
|
await viewModel.send()
|
||||||
|
|||||||
Reference in New Issue
Block a user