Change details view
This commit is contained in:
parent
89b2bdee98
commit
8bb6b1d985
|
@ -15,7 +15,6 @@
|
|||
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048092961EA1900E6868A /* AttachmentDataHandler.swift */; };
|
||||
F8341F90295C636C009C8EE6 /* UIImage+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */; };
|
||||
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F91295C63BB009C8EE6 /* ImageStatus.swift */; };
|
||||
F83901A4295D864D00456AE2 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83901A3295D864D00456AE2 /* Tag.swift */; };
|
||||
F83901A6295D8EC000456AE2 /* LabelIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83901A5295D8EC000456AE2 /* LabelIcon.swift */; };
|
||||
F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D4970296402DC00751DF7 /* AuthorizationService.swift */; };
|
||||
F85D4973296406E700751DF7 /* BottomRight.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D4972296406E700751DF7 /* BottomRight.swift */; };
|
||||
|
@ -64,7 +63,6 @@
|
|||
F80048092961EA1900E6868A /* AttachmentDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentDataHandler.swift; sourceTree = "<group>"; };
|
||||
F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Exif.swift"; sourceTree = "<group>"; };
|
||||
F8341F91295C63BB009C8EE6 /* ImageStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageStatus.swift; sourceTree = "<group>"; };
|
||||
F83901A3295D864D00456AE2 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
||||
F83901A5295D8EC000456AE2 /* LabelIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelIcon.swift; sourceTree = "<group>"; };
|
||||
F85D4970296402DC00751DF7 /* AuthorizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationService.swift; sourceTree = "<group>"; };
|
||||
F85D4972296406E700751DF7 /* BottomRight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomRight.swift; sourceTree = "<group>"; };
|
||||
|
@ -184,7 +182,6 @@
|
|||
F83901A2295D863B00456AE2 /* Widgets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F83901A3295D864D00456AE2 /* Tag.swift */,
|
||||
F83901A5295D8EC000456AE2 /* LabelIcon.swift */,
|
||||
F85D4972296406E700751DF7 /* BottomRight.swift */,
|
||||
F85D497629640A5200751DF7 /* ImageRow.swift */,
|
||||
|
@ -360,7 +357,6 @@
|
|||
F866F6A729604629002E8F88 /* SignInView.swift in Sources */,
|
||||
F88C246C295C37B80006098B /* VernissageApp.swift in Sources */,
|
||||
F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */,
|
||||
F83901A4295D864D00456AE2 /* Tag.swift in Sources */,
|
||||
F88FAD25295F3FF7009B20C9 /* FederatedFeedView.swift in Sources */,
|
||||
F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */,
|
||||
F88FAD27295F400E009B20C9 /* NotificationsView.swift in Sources */,
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "0.561",
|
||||
"red" : "0.000"
|
||||
"blue" : "247",
|
||||
"green" : "131",
|
||||
"red" : "52"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "0.643",
|
||||
"red" : "0.000"
|
||||
"blue" : "248",
|
||||
"green" : "167",
|
||||
"red" : "74"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "240",
|
||||
"green" : "240",
|
||||
"red" : "240"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "30",
|
||||
"green" : "30",
|
||||
"red" : "30"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0",
|
||||
"green" : "0",
|
||||
"red" : "0"
|
||||
"blue" : "10",
|
||||
"green" : "10",
|
||||
"red" : "10"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "255",
|
||||
"red" : "255"
|
||||
"blue" : "245",
|
||||
"green" : "245",
|
||||
"red" : "245"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -67,38 +67,45 @@ extension String {
|
|||
case emailTimePreview
|
||||
|
||||
var stringFormat:String {
|
||||
switch self {
|
||||
//handle iso Time
|
||||
case .birthDateFormatOne: return "dd/MM/YYYY"
|
||||
case .birthDateFormatTwo: return "dd-MM-YYYY"
|
||||
case .isoYear: return "yyyy"
|
||||
case .isoYearMonth: return "yyyy-MM"
|
||||
case .isoDate: return "yyyy-MM-dd"
|
||||
case .isoDateTime: return "yyyy-MM-dd'T'HH:mmZ"
|
||||
case .isoDateTimeSec: return "yyyy-MM-dd'T'HH:mm:ssZ"
|
||||
case .isoDateTimeMilliSec: return "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
|
||||
case .dotNet: return "/Date(%d%f)/"
|
||||
case .rss: return "EEE, d MMM yyyy HH:mm:ss ZZZ"
|
||||
case .altRSS: return "d MMM yyyy HH:mm:ss ZZZ"
|
||||
case .httpHeader: return "EEE, dd MM yyyy HH:mm:ss ZZZ"
|
||||
case .standard: return "EEE MMM dd HH:mm:ss Z yyyy"
|
||||
case .custom(let customFormat): return customFormat
|
||||
|
||||
//handle local Time
|
||||
case .localDateTimeSec: return "yyyy-MM-dd HH:mm:ss"
|
||||
case .localTimeWithNoon: return "hh:mm a"
|
||||
case .localDate: return "yyyy-MM-dd"
|
||||
case .localPhotoSave: return "yyyyMMddHHmmss"
|
||||
case .messageRTetriveFormat: return "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
|
||||
case .emailTimePreview: return "dd MMM yyyy, h:mm a"
|
||||
}
|
||||
switch self {
|
||||
//handle iso Time
|
||||
case .birthDateFormatOne: return "dd/MM/YYYY"
|
||||
case .birthDateFormatTwo: return "dd-MM-YYYY"
|
||||
case .isoYear: return "yyyy"
|
||||
case .isoYearMonth: return "yyyy-MM"
|
||||
case .isoDate: return "yyyy-MM-dd"
|
||||
case .isoDateTime: return "yyyy-MM-dd'T'HH:mmZ"
|
||||
case .isoDateTimeSec: return "yyyy-MM-dd'T'HH:mm:ssZ"
|
||||
case .isoDateTimeMilliSec: return "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
|
||||
case .dotNet: return "/Date(%d%f)/"
|
||||
case .rss: return "EEE, d MMM yyyy HH:mm:ss ZZZ"
|
||||
case .altRSS: return "d MMM yyyy HH:mm:ss ZZZ"
|
||||
case .httpHeader: return "EEE, dd MM yyyy HH:mm:ss ZZZ"
|
||||
case .standard: return "EEE MMM dd HH:mm:ss Z yyyy"
|
||||
case .custom(let customFormat): return customFormat
|
||||
|
||||
//handle local Time
|
||||
case .localDateTimeSec: return "yyyy-MM-dd HH:mm:ss"
|
||||
case .localTimeWithNoon: return "hh:mm a"
|
||||
case .localDate: return "yyyy-MM-dd"
|
||||
case .localPhotoSave: return "yyyyMMddHHmmss"
|
||||
case .messageRTetriveFormat: return "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
|
||||
case .emailTimePreview: return "dd MMM yyyy, h:mm a"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toDate(_ format: DateFormatType = .isoDate) -> Date?{
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = format.stringFormat
|
||||
let date = dateFormatter.date(from: self)
|
||||
return date
|
||||
}
|
||||
func toDate(_ format: DateFormatType = .isoDate) -> Date? {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = format.stringFormat
|
||||
let date = dateFormatter.date(from: self)
|
||||
return date
|
||||
}
|
||||
|
||||
func toRelative(_ format: DateFormatType = .isoDate) -> String {
|
||||
let formatter = RelativeDateTimeFormatter()
|
||||
let date = self.toDate(format) ?? Date()
|
||||
|
||||
return formatter.localizedString(for: date, relativeTo: Date.now)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ struct HTMLFormattedText: UIViewRepresentable {
|
|||
textView.isUserInteractionEnabled = false
|
||||
textView.translatesAutoresizingMaskIntoConstraints = false
|
||||
textView.isScrollEnabled = false
|
||||
textView.backgroundColor = UIColor(Color.clear)
|
||||
|
||||
return textView
|
||||
}
|
||||
|
@ -47,12 +48,12 @@ struct HTMLFormattedText: UIViewRepresentable {
|
|||
|
||||
let largeAttributes = [
|
||||
NSAttributedString.Key.font: UIFont.systemFont(ofSize: CGFloat(self.fontSize)),
|
||||
NSAttributedString.Key.foregroundColor: UIColor(Color("mainTextColor"))
|
||||
NSAttributedString.Key.foregroundColor: UIColor(Color("MainTextColor"))
|
||||
]
|
||||
|
||||
let linkAttributes = [
|
||||
NSAttributedString.Key.font: UIFont.systemFont(ofSize: CGFloat(self.fontSize)),
|
||||
NSAttributedString.Key.foregroundColor: UIColor(Color("AccentColor"))
|
||||
NSAttributedString.Key.foregroundColor: UIColor(Color.accentColor)
|
||||
]
|
||||
|
||||
if let attributedString = try? NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
|
||||
|
|
|
@ -37,6 +37,34 @@ public class TimelineService {
|
|||
try await self.loadData(for: accountData, on: backgroundContext, minId: newestStatus?.id)
|
||||
}
|
||||
|
||||
public func getStatus(withId statusId: String, and accountData: AccountData) async throws -> Status? {
|
||||
guard let accessToken = accountData.accessToken else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
||||
return try await client.read(statusId: statusId)
|
||||
}
|
||||
|
||||
public func updateStatus(statusData: StatusData, and accountData: AccountData) async throws -> StatusData? {
|
||||
guard let accessToken = accountData.accessToken else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load data from API and operate on CoreData on background context.
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
|
||||
// Get new information from API.
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
||||
let status = try await client.read(statusId: statusData.id)
|
||||
|
||||
// Update status data in database.
|
||||
try await self.updateStatusData(from: status, to: statusData, on: backgroundContext)
|
||||
try backgroundContext.save()
|
||||
|
||||
return statusData
|
||||
}
|
||||
|
||||
public func getComments(for statusId: String, and accountData: AccountData) async throws -> Context {
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accountData.accessToken ?? "")
|
||||
return try await client.getContext(for: statusId)
|
||||
|
@ -46,83 +74,90 @@ public class TimelineService {
|
|||
guard let accessToken = accountData.accessToken else {
|
||||
return
|
||||
}
|
||||
|
||||
// Get maximimum downloaded stauts id.
|
||||
let attachmentDataHandler = AttachmentDataHandler()
|
||||
let statusDataHandler = StatusDataHandler()
|
||||
|
||||
|
||||
// Retrieve statuses from API.
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
||||
let statuses = try await client.getHomeTimeline(maxId: maxId, minId: minId, limit: 40)
|
||||
|
||||
// Download status images and save it into database.
|
||||
// Create handler for managing statuses in database.
|
||||
let statusDataHandler = StatusDataHandler()
|
||||
|
||||
// Save status data in database.
|
||||
for status in statuses {
|
||||
|
||||
// Save status data in database.
|
||||
let statusDataEntity = statusDataHandler.createStatusDataEntity(viewContext: backgroundContext)
|
||||
statusDataEntity.accountAvatar = status.account?.avatar
|
||||
statusDataEntity.accountDisplayName = status.account?.displayName
|
||||
statusDataEntity.accountId = status.account!.id
|
||||
statusDataEntity.accountUsername = status.account!.username
|
||||
statusDataEntity.applicationName = status.application?.name
|
||||
statusDataEntity.applicationWebsite = status.application?.website
|
||||
statusDataEntity.bookmarked = status.bookmarked
|
||||
statusDataEntity.content = status.content
|
||||
statusDataEntity.createdAt = status.createdAt
|
||||
statusDataEntity.favourited = status.favourited
|
||||
statusDataEntity.favouritesCount = Int32(status.favouritesCount)
|
||||
statusDataEntity.id = status.id
|
||||
statusDataEntity.inReplyToAccount = status.inReplyToAccount
|
||||
statusDataEntity.inReplyToId = status.inReplyToId
|
||||
statusDataEntity.muted = status.muted
|
||||
statusDataEntity.pinned = status.pinned
|
||||
statusDataEntity.reblogged = status.reblogged
|
||||
statusDataEntity.reblogsCount = Int32(status.reblogsCount)
|
||||
statusDataEntity.repliesCount = Int32(status.repliesCount)
|
||||
statusDataEntity.sensitive = status.sensitive
|
||||
statusDataEntity.spoilerText = status.spoilerText
|
||||
statusDataEntity.uri = status.uri
|
||||
statusDataEntity.url = status.url
|
||||
statusDataEntity.visibility = status.visibility.rawValue
|
||||
|
||||
for attachment in status.mediaAttachments {
|
||||
let imageData = try await self.fetchImage(attachment: attachment)
|
||||
|
||||
guard let imageData = imageData else {
|
||||
continue
|
||||
}
|
||||
|
||||
/*
|
||||
var exif = image.getExifData()
|
||||
if let dict = exif as? [String: AnyObject] {
|
||||
dict.keys.map { key in
|
||||
print(key)
|
||||
print(dict[key])
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Save attachment in database.
|
||||
let attachmentData = attachmentDataHandler.createAttachmnentDataEntity(viewContext: backgroundContext)
|
||||
attachmentData.id = attachment.id
|
||||
attachmentData.url = attachment.url
|
||||
attachmentData.blurhash = attachment.blurhash
|
||||
attachmentData.previewUrl = attachment.previewUrl
|
||||
attachmentData.remoteUrl = attachment.remoteUrl
|
||||
attachmentData.text = attachment.description
|
||||
attachmentData.type = attachment.type.rawValue
|
||||
|
||||
attachmentData.statusId = statusDataEntity.id
|
||||
attachmentData.data = imageData
|
||||
|
||||
attachmentData.statusRelation = statusDataEntity
|
||||
statusDataEntity.addToAttachmentRelation(attachmentData)
|
||||
}
|
||||
let statusData = statusDataHandler.createStatusDataEntity(viewContext: backgroundContext)
|
||||
try await self.updateStatusData(from: status, to: statusData, on: backgroundContext)
|
||||
}
|
||||
|
||||
try backgroundContext.save()
|
||||
}
|
||||
|
||||
private func updateStatusData(from status: Status, to statusData: StatusData, on backgroundContext: NSManagedObjectContext) async throws {
|
||||
statusData.id = status.id
|
||||
statusData.createdAt = status.createdAt
|
||||
statusData.accountAvatar = status.account?.avatar
|
||||
statusData.accountDisplayName = status.account?.displayName
|
||||
statusData.accountId = status.account!.id
|
||||
statusData.accountUsername = status.account!.username
|
||||
statusData.applicationName = status.application?.name
|
||||
statusData.applicationWebsite = status.application?.website
|
||||
statusData.bookmarked = status.bookmarked
|
||||
statusData.content = status.content
|
||||
statusData.favourited = status.favourited
|
||||
statusData.favouritesCount = Int32(status.favouritesCount)
|
||||
statusData.inReplyToAccount = status.inReplyToAccount
|
||||
statusData.inReplyToId = status.inReplyToId
|
||||
statusData.muted = status.muted
|
||||
statusData.pinned = status.pinned
|
||||
statusData.reblogged = status.reblogged
|
||||
statusData.reblogsCount = Int32(status.reblogsCount)
|
||||
statusData.repliesCount = Int32(status.repliesCount)
|
||||
statusData.sensitive = status.sensitive
|
||||
statusData.spoilerText = status.spoilerText
|
||||
statusData.uri = status.uri
|
||||
statusData.url = status.url
|
||||
statusData.visibility = status.visibility.rawValue
|
||||
|
||||
let attachmentDataHandler = AttachmentDataHandler()
|
||||
|
||||
for attachment in status.mediaAttachments {
|
||||
let imageData = try await self.fetchImage(attachment: attachment)
|
||||
|
||||
guard let imageData = imageData else {
|
||||
continue
|
||||
}
|
||||
|
||||
/*
|
||||
var exif = image.getExifData()
|
||||
if let dict = exif as? [String: AnyObject] {
|
||||
dict.keys.map { key in
|
||||
print(key)
|
||||
print(dict[key])
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Save attachment in database.
|
||||
let attachmentData = statusData.attachments().first { item in item.id == attachment.id }
|
||||
?? attachmentDataHandler.createAttachmnentDataEntity(viewContext: backgroundContext)
|
||||
|
||||
attachmentData.id = attachment.id
|
||||
attachmentData.url = attachment.url
|
||||
attachmentData.blurhash = attachment.blurhash
|
||||
attachmentData.previewUrl = attachment.previewUrl
|
||||
attachmentData.remoteUrl = attachment.remoteUrl
|
||||
attachmentData.text = attachment.description
|
||||
attachmentData.type = attachment.type.rawValue
|
||||
|
||||
attachmentData.statusId = statusData.id
|
||||
attachmentData.data = imageData
|
||||
|
||||
if attachmentData.isInserted {
|
||||
attachmentData.statusRelation = statusData
|
||||
statusData.addToAttachmentRelation(attachmentData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchImage(attachment: Attachment) async throws -> Data? {
|
||||
guard let data = try await RemoteFileService.shared.fetchData(url: attachment.url) else {
|
||||
return nil
|
||||
|
|
|
@ -9,7 +9,8 @@ import MastodonSwift
|
|||
import AVFoundation
|
||||
|
||||
struct DetailsView: View {
|
||||
@State public var statusData: StatusData
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
@ObservedObject public var statusData: StatusData
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
|
@ -18,6 +19,7 @@ struct DetailsView: View {
|
|||
|
||||
VStack(alignment: .leading) {
|
||||
UsernameRow(statusData: statusData)
|
||||
|
||||
HTMLFormattedText(statusData.content)
|
||||
.padding(.leading, -4)
|
||||
|
||||
|
@ -27,44 +29,44 @@ struct DetailsView: View {
|
|||
LabelIcon(iconName: "timelapse", value: "24.0 mm, f/1.8, 1/640s, ISO 100")
|
||||
LabelIcon(iconName: "calendar", value: "2 Oct 2022")
|
||||
}
|
||||
.foregroundColor(Color("lightGrayColor"))
|
||||
.foregroundColor(Color("LightGrayColor"))
|
||||
|
||||
HStack {
|
||||
Text("Uploaded")
|
||||
Text(statusData.createdAt.toDate(.isoDateTimeMilliSec) ?? Date(), style: .relative)
|
||||
Text(statusData.createdAt.toRelative(.isoDateTimeMilliSec))
|
||||
.padding(.horizontal, -4)
|
||||
Text("ago")
|
||||
if let applicationName = statusData.applicationName {
|
||||
Text("via \(applicationName)")
|
||||
.padding(.horizontal, -4)
|
||||
}
|
||||
}
|
||||
.foregroundColor(Color("lightGrayColor"))
|
||||
.foregroundColor(Color("LightGrayColor"))
|
||||
.font(.footnote)
|
||||
|
||||
InteractionRow(statusData: statusData)
|
||||
.padding(8)
|
||||
}
|
||||
.padding(8)
|
||||
|
||||
if statusData.repliesCount > 0 {
|
||||
HStack (alignment: .center) {
|
||||
Image(systemName: "message")
|
||||
.padding(.leading, 8)
|
||||
.padding(.vertical, 8)
|
||||
Text("\(statusData.repliesCount) replies")
|
||||
Spacer()
|
||||
}
|
||||
.font(.footnote)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color("mainTextColor").opacity(0.05))
|
||||
.foregroundColor(Color("lightGrayColor"))
|
||||
|
||||
CommentsSection(statusId: statusData.id)
|
||||
}
|
||||
Rectangle()
|
||||
.size(width: UIScreen.main.bounds.width, height: 4)
|
||||
.fill(Color("MainTextColor"))
|
||||
.opacity(0.1)
|
||||
|
||||
CommentsSection(statusId: statusData.id)
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Details")
|
||||
.onAppear {
|
||||
Task {
|
||||
do {
|
||||
if let accountData = self.applicationState.accountData {
|
||||
let timelineService = TimelineService()
|
||||
_ = try await timelineService.updateStatus(statusData: self.statusData, and: accountData)
|
||||
}
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ struct MainView: View {
|
|||
.font(.subheadline)
|
||||
}
|
||||
.frame(width: 150)
|
||||
.foregroundColor(Color("mainTextColor"))
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ struct MainView: View {
|
|||
Text(self.applicationState.accountData?.displayName ?? self.applicationState.accountData?.username ?? "")
|
||||
Image(systemName: "person.circle.fill")
|
||||
.resizable()
|
||||
.foregroundColor(Color("mainTextColor"))
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,7 @@ struct MainView: View {
|
|||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.frame(width: 32.0, height: 32.0)
|
||||
.foregroundColor(Color("mainTextColor"))
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ struct CommentsSection: View {
|
|||
|
||||
@State public var statusId: String
|
||||
@State public var withDivider = true
|
||||
@State private var context: Context?
|
||||
|
||||
@State private var context: Context?
|
||||
|
||||
private let contentWidth = Int(UIScreen.main.bounds.width) - 50
|
||||
|
||||
var body: some View {
|
||||
|
@ -29,42 +29,87 @@ struct CommentsSection: View {
|
|||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.foregroundColor(Color("mainTextColor"))
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
}
|
||||
.frame(width: 32.0, height: 32.0)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
HStack (alignment: .top) {
|
||||
Text(status.account?.displayName ?? status.account?.username ?? "")
|
||||
.foregroundColor(Color("displayNameColor"))
|
||||
.foregroundColor(Color("DisplayNameColor"))
|
||||
.font(.footnote)
|
||||
.fontWeight(.bold)
|
||||
Text("@\(status.account?.username ?? "")")
|
||||
.foregroundColor(Color("lightGrayColor"))
|
||||
.foregroundColor(Color("LightGrayColor"))
|
||||
.font(.footnote)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(status.createdAt.toRelative(.isoDateTimeMilliSec))
|
||||
.foregroundColor(Color("LightGrayColor").opacity(0.5))
|
||||
.font(.footnote)
|
||||
|
||||
/*
|
||||
Image(systemName: "message")
|
||||
.foregroundColor(Color.accentColor)
|
||||
Image(systemName: "hand.thumbsup")
|
||||
.foregroundColor(Color.accentColor)
|
||||
*/
|
||||
}
|
||||
.padding(.bottom, -10)
|
||||
|
||||
|
||||
|
||||
HTMLFormattedText(status.content, withFontSize: 14, andWidth: contentWidth)
|
||||
.padding(.leading, -4)
|
||||
|
||||
if status.mediaAttachments.count > 0 {
|
||||
LazyVGrid(columns: Array(repeating: .init(.flexible()), count: status.mediaAttachments.count == 1 ? 1 : 2), alignment: .center, spacing: 4) {
|
||||
ForEach(status.mediaAttachments, id: \.id) { attachment in
|
||||
AsyncImage(url: status.mediaAttachments[0].url) { image in
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.frame(height: status.mediaAttachments.count == 1 ? 200 : 100)
|
||||
.cornerRadius(10)
|
||||
.shadow(color: Color("MainTextColor").opacity(0.3), radius: 2)
|
||||
} placeholder: {
|
||||
Image(systemName: "photo")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.frame(height: status.mediaAttachments.count == 1 ? 200 : 100)
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
.opacity(0.05)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 8)
|
||||
|
||||
|
||||
CommentsSection(statusId: status.id, withDivider: false)
|
||||
|
||||
if withDivider {
|
||||
Rectangle()
|
||||
.size(width: UIScreen.main.bounds.width, height: 4)
|
||||
.fill(Color("mainTextColor"))
|
||||
.opacity(0.05)
|
||||
.fill(Color("MainTextColor"))
|
||||
.opacity(0.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.task {
|
||||
}
|
||||
.task {
|
||||
do {
|
||||
if let accountData = applicationState.accountData {
|
||||
self.context = try await TimelineService.shared.getComments(for: statusId, and: accountData)
|
||||
self.context = try await TimelineService.shared.getComments(
|
||||
for: statusId,
|
||||
and: accountData)
|
||||
}
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
|
|
|
@ -7,38 +7,63 @@
|
|||
import SwiftUI
|
||||
|
||||
struct InteractionRow: View {
|
||||
@State public var statusData: StatusData
|
||||
@ObservedObject public var statusData: StatusData
|
||||
|
||||
var body: some View {
|
||||
HStack (alignment: .top) {
|
||||
Tag {
|
||||
// Favorite
|
||||
} content: {
|
||||
HStack {
|
||||
Image(systemName: statusData.favourited ? "heart.fill" : "heart")
|
||||
Text("\(statusData.favouritesCount) likes")
|
||||
}
|
||||
}
|
||||
|
||||
Tag {
|
||||
// Reboost
|
||||
} content: {
|
||||
HStack {
|
||||
Image(systemName: statusData.reblogged ? "arrowshape.turn.up.forward.fill" : "arrowshape.turn.up.forward")
|
||||
Text("\(statusData.reblogsCount) boosts")
|
||||
Button {
|
||||
// Reply
|
||||
} label: {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: "message")
|
||||
Text("\(statusData.repliesCount)")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Tag {
|
||||
Button {
|
||||
// Reboost
|
||||
} label: {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: statusData.reblogged ? "arrowshape.turn.up.forward.fill" : "arrowshape.turn.up.forward")
|
||||
Text("\(statusData.reblogsCount)")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
// Favorite
|
||||
} label: {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: statusData.favourited ? "hand.thumbsup.fill" : "hand.thumbsup")
|
||||
Text("\(statusData.favouritesCount)")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
// Bookmark
|
||||
} content: {
|
||||
} label: {
|
||||
Image(systemName: statusData.bookmarked ? "bookmark.fill" : "bookmark")
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
// Share
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
}
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color("mainTextColor"))
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(Color.accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,13 +11,13 @@ struct LabelIcon: View {
|
|||
let value: String
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: iconName)
|
||||
.frame(width: 36)
|
||||
.frame(width: 30, alignment: .leading)
|
||||
Text(value)
|
||||
.font(.footnote)
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
.padding(.vertical, 2)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2022 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct Tag<Content: View>: View {
|
||||
let content: Content
|
||||
let action: () -> Void
|
||||
|
||||
init(action: @escaping () -> Void, @ViewBuilder content: () -> Content) {
|
||||
self.content = content()
|
||||
self.action = action
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
self.action()
|
||||
} label: {
|
||||
content
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(Color("actionButtonColor"))
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TagView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Tag {
|
||||
|
||||
} content: {
|
||||
HStack {
|
||||
Image(systemName: "arrow.2.squarepath")
|
||||
Text("7 boosts")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
import SwiftUI
|
||||
|
||||
struct UsernameRow: View {
|
||||
@State public var statusData: StatusData
|
||||
@ObservedObject public var statusData: StatusData
|
||||
|
||||
var body: some View {
|
||||
HStack (alignment: .center) {
|
||||
|
@ -19,15 +19,15 @@ struct UsernameRow: View {
|
|||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.foregroundColor(Color("mainTextColor"))
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
}
|
||||
.frame(width: 48.0, height: 48.0)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
Text(statusData.accountDisplayName ?? statusData.accountUsername)
|
||||
.foregroundColor(Color("displayNameColor"))
|
||||
.foregroundColor(Color("DisplayNameColor"))
|
||||
Text("@\(statusData.accountUsername)")
|
||||
.foregroundColor(Color("lightGrayColor"))
|
||||
.foregroundColor(Color("LightGrayColor"))
|
||||
.font(.footnote)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
|
|
Loading…
Reference in New Issue