mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
[WIP] Message auto-delete and widget
This commit is contained in:
@@ -17,6 +17,29 @@ import WidgetItemsUtils
|
||||
|
||||
import GeneratedSources
|
||||
|
||||
struct ParsedPeer {
|
||||
var accountId: Int64
|
||||
var accountPeerId: Int64
|
||||
var peer: WidgetDataPeer
|
||||
}
|
||||
|
||||
struct ParsedPeers {
|
||||
var peers: [ParsedPeer]
|
||||
var updateTimestamp: Int32
|
||||
}
|
||||
|
||||
private extension ParsedPeers {
|
||||
init(accountId: Int64, peers: WidgetDataPeers) {
|
||||
self.init(peers: peers.peers.map { peer -> ParsedPeer in
|
||||
return ParsedPeer(
|
||||
accountId: accountId,
|
||||
accountPeerId: peers.accountPeerId,
|
||||
peer: peer
|
||||
)
|
||||
}, updateTimestamp: peers.updateTimestamp)
|
||||
}
|
||||
}
|
||||
|
||||
private var installedSharedLogger = false
|
||||
|
||||
private func setupSharedLogger(rootPath: String, path: String) {
|
||||
@@ -64,14 +87,7 @@ struct Provider: IntentTimelineProvider {
|
||||
}
|
||||
|
||||
func getSnapshot(for configuration: SelectFriendsIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
||||
let contents: SimpleEntry.Contents
|
||||
switch configuration.contents {
|
||||
case .unknown, .recent:
|
||||
contents = .recent
|
||||
case .custom:
|
||||
contents = .recent
|
||||
}
|
||||
let entry = SimpleEntry(date: Date(), contents: contents)
|
||||
let entry = SimpleEntry(date: Date(), contents: .peers(ParsedPeers(accountId: 0, peers: WidgetDataPeers(accountPeerId: 0, peers: [], updateTimestamp: 0))))
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
@@ -79,111 +95,209 @@ struct Provider: IntentTimelineProvider {
|
||||
let currentDate = Date()
|
||||
let entryDate = Calendar.current.date(byAdding: .hour, value: 0, to: currentDate)!
|
||||
|
||||
switch configuration.contents {
|
||||
case .unknown, .recent:
|
||||
guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
|
||||
case .custom:
|
||||
guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||
|
||||
let appGroupName = "group.\(baseAppBundleId)"
|
||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
|
||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
|
||||
return
|
||||
}
|
||||
|
||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||
|
||||
let dataPath = rootPath + "/widget-data"
|
||||
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data), case let .peers(widgetPeers) = widgetData.content else {
|
||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
|
||||
return
|
||||
}
|
||||
|
||||
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64())
|
||||
|
||||
let logsPath = rootPath + "/widget-logs"
|
||||
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
setupSharedLogger(rootPath: rootPath, path: logsPath)
|
||||
|
||||
initializeAccountManagement()
|
||||
|
||||
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
|
||||
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
|
||||
|
||||
var itemsByAccount: [Int64: [(Int64, Friend)]] = [:]
|
||||
var itemOrder: [(Int64, Int64)] = []
|
||||
if let friends = configuration.friends {
|
||||
for item in friends {
|
||||
guard let identifier = item.identifier else {
|
||||
continue
|
||||
}
|
||||
guard let index = identifier.firstIndex(of: ":") else {
|
||||
continue
|
||||
}
|
||||
guard let accountId = Int64(identifier[identifier.startIndex ..< index]) else {
|
||||
continue
|
||||
}
|
||||
guard let peerId = Int64(identifier[identifier.index(after: index)...]) else {
|
||||
continue
|
||||
}
|
||||
if itemsByAccount[accountId] == nil {
|
||||
itemsByAccount[accountId] = []
|
||||
}
|
||||
itemsByAccount[accountId]?.append((peerId, item))
|
||||
itemOrder.append((accountId, peerId))
|
||||
}
|
||||
|
||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||
|
||||
let appGroupName = "group.\(baseAppBundleId)"
|
||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
|
||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
|
||||
return
|
||||
}
|
||||
|
||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||
|
||||
let dataPath = rootPath + "/widget-data"
|
||||
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data), case let .peers(widgetPeers) = widgetData.content else {
|
||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
|
||||
return
|
||||
}
|
||||
|
||||
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64())
|
||||
|
||||
let logsPath = rootPath + "/widget-logs"
|
||||
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
setupSharedLogger(rootPath: rootPath, path: logsPath)
|
||||
|
||||
initializeAccountManagement()
|
||||
|
||||
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
|
||||
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
|
||||
|
||||
let _ = (accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: widgetData.accountId), encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> WidgetDataPeers in
|
||||
var peers: [WidgetDataPeer] = []
|
||||
if let items = configuration.friends {
|
||||
for item in items {
|
||||
guard let identifier = item.identifier, let peerIdValue = Int64(identifier) else {
|
||||
continue
|
||||
}
|
||||
|
||||
var friendsByAccount: [Signal<[ParsedPeer], NoError>] = []
|
||||
for (accountId, items) in itemsByAccount {
|
||||
friendsByAccount.append(accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: accountId), encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> [ParsedPeer] in
|
||||
guard let state = transaction.getState() as? AuthorizedAccountState else {
|
||||
return []
|
||||
}
|
||||
|
||||
var result: [ParsedPeer] = []
|
||||
|
||||
for (peerId, _) in items {
|
||||
guard let peer = transaction.getPeer(PeerId(peerId)) else {
|
||||
continue
|
||||
}
|
||||
|
||||
var name: String = ""
|
||||
var lastName: String?
|
||||
|
||||
if let user = peer as? TelegramUser {
|
||||
if let firstName = user.firstName {
|
||||
name = firstName
|
||||
lastName = user.lastName
|
||||
} else if let lastName = user.lastName {
|
||||
name = lastName
|
||||
} else if let phone = user.phone, !phone.isEmpty {
|
||||
name = phone
|
||||
}
|
||||
guard let peer = transaction.getPeer(PeerId(peerIdValue)) else {
|
||||
continue
|
||||
} else {
|
||||
name = peer.debugDisplayTitle
|
||||
}
|
||||
|
||||
var badge: WidgetDataPeer.Badge?
|
||||
|
||||
if let readState = transaction.getCombinedPeerReadState(peer.id), readState.count > 0 {
|
||||
var isMuted = false
|
||||
if let notificationSettings = transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings {
|
||||
isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false)
|
||||
}
|
||||
|
||||
var name: String = ""
|
||||
var lastName: String?
|
||||
|
||||
if let user = peer as? TelegramUser {
|
||||
if let firstName = user.firstName {
|
||||
name = firstName
|
||||
lastName = user.lastName
|
||||
} else if let lastName = user.lastName {
|
||||
name = lastName
|
||||
} else if let phone = user.phone, !phone.isEmpty {
|
||||
name = phone
|
||||
}
|
||||
} else {
|
||||
name = peer.debugDisplayTitle
|
||||
badge = WidgetDataPeer.Badge(
|
||||
count: Int(readState.count),
|
||||
isMuted: isMuted
|
||||
)
|
||||
}
|
||||
|
||||
var mappedMessage: WidgetDataPeer.Message?
|
||||
if let index = transaction.getTopPeerMessageIndex(peerId: peer.id) {
|
||||
if let message = transaction.getMessage(index.id) {
|
||||
mappedMessage = WidgetDataPeer.Message(message: message)
|
||||
}
|
||||
|
||||
var badge: WidgetDataPeer.Badge?
|
||||
|
||||
if let readState = transaction.getCombinedPeerReadState(peer.id), readState.count > 0 {
|
||||
var isMuted = false
|
||||
if let notificationSettings = transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings {
|
||||
isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false)
|
||||
}
|
||||
badge = WidgetDataPeer.Badge(
|
||||
count: Int(readState.count),
|
||||
isMuted: isMuted
|
||||
)
|
||||
}
|
||||
|
||||
let widgetPeer = WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
|
||||
return postbox.mediaBox.resourcePath(representation.resource)
|
||||
}, badge: badge, message: mappedMessage)
|
||||
|
||||
result.append(ParsedPeer(accountId: accountId, accountPeerId: state.peerId.toInt64(), peer: widgetPeer))
|
||||
}
|
||||
|
||||
return result
|
||||
}))
|
||||
}
|
||||
|
||||
let _ = combineLatest(friendsByAccount).start(next: { allPeers in
|
||||
var orderedPeers: [ParsedPeer] = []
|
||||
|
||||
outer: for (accountId, peerId) in itemOrder {
|
||||
for peerSet in allPeers {
|
||||
for peer in peerSet {
|
||||
if peer.accountId == accountId && peer.peer.id == peerId {
|
||||
orderedPeers.append(peer)
|
||||
continue outer
|
||||
}
|
||||
|
||||
var mappedMessage: WidgetDataPeer.Message?
|
||||
if let index = transaction.getTopPeerMessageIndex(peerId: peer.id) {
|
||||
if let message = transaction.getMessage(index.id) {
|
||||
mappedMessage = WidgetDataPeer.Message(message: message)
|
||||
}
|
||||
}
|
||||
|
||||
peers.append(WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
|
||||
return postbox.mediaBox.resourcePath(representation.resource)
|
||||
}, badge: badge, message: mappedMessage))
|
||||
}
|
||||
}
|
||||
return WidgetDataPeers(accountPeerId: widgetPeers.accountPeerId, peers: peers, updateTimestamp: Int32(Date().timeIntervalSince1970))
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { peers in
|
||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .peers(peers))], policy: .atEnd))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let result = ParsedPeers(peers: orderedPeers, updateTimestamp: Int32(Date().timeIntervalSince1970))
|
||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .peers(result))], policy: .atEnd))
|
||||
})
|
||||
|
||||
/*let _ = (accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: widgetData.accountId), encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> ParsedPeers in
|
||||
var peers: [ParsedPeer] = []
|
||||
if let items = configuration.friends {
|
||||
for item in items {
|
||||
guard let identifier = item.identifier, let peerIdValue = Int64(identifier) else {
|
||||
continue
|
||||
}
|
||||
guard let peer = transaction.getPeer(PeerId(peerIdValue)) else {
|
||||
continue
|
||||
}
|
||||
|
||||
var name: String = ""
|
||||
var lastName: String?
|
||||
|
||||
if let user = peer as? TelegramUser {
|
||||
if let firstName = user.firstName {
|
||||
name = firstName
|
||||
lastName = user.lastName
|
||||
} else if let lastName = user.lastName {
|
||||
name = lastName
|
||||
} else if let phone = user.phone, !phone.isEmpty {
|
||||
name = phone
|
||||
}
|
||||
} else {
|
||||
name = peer.debugDisplayTitle
|
||||
}
|
||||
|
||||
var badge: WidgetDataPeer.Badge?
|
||||
|
||||
if let readState = transaction.getCombinedPeerReadState(peer.id), readState.count > 0 {
|
||||
var isMuted = false
|
||||
if let notificationSettings = transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings {
|
||||
isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false)
|
||||
}
|
||||
badge = WidgetDataPeer.Badge(
|
||||
count: Int(readState.count),
|
||||
isMuted: isMuted
|
||||
)
|
||||
}
|
||||
|
||||
var mappedMessage: WidgetDataPeer.Message?
|
||||
if let index = transaction.getTopPeerMessageIndex(peerId: peer.id) {
|
||||
if let message = transaction.getMessage(index.id) {
|
||||
mappedMessage = WidgetDataPeer.Message(message: message)
|
||||
}
|
||||
}
|
||||
|
||||
peers.append(WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
|
||||
return postbox.mediaBox.resourcePath(representation.resource)
|
||||
}, badge: badge, message: mappedMessage))
|
||||
}
|
||||
}
|
||||
return ParsedPeers(peers: peers, updateTimestamp: Int32(Date().timeIntervalSince1970))
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { peers in
|
||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .peers(peers))], policy: .atEnd))
|
||||
})*/
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
enum Contents {
|
||||
case recent
|
||||
case peers(WidgetDataPeers)
|
||||
case peers(ParsedPeers)
|
||||
}
|
||||
|
||||
let date: Date
|
||||
@@ -191,27 +305,32 @@ struct SimpleEntry: TimelineEntry {
|
||||
}
|
||||
|
||||
enum PeersWidgetData {
|
||||
case placeholder
|
||||
case empty
|
||||
case locked
|
||||
case peers(WidgetDataPeers)
|
||||
case peers(ParsedPeers)
|
||||
}
|
||||
|
||||
extension PeersWidgetData {
|
||||
static let previewData = PeersWidgetData.placeholder
|
||||
static let previewData = PeersWidgetData.empty
|
||||
}
|
||||
|
||||
struct AvatarItemView: View {
|
||||
var accountPeerId: Int64
|
||||
var peer: WidgetDataPeer
|
||||
var peer: ParsedPeer?
|
||||
var itemSize: CGFloat
|
||||
var displayBadge: Bool = true
|
||||
|
||||
var body: some View {
|
||||
return ZStack {
|
||||
Image(uiImage: avatarImage(accountPeerId: accountPeerId, peer: peer, size: CGSize(width: itemSize, height: itemSize)))
|
||||
if let peer = peer {
|
||||
Image(uiImage: avatarImage(accountPeerId: peer.accountPeerId, peer: peer.peer, size: CGSize(width: itemSize, height: itemSize)))
|
||||
.clipShape(Circle())
|
||||
if displayBadge, let badge = peer.badge, badge.count > 0 {
|
||||
} else {
|
||||
Image(uiImage: avatarImage(accountPeerId: nil, peer: nil, size: CGSize(width: itemSize, height: itemSize)))
|
||||
//Rectangle()
|
||||
.frame(width: itemSize, height: itemSize)
|
||||
.clipShape(Circle())
|
||||
.redacted(reason: .placeholder)
|
||||
}
|
||||
/*if let peer = peer, displayBadge, let badge = peer.badge, badge.count > 0 {
|
||||
Text("\(badge.count)")
|
||||
.font(Font.system(size: 16.0))
|
||||
.multilineTextAlignment(.center)
|
||||
@@ -223,7 +342,7 @@ struct AvatarItemView: View {
|
||||
.frame(minWidth: 20, idealWidth: 20, maxWidth: .infinity, minHeight: 20, idealHeight: 20, maxHeight: 20.0, alignment: .center)
|
||||
)
|
||||
.position(x: floor(0.84 * itemSize), y: floor(0.16 * itemSize))
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,7 +378,7 @@ struct WidgetView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func peersView(geometry: GeometryProxy, peers: WidgetDataPeers) -> some View {
|
||||
func peersView(geometry: GeometryProxy, peers: ParsedPeers) -> some View {
|
||||
let columnCount: Int
|
||||
let rowCount: Int
|
||||
|
||||
@@ -308,9 +427,8 @@ struct WidgetView: View {
|
||||
|
||||
return ZStack {
|
||||
ForEach(0 ..< min(peers.peers.count, columnCount * rowCount), content: { i in
|
||||
Link(destination: URL(string: linkForPeer(id: peers.peers[i].id))!, label: {
|
||||
Link(destination: URL(string: linkForPeer(id: peers.peers[i].peer.id))!, label: {
|
||||
AvatarItemView(
|
||||
accountPeerId: peers.accountPeerId,
|
||||
peer: peers.peers[i],
|
||||
itemSize: itemSize
|
||||
).frame(width: itemSize, height: itemSize)
|
||||
@@ -322,18 +440,10 @@ struct WidgetView: View {
|
||||
|
||||
func peerViews() -> AnyView {
|
||||
switch data {
|
||||
case .placeholder:
|
||||
case .empty:
|
||||
return AnyView(GeometryReader { geometry in
|
||||
placeholder(geometry: geometry)
|
||||
})
|
||||
case .empty:
|
||||
return AnyView(VStack {
|
||||
Text(presentationData.applicationStartRequiredString)
|
||||
})
|
||||
case .locked:
|
||||
return AnyView(VStack {
|
||||
Text(presentationData.applicationLockedString)
|
||||
})
|
||||
case let .peers(peers):
|
||||
return AnyView(GeometryReader { geometry in
|
||||
peersView(geometry: geometry, peers: peers)
|
||||
@@ -348,23 +458,50 @@ struct WidgetView: View {
|
||||
.padding(0.0)
|
||||
}
|
||||
|
||||
func chatTopLine(_ peer: WidgetDataPeer) -> some View {
|
||||
func chatTopLine(_ peer: ParsedPeer?) -> some View {
|
||||
let dateText: String
|
||||
if let message = peer.message {
|
||||
dateText = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(message.timestamp)), dateStyle: .none, timeStyle: .short)
|
||||
|
||||
let chatTitle: Text
|
||||
let date: Text
|
||||
|
||||
if let peer = peer {
|
||||
if let message = peer.peer.message {
|
||||
dateText = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(message.timestamp)), dateStyle: .none, timeStyle: .short)
|
||||
} else {
|
||||
dateText = ""
|
||||
}
|
||||
chatTitle = Text(peer.peer.name).font(Font.system(size: 16.0, weight: .medium, design: .default)).foregroundColor(.primary)
|
||||
date = Text(dateText)
|
||||
.font(Font.system(size: 14.0, weight: .regular, design: .default)).foregroundColor(.secondary)
|
||||
} else {
|
||||
dateText = ""
|
||||
dateText = "10:00"
|
||||
chatTitle = Text("Chat Title").font(Font.system(size: 16.0, weight: .medium, design: .default)).foregroundColor(.primary)
|
||||
date = Text(dateText)
|
||||
.font(Font.system(size: 14.0, weight: .regular, design: .default)).foregroundColor(.secondary)
|
||||
}
|
||||
return HStack(alignment: .center, spacing: 0.0, content: {
|
||||
Text(peer.name).font(Font.system(size: 16.0, weight: .medium, design: .default)).foregroundColor(.primary)
|
||||
if peer != nil {
|
||||
chatTitle
|
||||
} else {
|
||||
chatTitle.redacted(reason: .placeholder)
|
||||
}
|
||||
Spacer()
|
||||
Text(dateText).font(Font.system(size: 14.0, weight: .regular, design: .default)).foregroundColor(.secondary)
|
||||
if peer != nil {
|
||||
date
|
||||
} else {
|
||||
date.redacted(reason: .placeholder)
|
||||
}
|
||||
})
|
||||
.padding(0.0)
|
||||
}
|
||||
|
||||
func chatBottomLine(_ peer: WidgetDataPeer) -> some View {
|
||||
var text = peer.message?.text ?? ""
|
||||
if let message = peer.message {
|
||||
func chatBottomLine(_ peer: ParsedPeer?) -> AnyView {
|
||||
var text = peer?.peer.message?.text ?? ""
|
||||
text += "\n"
|
||||
if peer == nil {
|
||||
text = "First Line Of Text Here\nSecond line fwqefeqwfqwef qwef wq"
|
||||
}
|
||||
if let message = peer?.peer.message {
|
||||
//TODO:localize
|
||||
switch message.content {
|
||||
case .text:
|
||||
@@ -406,17 +543,47 @@ struct WidgetView: View {
|
||||
case let .poll(poll):
|
||||
text = "📊 \(poll.title)"
|
||||
}
|
||||
|
||||
if let author = message.author {
|
||||
if author.isMe {
|
||||
text = "You: \(text)"
|
||||
} else {
|
||||
text = "\(author.title): \(text)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hasBadge = false
|
||||
if let badge = peer.badge, badge.count > 0 {
|
||||
if let peer = peer, let badge = peer.peer.badge, badge.count > 0 {
|
||||
hasBadge = true
|
||||
}
|
||||
|
||||
return HStack(alignment: .center, spacing: hasBadge ? 6.0 : 0.0, content: {
|
||||
Text(text).lineLimit(nil).font(Font.system(size: 15.0, weight: .regular, design: .default)).foregroundColor(.secondary).multilineTextAlignment(.leading).frame(maxHeight: .infinity, alignment: .topLeading)
|
||||
Spacer()
|
||||
if let badge = peer.badge, badge.count > 0 {
|
||||
let textView = Text(text)
|
||||
.lineLimit(2)
|
||||
.font(Font.system(size: 15.0, weight: .regular, design: .default))
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(0.0)
|
||||
//.frame(maxHeight: .infinity, alignment: .topLeading)
|
||||
//.background(Rectangle().foregroundColor(.gray))
|
||||
|
||||
if peer != nil {
|
||||
return AnyView(textView)
|
||||
} else {
|
||||
return AnyView(
|
||||
textView
|
||||
.redacted(reason: .placeholder)
|
||||
)
|
||||
}
|
||||
|
||||
/*return HStack(alignment: .center, spacing: hasBadge ? 6.0 : 0.0, content: {
|
||||
if peer != nil {
|
||||
textView
|
||||
} else {
|
||||
textView.redacted(reason: .placeholder)
|
||||
}
|
||||
//Spacer()
|
||||
/*if let peer = peer, let badge = peer.badge, badge.count > 0 {
|
||||
VStack {
|
||||
Spacer()
|
||||
Text("\(badge.count)")
|
||||
@@ -431,78 +598,114 @@ struct WidgetView: View {
|
||||
)
|
||||
.padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 6.0, trailing: 3.0))
|
||||
}
|
||||
}
|
||||
})
|
||||
}*/
|
||||
})*/
|
||||
}
|
||||
|
||||
func chatContent(_ peer: WidgetDataPeer) -> some View {
|
||||
return VStack(alignment: .leading, spacing: 2.0, content: {
|
||||
func chatContent(_ peer: ParsedPeer?) -> some View {
|
||||
return VStack(alignment: .leading, spacing: 0.0, content: {
|
||||
chatTopLine(peer)
|
||||
chatBottomLine(peer).frame(maxHeight: .infinity)
|
||||
chatBottomLine(peer)
|
||||
})
|
||||
}
|
||||
|
||||
func chatContentView(_ index: Int, size: CGSize) -> AnyView {
|
||||
let peers: WidgetDataPeers
|
||||
let peers: ParsedPeers?
|
||||
switch data {
|
||||
case let .peers(peersValue):
|
||||
peers = peersValue
|
||||
if peers.peers.count <= index {
|
||||
return AnyView(Spacer())
|
||||
if peersValue.peers.count <= index {
|
||||
peers = nil
|
||||
} else {
|
||||
peers = peersValue
|
||||
}
|
||||
default:
|
||||
return AnyView(Spacer())
|
||||
peers = nil
|
||||
}
|
||||
|
||||
let itemHeight = (size.height - 22.0) / 2.0
|
||||
|
||||
let url: URL
|
||||
if let peers = peers {
|
||||
url = URL(string: linkForPeer(id: peers.peers[index].peer.id))!
|
||||
} else {
|
||||
url = URL(string: "\(buildConfig.appSpecificUrlScheme)://")!
|
||||
}
|
||||
|
||||
return AnyView(
|
||||
Link(destination: URL(string: linkForPeer(id: peers.peers[index].id))!, label: {
|
||||
Link(destination: url, label: {
|
||||
HStack(alignment: .center, spacing: 0.0, content: {
|
||||
AvatarItemView(accountPeerId: peers.accountPeerId, peer: peers.peers[index], itemSize: 54.0, displayBadge: false).frame(width: 54.0, height: 54.0, alignment: .leading).padding(EdgeInsets(top: 0.0, leading: 10.0, bottom: 0.0, trailing: 10.0))
|
||||
chatContent(peers.peers[index]).frame(maxWidth: .infinity).padding(EdgeInsets(top: 10.0, leading: 0.0, bottom: 10.0, trailing: 10.0))
|
||||
AvatarItemView(peer: peers?.peers[index], itemSize: 54.0, displayBadge: false).frame(width: 54.0, height: 54.0, alignment: .leading).padding(EdgeInsets(top: 0.0, leading: 10.0, bottom: 0.0, trailing: 10.0))
|
||||
chatContent(peers?.peers[index]).frame(maxWidth: .infinity).padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 10.0))
|
||||
})
|
||||
}).position(x: size.width / 2.0, y: (itemHeight * 2.0) / 4.0 + CGFloat(index) * itemHeight).frame(width: size.width, height: itemHeight, alignment: .leading)
|
||||
})
|
||||
.frame(width: size.width, height: itemHeight, alignment: .leading)
|
||||
)
|
||||
}
|
||||
|
||||
func chatSeparatorView(size: CGSize) -> some View {
|
||||
let separatorWidth = size.width - 54.0 - 20.0
|
||||
return HStack(alignment: .center, spacing: 0.0, content: {
|
||||
Spacer()
|
||||
Rectangle()
|
||||
.foregroundColor(getSeparatorColor())
|
||||
.frame(width: size.width - 54.0 - 20.0, height: 0.5, alignment: .leading)
|
||||
})
|
||||
.frame(width: size.width, height: 1.0, alignment: .leading)
|
||||
/*let separatorWidth = size.width - 54.0 - 20.0
|
||||
let itemHeight = (size.height - 22.0) / 2.0
|
||||
return Rectangle().foregroundColor(getSeparatorColor()).position(x: (54.0 + 20.0 + separatorWidth) / 2.0, y: itemHeight / 2.0).frame(width: separatorWidth, height: 0.33, alignment: .leading)
|
||||
return Rectangle().foregroundColor(getSeparatorColor())
|
||||
//.position(x: (54.0 + 20.0 + separatorWidth) / 2.0, y: itemHeight / 2.0)
|
||||
.frame(width: size.width, height: 1.0, alignment: .leading)*/
|
||||
}
|
||||
|
||||
func chatsUpdateBackgroundView(size: CGSize) -> some View {
|
||||
return Rectangle().foregroundColor(getUpdatedBackgroundColor()).position(x: size.width / 2.0, y: size.height - 22.0 - 11.0).frame(width: size.width, height: 22.0, alignment: .leading)
|
||||
return Rectangle().foregroundColor(getUpdatedBackgroundColor())
|
||||
//.position(x: size.width / 2.0, y: size.height - 11.0)
|
||||
.frame(width: size.width, height: 22.0, alignment: .center)
|
||||
}
|
||||
|
||||
func chatUpdateView(size: CGSize) -> some View {
|
||||
return ZStack(alignment: Alignment(horizontal: .center, vertical: .center), content: {
|
||||
Rectangle().foregroundColor(getUpdatedBackgroundColor())
|
||||
chatsUpdateTimestampView(size: size)
|
||||
})
|
||||
.frame(width: size.width, height: 22.0 - 1.0, alignment: .center)
|
||||
}
|
||||
|
||||
func chatsUpdateTimestampView(size: CGSize) -> some View {
|
||||
let text: String
|
||||
switch data {
|
||||
case let .peers(peersValue):
|
||||
let date = Date(timeIntervalSince1970: Double(peersValue.updateTimestamp))
|
||||
let calendar = Calendar.current
|
||||
//TODO:localize
|
||||
if !calendar.isDate(Date(), inSameDayAs: date) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
formatter.timeStyle = .none
|
||||
text = "updated on \(formatter.string(from: date))"
|
||||
if peersValue.peers.isEmpty {
|
||||
text = "Long tap to edit widget"
|
||||
} else {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .none
|
||||
formatter.timeStyle = .short
|
||||
text = "updated at \(formatter.string(from: date))"
|
||||
let date = Date(timeIntervalSince1970: Double(peersValue.updateTimestamp))
|
||||
let calendar = Calendar.current
|
||||
//TODO:localize
|
||||
if !calendar.isDate(Date(), inSameDayAs: date) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
formatter.timeStyle = .none
|
||||
text = "updated on \(formatter.string(from: date))"
|
||||
} else {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .none
|
||||
formatter.timeStyle = .short
|
||||
text = "updated at \(formatter.string(from: date))"
|
||||
}
|
||||
}
|
||||
default:
|
||||
text = ""
|
||||
}
|
||||
|
||||
return HStack(alignment: .center, spacing: 0.0, content: {
|
||||
return Text(text)
|
||||
.font(Font.system(size: 12.0))
|
||||
.foregroundColor(getUpdatedTextColor())
|
||||
|
||||
/*return HStack(alignment: .center, spacing: 0.0, content: {
|
||||
Text(text)
|
||||
.font(Font.system(size: 12.0))
|
||||
.foregroundColor(getUpdatedTextColor())
|
||||
}).position(x: size.width / 2.0, y: size.height - 22.0 - 11.0).frame(width: size.width, height: 22.0, alignment: .leading)
|
||||
}).position(x: size.width / 2.0, y: size.height - 11.0).frame(width: size.width, height: 22.0, alignment: .leading)*/
|
||||
}
|
||||
|
||||
func getSeparatorColor() -> Color {
|
||||
@@ -540,13 +743,12 @@ struct WidgetView: View {
|
||||
|
||||
var body: some View {
|
||||
GeometryReader(content: { geometry in
|
||||
return ZStack {
|
||||
return VStack(alignment: .center, spacing: 0.0, content: {
|
||||
chatContentView(0, size: geometry.size)
|
||||
chatContentView(1, size: geometry.size)
|
||||
chatSeparatorView(size: geometry.size)
|
||||
chatsUpdateBackgroundView(size: geometry.size)
|
||||
chatsUpdateTimestampView(size: geometry.size)
|
||||
}
|
||||
chatContentView(1, size: geometry.size)
|
||||
chatUpdateView(size: geometry.size)
|
||||
})
|
||||
})
|
||||
.padding(0.0)
|
||||
}
|
||||
@@ -600,7 +802,7 @@ func getWidgetData(contents: SimpleEntry.Contents) -> PeersWidgetData {
|
||||
case .recent:
|
||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
return .placeholder
|
||||
return .empty
|
||||
}
|
||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||
|
||||
@@ -608,28 +810,28 @@ func getWidgetData(contents: SimpleEntry.Contents) -> PeersWidgetData {
|
||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
|
||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||
return .placeholder
|
||||
return .empty
|
||||
}
|
||||
|
||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) {
|
||||
return .locked
|
||||
}
|
||||
/*if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data) {
|
||||
if state.isManuallyLocked || state.autolockTimeout != nil {
|
||||
return .empty
|
||||
}
|
||||
}*/
|
||||
|
||||
let dataPath = rootPath + "/widget-data"
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) {
|
||||
switch widgetData.content {
|
||||
case let .peers(peers):
|
||||
return .peers(peers)
|
||||
case .disabled:
|
||||
return .placeholder
|
||||
case .notAuthorized:
|
||||
return .locked
|
||||
return .peers(ParsedPeers(accountId: widgetData.accountId, peers: peers))
|
||||
case .empty:
|
||||
return .empty
|
||||
}
|
||||
} else {
|
||||
return .placeholder
|
||||
return .empty
|
||||
}
|
||||
case let .peers(peers):
|
||||
return .peers(peers)
|
||||
|
||||
Reference in New Issue
Block a user