mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Project organization updates
This commit is contained in:
parent
58d5869965
commit
e547ee0075
@ -315,13 +315,11 @@ apple_bundle(
|
|||||||
apple_binary(
|
apple_binary(
|
||||||
name = "NotificationServiceBinary",
|
name = "NotificationServiceBinary",
|
||||||
srcs = glob([
|
srcs = glob([
|
||||||
"NotificationService/**/*.m",
|
"NotificationService/*.swift",
|
||||||
"NotificationService/**/*.swift",
|
|
||||||
]),
|
]),
|
||||||
headers = glob([
|
headers = glob([
|
||||||
"NotificationService/**/*.h",
|
"NotificationService/*.h",
|
||||||
]),
|
]),
|
||||||
bridging_header = "NotificationService/NotificationService-Bridging-Header.h",
|
|
||||||
configs = notification_service_extension_configs(),
|
configs = notification_service_extension_configs(),
|
||||||
swift_compiler_flags = [
|
swift_compiler_flags = [
|
||||||
"-application-extension",
|
"-application-extension",
|
||||||
@ -339,6 +337,7 @@ apple_binary(
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//Telegram/NotificationService/NotificationServiceObjC:NotificationServiceObjC",
|
||||||
"//submodules/BuildConfig:BuildConfig",
|
"//submodules/BuildConfig:BuildConfig",
|
||||||
"//submodules/MtProtoKit:MtProtoKit#shared",
|
"//submodules/MtProtoKit:MtProtoKit#shared",
|
||||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
|
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
|
||||||
|
@ -1099,7 +1099,7 @@ swift_library(
|
|||||||
name = "WidgetExtensionLib",
|
name = "WidgetExtensionLib",
|
||||||
module_name = "WidgetExtensionLib",
|
module_name = "WidgetExtensionLib",
|
||||||
srcs = glob([
|
srcs = glob([
|
||||||
"Widget/**/*.swift",
|
"WidgetKitWidget/**/*.swift",
|
||||||
]),
|
]),
|
||||||
deps = [
|
deps = [
|
||||||
"//submodules/BuildConfig:BuildConfig",
|
"//submodules/BuildConfig:BuildConfig",
|
||||||
|
@ -1,171 +0,0 @@
|
|||||||
import UIKit
|
|
||||||
import NotificationCenter
|
|
||||||
import BuildConfig
|
|
||||||
import WidgetItems
|
|
||||||
import AppLockState
|
|
||||||
|
|
||||||
private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
|
||||||
return appGroupPath + "/telegram-data"
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc(TodayViewController)
|
|
||||||
class TodayViewController: UIViewController, NCWidgetProviding {
|
|
||||||
private var initializedInterface = false
|
|
||||||
|
|
||||||
private var buildConfig: BuildConfig?
|
|
||||||
|
|
||||||
private var primaryColor: UIColor = .black
|
|
||||||
private var placeholderLabel: UILabel?
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
|
||||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
|
||||||
|
|
||||||
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
|
||||||
self.buildConfig = buildConfig
|
|
||||||
|
|
||||||
let appGroupName = "group.\(baseAppBundleId)"
|
|
||||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
|
||||||
|
|
||||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
|
||||||
|
|
||||||
let presentationData: WidgetPresentationData
|
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: widgetPresentationDataPath(rootPath: rootPath))), let value = try? JSONDecoder().decode(WidgetPresentationData.self, from: data) {
|
|
||||||
presentationData = value
|
|
||||||
} else {
|
|
||||||
presentationData = WidgetPresentationData(applicationLockedString: "Unlock the app to use the widget", applicationStartRequiredString: "Open the app to use the widget")
|
|
||||||
}
|
|
||||||
|
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) {
|
|
||||||
self.setPlaceholderText(presentationData.applicationLockedString)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.initializedInterface {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.initializedInterface = true
|
|
||||||
|
|
||||||
let dataPath = rootPath + "/widget-data"
|
|
||||||
|
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) {
|
|
||||||
self.setWidgetData(widgetData: widgetData, presentationData: presentationData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setPlaceholderText(_ text: String) {
|
|
||||||
let fontSize = UIFont.preferredFont(forTextStyle: .body).pointSize
|
|
||||||
let placeholderLabel = UILabel()
|
|
||||||
if #available(iOSApplicationExtension 13.0, *) {
|
|
||||||
placeholderLabel.textColor = UIColor.label
|
|
||||||
} else {
|
|
||||||
placeholderLabel.textColor = self.primaryColor
|
|
||||||
}
|
|
||||||
placeholderLabel.font = UIFont.systemFont(ofSize: fontSize)
|
|
||||||
placeholderLabel.text = text
|
|
||||||
placeholderLabel.sizeToFit()
|
|
||||||
self.placeholderLabel = placeholderLabel
|
|
||||||
self.view.addSubview(placeholderLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
|
|
||||||
completionHandler(.newData)
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(iOSApplicationExtension 10.0, *)
|
|
||||||
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private var widgetData: WidgetData?
|
|
||||||
|
|
||||||
private func setWidgetData(widgetData: WidgetData, presentationData: WidgetPresentationData) {
|
|
||||||
self.widgetData = widgetData
|
|
||||||
self.peerViews.forEach {
|
|
||||||
$0.removeFromSuperview()
|
|
||||||
}
|
|
||||||
self.peerViews = []
|
|
||||||
switch widgetData {
|
|
||||||
case .notAuthorized, .disabled:
|
|
||||||
break
|
|
||||||
case let .peers(peers):
|
|
||||||
for peer in peers.peers {
|
|
||||||
let peerView = PeerView(primaryColor: self.primaryColor, accountPeerId: peers.accountPeerId, peer: peer, tapped: { [weak self] in
|
|
||||||
if let strongSelf = self, let buildConfig = strongSelf.buildConfig {
|
|
||||||
if let url = URL(string: "\(buildConfig.appSpecificUrlScheme)://localpeer?id=\(peer.id)") {
|
|
||||||
strongSelf.extensionContext?.open(url, completionHandler: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
self.view.addSubview(peerView)
|
|
||||||
self.peerViews.append(peerView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.peerViews.isEmpty {
|
|
||||||
self.setPlaceholderText(presentationData.applicationStartRequiredString)
|
|
||||||
} else {
|
|
||||||
self.placeholderLabel?.removeFromSuperview()
|
|
||||||
self.placeholderLabel = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let size = self.validLayout {
|
|
||||||
self.updateLayout(size: size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var validLayout: CGSize?
|
|
||||||
|
|
||||||
private var peerViews: [PeerView] = []
|
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
|
||||||
super.viewDidLayoutSubviews()
|
|
||||||
|
|
||||||
self.updateLayout(size: self.view.bounds.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateLayout(size: CGSize) {
|
|
||||||
self.validLayout = size
|
|
||||||
|
|
||||||
if let placeholderLabel = self.placeholderLabel {
|
|
||||||
placeholderLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - placeholderLabel.bounds.width) / 2.0), y: floor((size.height - placeholderLabel.bounds.height) / 2.0)), size: placeholderLabel.bounds.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
let peerSize = CGSize(width: 70.0, height: 100.0)
|
|
||||||
|
|
||||||
var peerFrames: [CGRect] = []
|
|
||||||
|
|
||||||
var offset: CGFloat = 0.0
|
|
||||||
for _ in self.peerViews {
|
|
||||||
let peerFrame = CGRect(origin: CGPoint(x: offset, y: 10.0), size: peerSize)
|
|
||||||
offset += peerFrame.size.width
|
|
||||||
if peerFrame.maxX > size.width {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
peerFrames.append(peerFrame)
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalSize: CGFloat = 0.0
|
|
||||||
for i in 0 ..< peerFrames.count {
|
|
||||||
totalSize += peerFrames[i].width
|
|
||||||
}
|
|
||||||
|
|
||||||
let spacing: CGFloat = floor((size.width - totalSize) / CGFloat(peerFrames.count))
|
|
||||||
offset = floor(spacing / 2.0)
|
|
||||||
for i in 0 ..< peerFrames.count {
|
|
||||||
let peerView = self.peerViews[i]
|
|
||||||
peerView.frame = CGRect(origin: CGPoint(x: offset, y: 16.0), size: peerFrames[i].size)
|
|
||||||
peerView.updateLayout(size: peerFrames[i].size)
|
|
||||||
offset += peerFrames[i].width + spacing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>com.apple.security.application-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>group.org.telegram.TelegramHD</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>com.apple.security.application-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>group.ph.telegra.Telegraph</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,4 +0,0 @@
|
|||||||
#ifndef Widget_Bridging_Header_h
|
|
||||||
#define Widget_Bridging_Header_h
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>com.apple.security.application-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>group.fork.telegram.Telegram-iOS</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>com.apple.security.application-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>group.org.telegram.Telegram-iOS</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,2 +0,0 @@
|
|||||||
load("//Config:buck_rule_macros.bzl", "static_library")
|
|
||||||
|
|
@ -11,6 +11,11 @@ static_library(
|
|||||||
exported_headers = glob([
|
exported_headers = glob([
|
||||||
"PublicHeaders/**/*.h",
|
"PublicHeaders/**/*.h",
|
||||||
]),
|
]),
|
||||||
|
deps = [
|
||||||
|
"//submodules/BuildConfig:BuildConfig",
|
||||||
|
"//submodules/MtProtoKit:MtProtoKit#shared",
|
||||||
|
"//submodules/NotificationsPresentationData:NotificationsPresentationData",
|
||||||
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||||
|
@ -5861,6 +5861,8 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Chat.PanelHidePinnedMessages" = "Don't Show Pinned Messages";
|
"Chat.PanelHidePinnedMessages" = "Don't Show Pinned Messages";
|
||||||
"Chat.PanelUnpinAllMessages_1" = "Unpin Message";
|
"Chat.PanelUnpinAllMessages_1" = "Unpin Message";
|
||||||
"Chat.PanelUnpinAllMessages_any" = "Unpin All %@ Messages";
|
"Chat.PanelUnpinAllMessages_any" = "Unpin All %@ Messages";
|
||||||
|
"Chat.UnpinAllMessagesConfirmation_1" = "Do you want to unpin 1 message in this chat?";
|
||||||
|
"Chat.UnpinAllMessagesConfirmation_any" = "Do you want to unpin all %@ messages in this chat?";
|
||||||
|
|
||||||
"Chat.MessagesUnpinned_1" = "Message Unpinned";
|
"Chat.MessagesUnpinned_1" = "Message Unpinned";
|
||||||
"Chat.MessagesUnpinned_any" = "%@ Messages Unpinned";
|
"Chat.MessagesUnpinned_any" = "%@ Messages Unpinned";
|
||||||
|
@ -39,7 +39,7 @@ private func avatarRoundImage(size: CGSize, source: UIImage) -> UIImage? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let deviceColorSpace: CGColorSpace = {
|
private let deviceColorSpace: CGColorSpace = {
|
||||||
if #available(iOSApplicationExtension 9.3, iOS 9.3, *) {
|
if #available(iOSApplicationExtension 9.3, *) {
|
||||||
if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) {
|
if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) {
|
||||||
return colorSpace
|
return colorSpace
|
||||||
} else {
|
} else {
|
||||||
@ -94,14 +94,6 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
|
|||||||
|
|
||||||
private let avatarSize = CGSize(width: 50.0, height: 50.0)
|
private let avatarSize = CGSize(width: 50.0, height: 50.0)
|
||||||
|
|
||||||
func avatarImage(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) -> UIImage {
|
|
||||||
if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
|
||||||
return roundImage
|
|
||||||
} else {
|
|
||||||
return avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters)!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class AvatarView: UIImageView {
|
private final class AvatarView: UIImageView {
|
||||||
init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) {
|
init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) {
|
||||||
super.init(frame: CGRect())
|
super.init(frame: CGRect())
|
||||||
@ -141,7 +133,7 @@ final class PeerView: UIView {
|
|||||||
let fontSize = floor(systemFontSize * 11.0 / 17.0)
|
let fontSize = floor(systemFontSize * 11.0 / 17.0)
|
||||||
|
|
||||||
self.titleLabel.text = title
|
self.titleLabel.text = title
|
||||||
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
|
if #available(iOSApplicationExtension 13.0, *) {
|
||||||
self.titleLabel.textColor = UIColor.label
|
self.titleLabel.textColor = UIColor.label
|
||||||
} else {
|
} else {
|
||||||
self.titleLabel.textColor = primaryColor
|
self.titleLabel.textColor = primaryColor
|
||||||
|
@ -3,219 +3,169 @@ import NotificationCenter
|
|||||||
import BuildConfig
|
import BuildConfig
|
||||||
import WidgetItems
|
import WidgetItems
|
||||||
import AppLockState
|
import AppLockState
|
||||||
import SwiftUI
|
|
||||||
import WidgetKit
|
|
||||||
|
|
||||||
private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||||
return appGroupPath + "/telegram-data"
|
return appGroupPath + "/telegram-data"
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Provider: TimelineProvider {
|
@objc(TodayViewController)
|
||||||
public typealias Entry = SimpleEntry
|
class TodayViewController: UIViewController, NCWidgetProviding {
|
||||||
|
private var initializedInterface = false
|
||||||
|
|
||||||
func placeholder(in context: Context) -> SimpleEntry {
|
private var buildConfig: BuildConfig?
|
||||||
return SimpleEntry(date: Date())
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
|
private var primaryColor: UIColor = .black
|
||||||
let entry = SimpleEntry(date: Date())
|
private var placeholderLabel: UILabel?
|
||||||
completion(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
|
override func viewDidLoad() {
|
||||||
var entries: [SimpleEntry] = []
|
super.viewDidLoad()
|
||||||
|
|
||||||
let currentDate = Date()
|
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||||
for hourOffset in 0 ..< 1 {
|
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||||
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
|
return
|
||||||
let entry = SimpleEntry(date: entryDate)
|
|
||||||
entries.append(entry)
|
|
||||||
}
|
}
|
||||||
|
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||||
let timeline = Timeline(entries: entries, policy: .atEnd)
|
|
||||||
completion(timeline)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SimpleEntry: TimelineEntry {
|
|
||||||
let date: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PeersWidgetData {
|
|
||||||
case placeholder
|
|
||||||
case empty
|
|
||||||
case locked
|
|
||||||
case data(WidgetData)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PeersWidgetData {
|
|
||||||
static let previewData = PeersWidgetData.placeholder
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WidgetView: View {
|
|
||||||
let data: PeersWidgetData
|
|
||||||
|
|
||||||
func placeholder(geometry: GeometryProxy) -> some View {
|
|
||||||
let defaultItemSize: CGFloat = 60.0
|
|
||||||
let defaultPaddingFraction: CGFloat = 0.36
|
|
||||||
|
|
||||||
let rowCount = Int(round(geometry.size.width / (defaultItemSize * (1.0 + defaultPaddingFraction))))
|
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
||||||
let itemSize = floor(geometry.size.width / (CGFloat(rowCount) + defaultPaddingFraction * CGFloat(rowCount - 1)))
|
self.buildConfig = buildConfig
|
||||||
|
|
||||||
let firstRowY = itemSize / 2.0
|
let appGroupName = "group.\(baseAppBundleId)"
|
||||||
let secondRowY = itemSize / 2.0 + geometry.size.height - itemSize
|
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||||
|
|
||||||
return ZStack {
|
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||||
ForEach(0 ..< rowCount * 2, content: { i in
|
return
|
||||||
return Circle().frame(width: itemSize, height: itemSize).position(x: itemSize / 2.0 + floor(CGFloat(i % rowCount) * itemSize * (1.0 + defaultPaddingFraction)), y: i / rowCount == 0 ? firstRowY : secondRowY).foregroundColor(.gray)
|
}
|
||||||
})
|
|
||||||
|
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||||
|
|
||||||
|
let presentationData: WidgetPresentationData
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: widgetPresentationDataPath(rootPath: rootPath))), let value = try? JSONDecoder().decode(WidgetPresentationData.self, from: data) {
|
||||||
|
presentationData = value
|
||||||
|
} else {
|
||||||
|
presentationData = WidgetPresentationData(applicationLockedString: "Unlock the app to use the widget", applicationStartRequiredString: "Open the app to use the widget", widgetGalleryTitle: "", widgetGalleryDescription: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) {
|
||||||
|
self.setPlaceholderText(presentationData.applicationLockedString)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.initializedInterface {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.initializedInterface = true
|
||||||
|
|
||||||
|
let dataPath = rootPath + "/widget-data"
|
||||||
|
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) {
|
||||||
|
self.setWidgetData(widgetData: widgetData, presentationData: presentationData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func peersView(geometry: GeometryProxy, peers: WidgetDataPeers) -> some View {
|
private func setPlaceholderText(_ text: String) {
|
||||||
let defaultItemSize: CGFloat = 60.0
|
let fontSize = UIFont.preferredFont(forTextStyle: .body).pointSize
|
||||||
let defaultPaddingFraction: CGFloat = 0.36
|
let placeholderLabel = UILabel()
|
||||||
|
if #available(iOSApplicationExtension 13.0, *) {
|
||||||
let rowCount = Int(round(geometry.size.width / (defaultItemSize * (1.0 + defaultPaddingFraction))))
|
placeholderLabel.textColor = UIColor.label
|
||||||
let itemSize = floor(geometry.size.width / (CGFloat(rowCount) + defaultPaddingFraction * CGFloat(rowCount - 1)))
|
} else {
|
||||||
|
placeholderLabel.textColor = self.primaryColor
|
||||||
let firstRowY = itemSize / 2.0
|
|
||||||
let secondRowY = itemSize / 2.0 + geometry.size.height - itemSize
|
|
||||||
|
|
||||||
return ZStack {
|
|
||||||
ForEach(0 ..< min(peers.peers.count, rowCount * 2), content: { i in
|
|
||||||
Link(destination: URL(string: "\(buildConfig.appSpecificUrlScheme)://localpeer?id=\(peers.peers[i].id)")!, label: {
|
|
||||||
Image(uiImage: avatarImage(accountPeerId: peers.accountPeerId, peer: peers.peers[i], size: CGSize(width: itemSize, height: itemSize)))
|
|
||||||
.frame(width: itemSize, height: itemSize)
|
|
||||||
}).frame(width: itemSize, height: itemSize)
|
|
||||||
.position(x: itemSize / 2.0 + floor(CGFloat(i % rowCount) * itemSize * (1.0 + defaultPaddingFraction)), y: i / rowCount == 0 ? firstRowY : secondRowY)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
placeholderLabel.font = UIFont.systemFont(ofSize: fontSize)
|
||||||
|
placeholderLabel.text = text
|
||||||
|
placeholderLabel.sizeToFit()
|
||||||
|
self.placeholderLabel = placeholderLabel
|
||||||
|
self.view.addSubview(placeholderLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func peerViews() -> AnyView {
|
func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
|
||||||
switch data {
|
completionHandler(.newData)
|
||||||
case .placeholder:
|
}
|
||||||
return AnyView(GeometryReader { geometry in
|
|
||||||
placeholder(geometry: geometry)
|
@available(iOSApplicationExtension 10.0, *)
|
||||||
})
|
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
|
||||||
case .empty:
|
|
||||||
return AnyView(VStack {
|
}
|
||||||
Text(presentationData.applicationStartRequiredString)
|
|
||||||
})
|
private var widgetData: WidgetData?
|
||||||
case .locked:
|
|
||||||
return AnyView(VStack {
|
private func setWidgetData(widgetData: WidgetData, presentationData: WidgetPresentationData) {
|
||||||
Text(presentationData.applicationLockedString)
|
self.widgetData = widgetData
|
||||||
})
|
self.peerViews.forEach {
|
||||||
case let .data(data):
|
$0.removeFromSuperview()
|
||||||
switch data {
|
}
|
||||||
case let .peers(peers):
|
self.peerViews = []
|
||||||
return AnyView(GeometryReader { geometry in
|
switch widgetData {
|
||||||
peersView(geometry: geometry, peers: peers)
|
case .notAuthorized, .disabled:
|
||||||
})
|
break
|
||||||
default:
|
case let .peers(peers):
|
||||||
return AnyView(ZStack {
|
for peer in peers.peers {
|
||||||
Circle()
|
let peerView = PeerView(primaryColor: self.primaryColor, accountPeerId: peers.accountPeerId, peer: peer, tapped: { [weak self] in
|
||||||
|
if let strongSelf = self, let buildConfig = strongSelf.buildConfig {
|
||||||
|
if let url = URL(string: "\(buildConfig.appSpecificUrlScheme)://localpeer?id=\(peer.id)") {
|
||||||
|
strongSelf.extensionContext?.open(url, completionHandler: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
self.view.addSubview(peerView)
|
||||||
|
self.peerViews.append(peerView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if self.peerViews.isEmpty {
|
||||||
var body: some View {
|
self.setPlaceholderText(presentationData.applicationStartRequiredString)
|
||||||
ZStack {
|
} else {
|
||||||
Color(.systemBackground)
|
self.placeholderLabel?.removeFromSuperview()
|
||||||
peerViews()
|
self.placeholderLabel = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let size = self.validLayout {
|
||||||
|
self.updateLayout(size: size)
|
||||||
}
|
}
|
||||||
.padding(.all)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private let buildConfig: BuildConfig = {
|
|
||||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
|
||||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
|
||||||
preconditionFailure()
|
|
||||||
}
|
|
||||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
|
||||||
|
|
||||||
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
|
||||||
return buildConfig
|
|
||||||
}()
|
|
||||||
|
|
||||||
private extension WidgetPresentationData {
|
|
||||||
static var `default` = WidgetPresentationData(
|
|
||||||
applicationLockedString: "Unlock the app to use the widget",
|
|
||||||
applicationStartRequiredString: "Open the app to use the widget",
|
|
||||||
widgetGalleryTitle: "Telegram",
|
|
||||||
widgetGalleryDescription: ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private let presentationData: WidgetPresentationData = {
|
|
||||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
|
||||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
|
||||||
return WidgetPresentationData.default
|
|
||||||
}
|
|
||||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
|
||||||
|
|
||||||
let appGroupName = "group.\(baseAppBundleId)"
|
|
||||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
|
||||||
|
|
||||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
|
||||||
return WidgetPresentationData.default
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: widgetPresentationDataPath(rootPath: rootPath))), let value = try? JSONDecoder().decode(WidgetPresentationData.self, from: data) {
|
private var peerViews: [PeerView] = []
|
||||||
return value
|
|
||||||
} else {
|
|
||||||
return WidgetPresentationData.default
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
func getWidgetData() -> PeersWidgetData {
|
|
||||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
|
||||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
|
||||||
return .placeholder
|
|
||||||
}
|
|
||||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
|
||||||
|
|
||||||
let appGroupName = "group.\(baseAppBundleId)"
|
override func viewDidLayoutSubviews() {
|
||||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
self.updateLayout(size: self.view.bounds.size)
|
||||||
return .placeholder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
private func updateLayout(size: CGSize) {
|
||||||
|
self.validLayout = size
|
||||||
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 placeholderLabel = self.placeholderLabel {
|
||||||
}
|
placeholderLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - placeholderLabel.bounds.width) / 2.0), y: floor((size.height - placeholderLabel.bounds.height) / 2.0)), size: placeholderLabel.bounds.size)
|
||||||
|
}
|
||||||
let dataPath = rootPath + "/widget-data"
|
|
||||||
|
let peerSize = CGSize(width: 70.0, height: 100.0)
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) {
|
|
||||||
return .data(widgetData)
|
var peerFrames: [CGRect] = []
|
||||||
} else {
|
|
||||||
return .placeholder
|
var offset: CGFloat = 0.0
|
||||||
}
|
for _ in self.peerViews {
|
||||||
}
|
let peerFrame = CGRect(origin: CGPoint(x: offset, y: 10.0), size: peerSize)
|
||||||
|
offset += peerFrame.size.width
|
||||||
@main
|
if peerFrame.maxX > size.width {
|
||||||
struct Static_Widget: Widget {
|
break
|
||||||
private let kind: String = "Static_Widget"
|
|
||||||
|
|
||||||
public var body: some WidgetConfiguration {
|
|
||||||
return StaticConfiguration(
|
|
||||||
kind: kind,
|
|
||||||
provider: Provider(),
|
|
||||||
content: { entry in
|
|
||||||
WidgetView(data: getWidgetData())
|
|
||||||
}
|
}
|
||||||
)
|
peerFrames.append(peerFrame)
|
||||||
.supportedFamilies([.systemMedium])
|
}
|
||||||
.configurationDisplayName(presentationData.widgetGalleryTitle)
|
|
||||||
.description(presentationData.widgetGalleryDescription)
|
var totalSize: CGFloat = 0.0
|
||||||
|
for i in 0 ..< peerFrames.count {
|
||||||
|
totalSize += peerFrames[i].width
|
||||||
|
}
|
||||||
|
|
||||||
|
let spacing: CGFloat = floor((size.width - totalSize) / CGFloat(peerFrames.count))
|
||||||
|
offset = floor(spacing / 2.0)
|
||||||
|
for i in 0 ..< peerFrames.count {
|
||||||
|
let peerView = self.peerViews[i]
|
||||||
|
peerView.frame = CGRect(origin: CGPoint(x: offset, y: 16.0), size: peerFrames[i].size)
|
||||||
|
peerView.updateLayout(size: peerFrames[i].size)
|
||||||
|
offset += peerFrames[i].width + spacing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ private func avatarRoundImage(size: CGSize, source: UIImage) -> UIImage? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let deviceColorSpace: CGColorSpace = {
|
private let deviceColorSpace: CGColorSpace = {
|
||||||
if #available(iOSApplicationExtension 9.3, *) {
|
if #available(iOSApplicationExtension 9.3, iOS 9.3, *) {
|
||||||
if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) {
|
if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) {
|
||||||
return colorSpace
|
return colorSpace
|
||||||
} else {
|
} else {
|
||||||
@ -94,6 +94,14 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
|
|||||||
|
|
||||||
private let avatarSize = CGSize(width: 50.0, height: 50.0)
|
private let avatarSize = CGSize(width: 50.0, height: 50.0)
|
||||||
|
|
||||||
|
func avatarImage(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) -> UIImage {
|
||||||
|
if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
||||||
|
return roundImage
|
||||||
|
} else {
|
||||||
|
return avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class AvatarView: UIImageView {
|
private final class AvatarView: UIImageView {
|
||||||
init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) {
|
init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) {
|
||||||
super.init(frame: CGRect())
|
super.init(frame: CGRect())
|
||||||
@ -133,7 +141,7 @@ final class PeerView: UIView {
|
|||||||
let fontSize = floor(systemFontSize * 11.0 / 17.0)
|
let fontSize = floor(systemFontSize * 11.0 / 17.0)
|
||||||
|
|
||||||
self.titleLabel.text = title
|
self.titleLabel.text = title
|
||||||
if #available(iOSApplicationExtension 13.0, *) {
|
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
|
||||||
self.titleLabel.textColor = UIColor.label
|
self.titleLabel.textColor = UIColor.label
|
||||||
} else {
|
} else {
|
||||||
self.titleLabel.textColor = primaryColor
|
self.titleLabel.textColor = primaryColor
|
221
Telegram/WidgetKitWidget/TodayViewController.swift
Normal file
221
Telegram/WidgetKitWidget/TodayViewController.swift
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
import UIKit
|
||||||
|
import NotificationCenter
|
||||||
|
import BuildConfig
|
||||||
|
import WidgetItems
|
||||||
|
import AppLockState
|
||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||||
|
return appGroupPath + "/telegram-data"
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Provider: TimelineProvider {
|
||||||
|
public typealias Entry = SimpleEntry
|
||||||
|
|
||||||
|
func placeholder(in context: Context) -> SimpleEntry {
|
||||||
|
return SimpleEntry(date: Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
|
||||||
|
let entry = SimpleEntry(date: Date())
|
||||||
|
completion(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
|
||||||
|
var entries: [SimpleEntry] = []
|
||||||
|
|
||||||
|
let currentDate = Date()
|
||||||
|
for hourOffset in 0 ..< 1 {
|
||||||
|
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
|
||||||
|
let entry = SimpleEntry(date: entryDate)
|
||||||
|
entries.append(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeline = Timeline(entries: entries, policy: .atEnd)
|
||||||
|
completion(timeline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SimpleEntry: TimelineEntry {
|
||||||
|
let date: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PeersWidgetData {
|
||||||
|
case placeholder
|
||||||
|
case empty
|
||||||
|
case locked
|
||||||
|
case data(WidgetData)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PeersWidgetData {
|
||||||
|
static let previewData = PeersWidgetData.placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WidgetView: View {
|
||||||
|
let data: PeersWidgetData
|
||||||
|
|
||||||
|
func placeholder(geometry: GeometryProxy) -> some View {
|
||||||
|
let defaultItemSize: CGFloat = 60.0
|
||||||
|
let defaultPaddingFraction: CGFloat = 0.36
|
||||||
|
|
||||||
|
let rowCount = Int(round(geometry.size.width / (defaultItemSize * (1.0 + defaultPaddingFraction))))
|
||||||
|
let itemSize = floor(geometry.size.width / (CGFloat(rowCount) + defaultPaddingFraction * CGFloat(rowCount - 1)))
|
||||||
|
|
||||||
|
let firstRowY = itemSize / 2.0
|
||||||
|
let secondRowY = itemSize / 2.0 + geometry.size.height - itemSize
|
||||||
|
|
||||||
|
return ZStack {
|
||||||
|
ForEach(0 ..< rowCount * 2, content: { i in
|
||||||
|
return Circle().frame(width: itemSize, height: itemSize).position(x: itemSize / 2.0 + floor(CGFloat(i % rowCount) * itemSize * (1.0 + defaultPaddingFraction)), y: i / rowCount == 0 ? firstRowY : secondRowY).foregroundColor(.gray)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func peersView(geometry: GeometryProxy, peers: WidgetDataPeers) -> some View {
|
||||||
|
let defaultItemSize: CGFloat = 60.0
|
||||||
|
let defaultPaddingFraction: CGFloat = 0.36
|
||||||
|
|
||||||
|
let rowCount = Int(round(geometry.size.width / (defaultItemSize * (1.0 + defaultPaddingFraction))))
|
||||||
|
let itemSize = floor(geometry.size.width / (CGFloat(rowCount) + defaultPaddingFraction * CGFloat(rowCount - 1)))
|
||||||
|
|
||||||
|
let firstRowY = itemSize / 2.0
|
||||||
|
let secondRowY = itemSize / 2.0 + geometry.size.height - itemSize
|
||||||
|
|
||||||
|
return ZStack {
|
||||||
|
ForEach(0 ..< min(peers.peers.count, rowCount * 2), content: { i in
|
||||||
|
Link(destination: URL(string: "\(buildConfig.appSpecificUrlScheme)://localpeer?id=\(peers.peers[i].id)")!, label: {
|
||||||
|
Image(uiImage: avatarImage(accountPeerId: peers.accountPeerId, peer: peers.peers[i], size: CGSize(width: itemSize, height: itemSize)))
|
||||||
|
.frame(width: itemSize, height: itemSize)
|
||||||
|
}).frame(width: itemSize, height: itemSize)
|
||||||
|
.position(x: itemSize / 2.0 + floor(CGFloat(i % rowCount) * itemSize * (1.0 + defaultPaddingFraction)), y: i / rowCount == 0 ? firstRowY : secondRowY)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func peerViews() -> AnyView {
|
||||||
|
switch data {
|
||||||
|
case .placeholder:
|
||||||
|
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 .data(data):
|
||||||
|
switch data {
|
||||||
|
case let .peers(peers):
|
||||||
|
return AnyView(GeometryReader { geometry in
|
||||||
|
peersView(geometry: geometry, peers: peers)
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return AnyView(ZStack {
|
||||||
|
Circle()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color(.systemBackground)
|
||||||
|
peerViews()
|
||||||
|
}
|
||||||
|
.padding(.all)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let buildConfig: BuildConfig = {
|
||||||
|
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||||
|
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||||
|
|
||||||
|
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
||||||
|
return buildConfig
|
||||||
|
}()
|
||||||
|
|
||||||
|
private extension WidgetPresentationData {
|
||||||
|
static var `default` = WidgetPresentationData(
|
||||||
|
applicationLockedString: "Unlock the app to use the widget",
|
||||||
|
applicationStartRequiredString: "Open the app to use the widget",
|
||||||
|
widgetGalleryTitle: "Telegram",
|
||||||
|
widgetGalleryDescription: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private let presentationData: WidgetPresentationData = {
|
||||||
|
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||||
|
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||||
|
return WidgetPresentationData.default
|
||||||
|
}
|
||||||
|
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||||
|
|
||||||
|
let appGroupName = "group.\(baseAppBundleId)"
|
||||||
|
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||||
|
|
||||||
|
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||||
|
return WidgetPresentationData.default
|
||||||
|
}
|
||||||
|
|
||||||
|
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||||
|
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: widgetPresentationDataPath(rootPath: rootPath))), let value = try? JSONDecoder().decode(WidgetPresentationData.self, from: data) {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
return WidgetPresentationData.default
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
func getWidgetData() -> PeersWidgetData {
|
||||||
|
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||||
|
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||||
|
return .placeholder
|
||||||
|
}
|
||||||
|
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||||
|
|
||||||
|
let appGroupName = "group.\(baseAppBundleId)"
|
||||||
|
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||||
|
|
||||||
|
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||||
|
return .placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataPath = rootPath + "/widget-data"
|
||||||
|
|
||||||
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) {
|
||||||
|
return .data(widgetData)
|
||||||
|
} else {
|
||||||
|
return .placeholder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct Static_Widget: Widget {
|
||||||
|
private let kind: String = "Static_Widget"
|
||||||
|
|
||||||
|
public var body: some WidgetConfiguration {
|
||||||
|
return StaticConfiguration(
|
||||||
|
kind: kind,
|
||||||
|
provider: Provider(),
|
||||||
|
content: { entry in
|
||||||
|
WidgetView(data: getWidgetData())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.supportedFamilies([.systemMedium])
|
||||||
|
.configurationDisplayName(presentationData.widgetGalleryTitle)
|
||||||
|
.description(presentationData.widgetGalleryDescription)
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ static_library(
|
|||||||
"Sources/*.h",
|
"Sources/*.h",
|
||||||
]),
|
]),
|
||||||
exported_headers = glob([
|
exported_headers = glob([
|
||||||
"Sources/*.h",
|
"PublicHeaders/**/*.h",
|
||||||
]),
|
]),
|
||||||
deps = [
|
deps = [
|
||||||
],
|
],
|
||||||
|
@ -13,7 +13,7 @@ static_library(
|
|||||||
"Sources/**/*.h",
|
"Sources/**/*.h",
|
||||||
]),
|
]),
|
||||||
deps = [
|
deps = [
|
||||||
"submodules/Database/MurmurHash/Impl:MurMurHashObjC",
|
"//submodules/Database/MurmurHash/Impl:MurMurHashObjC",
|
||||||
],
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
2
third-party/webrtc/BUILD
vendored
2
third-party/webrtc/BUILD
vendored
@ -1,4 +1,4 @@
|
|||||||
use_gn_build = False
|
use_gn_build = True
|
||||||
|
|
||||||
webrtc_libs = [
|
webrtc_libs = [
|
||||||
"libwebrtc.a",
|
"libwebrtc.a",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user