mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Experimental widget settings
This commit is contained in:
parent
70f5732f5f
commit
08040c1598
@ -5888,3 +5888,5 @@ Sorry for the inconvenience.";
|
||||
"Stats.Message.Views" = "Views";
|
||||
"Stats.Message.PublicShares" = "Public Shares";
|
||||
"Stats.Message.PrivateShares" = "Private Shares";
|
||||
|
||||
"ChatSettings.WidgetSettings" = "Widget";
|
||||
|
@ -208,11 +208,13 @@ private final class LoadingShimmerNode: ASDisplayNode {
|
||||
public struct ItemListPeerItemEditing: Equatable {
|
||||
public var editable: Bool
|
||||
public var editing: Bool
|
||||
public var canBeReordered: Bool
|
||||
public var revealed: Bool?
|
||||
|
||||
public init(editable: Bool, editing: Bool, revealed: Bool?) {
|
||||
public init(editable: Bool, editing: Bool, canBeReordered: Bool = false, revealed: Bool?) {
|
||||
self.editable = editable
|
||||
self.editing = editing
|
||||
self.canBeReordered = canBeReordered
|
||||
self.revealed = revealed
|
||||
}
|
||||
}
|
||||
@ -460,6 +462,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
private var layoutParams: (ItemListPeerItem, ListViewItemLayoutParams, ItemListNeighbors, Bool)?
|
||||
|
||||
private var editableControlNode: ItemListEditableControlNode?
|
||||
private var reorderControlNode: ItemListEditableReorderControlNode?
|
||||
|
||||
override public var canBeSelected: Bool {
|
||||
if self.editableControlNode != nil || self.disabledOverlayNode != nil {
|
||||
@ -560,6 +563,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
||||
|
||||
var currentDisabledOverlayNode = self.disabledOverlayNode
|
||||
|
||||
@ -761,12 +765,20 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
}
|
||||
|
||||
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
|
||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
||||
|
||||
let editingOffset: CGFloat
|
||||
var reorderInset: CGFloat = 0.0
|
||||
if item.editing.editing {
|
||||
let sizeAndApply = editableControlLayout(item.presentationData.theme, false)
|
||||
editableControlSizeAndApply = sizeAndApply
|
||||
editingOffset = sizeAndApply.0
|
||||
|
||||
if item.editing.canBeReordered {
|
||||
let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme)
|
||||
reorderControlSizeAndApply = reorderSizeAndApply
|
||||
reorderInset = reorderSizeAndApply.0
|
||||
}
|
||||
} else {
|
||||
editingOffset = 0.0
|
||||
}
|
||||
@ -804,6 +816,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
labelInset += 15.0
|
||||
}
|
||||
|
||||
labelInset += reorderInset
|
||||
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - editingOffset - rightInset - labelLayout.size.width - labelInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
@ -931,6 +945,23 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
})
|
||||
}
|
||||
|
||||
if let reorderControlSizeAndApply = reorderControlSizeAndApply {
|
||||
if strongSelf.reorderControlNode == nil {
|
||||
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate)
|
||||
strongSelf.reorderControlNode = reorderControlNode
|
||||
strongSelf.addSubnode(reorderControlNode)
|
||||
reorderControlNode.alpha = 0.0
|
||||
transition.updateAlpha(node: reorderControlNode, alpha: 1.0)
|
||||
}
|
||||
let reorderControlFrame = CGRect(origin: CGPoint(x: params.width + revealOffset - params.rightInset - reorderControlSizeAndApply.0, y: 0.0), size: CGSize(width: reorderControlSizeAndApply.0, height: layout.contentSize.height))
|
||||
strongSelf.reorderControlNode?.frame = reorderControlFrame
|
||||
} else if let reorderControlNode = strongSelf.reorderControlNode {
|
||||
strongSelf.reorderControlNode = nil
|
||||
transition.updateAlpha(node: reorderControlNode, alpha: 0.0, completion: { [weak reorderControlNode] _ in
|
||||
reorderControlNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
let _ = statusApply()
|
||||
let _ = labelApply()
|
||||
@ -1293,6 +1324,13 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
override public func isReorderable(at point: CGPoint) -> Bool {
|
||||
if let reorderControlNode = self.reorderControlNode, reorderControlNode.frame.contains(point), !self.isDisplayingRevealedOptions {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public final class ItemListPeerItemHeader: ListViewItemHeader {
|
||||
|
@ -226,7 +226,7 @@ public func mergeListsStableWithUpdates<T>(leftList: [T], rightList: [T], isLess
|
||||
}
|
||||
var i = 0
|
||||
while i < rightList.count {
|
||||
if updatedItems[i].1 != getId(rightList[i]) {
|
||||
if updatedItems.count <= i || updatedItems[i].1 != getId(rightList[i]) {
|
||||
updatedItems.insert((rightList[i], getId(rightList[i])), at: i)
|
||||
var previousIndex: Int?
|
||||
for k in 0 ..< leftList.count {
|
||||
|
@ -86,6 +86,7 @@ swift_library(
|
||||
"//submodules/OpenInExternalAppUI:OpenInExternalAppUI",
|
||||
"//submodules/AccountUtils:AccountUtils",
|
||||
"//submodules/AuthTransferUI:AuthTransferUI",
|
||||
"//submodules/WidgetSetupScreen:WidgetSetupScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -12,6 +12,7 @@ import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import OpenInExternalAppUI
|
||||
import WidgetSetupScreen
|
||||
|
||||
private final class DataAndStorageControllerArguments {
|
||||
let openStorageUsage: () -> Void
|
||||
@ -27,9 +28,10 @@ private final class DataAndStorageControllerArguments {
|
||||
let toggleDownloadInBackground: (Bool) -> Void
|
||||
let openBrowserSelection: () -> Void
|
||||
let openIntents: () -> Void
|
||||
let openWidgetSettings: () -> Void
|
||||
let toggleEnableSensitiveContent: (Bool) -> Void
|
||||
|
||||
init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, openVoiceUseLessData: @escaping () -> Void, openSaveIncomingPhotos: @escaping () -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) {
|
||||
init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, openAutomaticDownloadConnectionType: @escaping (AutomaticDownloadConnectionType) -> Void, resetAutomaticDownload: @escaping () -> Void, openVoiceUseLessData: @escaping () -> Void, openSaveIncomingPhotos: @escaping () -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void, toggleAutoplayVideos: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, openWidgetSettings: @escaping () -> Void, toggleEnableSensitiveContent: @escaping (Bool) -> Void) {
|
||||
self.openStorageUsage = openStorageUsage
|
||||
self.openNetworkUsage = openNetworkUsage
|
||||
self.openProxy = openProxy
|
||||
@ -43,6 +45,7 @@ private final class DataAndStorageControllerArguments {
|
||||
self.toggleDownloadInBackground = toggleDownloadInBackground
|
||||
self.openBrowserSelection = openBrowserSelection
|
||||
self.openIntents = openIntents
|
||||
self.openWidgetSettings = openWidgetSettings
|
||||
self.toggleEnableSensitiveContent = toggleEnableSensitiveContent
|
||||
}
|
||||
}
|
||||
@ -87,6 +90,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
case useLessVoiceData(PresentationTheme, String, String)
|
||||
case otherHeader(PresentationTheme, String)
|
||||
case shareSheet(PresentationTheme, String)
|
||||
case widgetSettings(String)
|
||||
case saveIncomingPhotos(PresentationTheme, String)
|
||||
case saveEditedPhotos(PresentationTheme, String, Bool)
|
||||
case openLinksIn(PresentationTheme, String, String)
|
||||
@ -106,7 +110,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
return DataAndStorageSection.autoPlay.rawValue
|
||||
case .voiceCallsHeader, .useLessVoiceData:
|
||||
return DataAndStorageSection.voiceCalls.rawValue
|
||||
case .otherHeader, .shareSheet, .saveIncomingPhotos, .saveEditedPhotos, .openLinksIn, .downloadInBackground, .downloadInBackgroundInfo:
|
||||
case .otherHeader, .shareSheet, .widgetSettings, .saveIncomingPhotos, .saveEditedPhotos, .openLinksIn, .downloadInBackground, .downloadInBackgroundInfo:
|
||||
return DataAndStorageSection.other.rawValue
|
||||
case .connectionHeader, .connectionProxy:
|
||||
return DataAndStorageSection.connection.rawValue
|
||||
@ -143,22 +147,24 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
return 11
|
||||
case .shareSheet:
|
||||
return 12
|
||||
case .saveIncomingPhotos:
|
||||
case .widgetSettings:
|
||||
return 13
|
||||
case .saveEditedPhotos:
|
||||
case .saveIncomingPhotos:
|
||||
return 14
|
||||
case .openLinksIn:
|
||||
case .saveEditedPhotos:
|
||||
return 15
|
||||
case .downloadInBackground:
|
||||
case .openLinksIn:
|
||||
return 16
|
||||
case .downloadInBackgroundInfo:
|
||||
case .downloadInBackground:
|
||||
return 17
|
||||
case .connectionHeader:
|
||||
case .downloadInBackgroundInfo:
|
||||
return 18
|
||||
case .connectionProxy:
|
||||
case .connectionHeader:
|
||||
return 19
|
||||
case .enableSensitiveContent:
|
||||
case .connectionProxy:
|
||||
return 20
|
||||
case .enableSensitiveContent:
|
||||
return 21
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,6 +248,12 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .widgetSettings(text):
|
||||
if case .widgetSettings(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .saveIncomingPhotos(lhsTheme, lhsText):
|
||||
if case let .saveIncomingPhotos(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -346,6 +358,10 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openIntents()
|
||||
})
|
||||
case let .widgetSettings(text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openWidgetSettings()
|
||||
})
|
||||
case let .saveIncomingPhotos(theme, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openSaveIncomingPhotos()
|
||||
@ -489,6 +505,9 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat
|
||||
if #available(iOSApplicationExtension 13.2, iOS 13.2, *) {
|
||||
entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings))
|
||||
}
|
||||
if #available(iOSApplicationExtension 14.0, iOS 14.0, *) {
|
||||
entries.append(.widgetSettings(presentationData.strings.ChatSettings_WidgetSettings))
|
||||
}
|
||||
entries.append(.saveIncomingPhotos(presentationData.theme, presentationData.strings.Settings_SaveIncomingPhotos))
|
||||
entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos))
|
||||
entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser))
|
||||
@ -641,6 +660,9 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
|
||||
}, openIntents: {
|
||||
let controller = intentsSettingsController(context: context)
|
||||
pushControllerImpl?(controller)
|
||||
}, openWidgetSettings: {
|
||||
let controller = widgetSetupScreen(context: context)
|
||||
pushControllerImpl?(controller)
|
||||
}, toggleEnableSensitiveContent: { value in
|
||||
let _ = (contentSettingsConfiguration.get()
|
||||
|> take(1)
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -57,6 +57,7 @@ private var telegramUIDeclaredEncodables: Void = {
|
||||
declareEncodable(IntentsSettings.self, f: { IntentsSettings(decoder: $0) })
|
||||
declareEncodable(CachedGeocode.self, f: { CachedGeocode(decoder: $0) })
|
||||
declareEncodable(ChatListFilterSettings.self, f: { ChatListFilterSettings(decoder: $0) })
|
||||
declareEncodable(WidgetSettings.self, f: { WidgetSettings(decoder: $0) })
|
||||
return
|
||||
}()
|
||||
|
||||
|
@ -7,6 +7,7 @@ import WidgetItems
|
||||
import TelegramPresentationData
|
||||
import NotificationsPresentationData
|
||||
import WidgetKit
|
||||
import TelegramUIPreferences
|
||||
|
||||
final class WidgetDataContext {
|
||||
private var currentAccount: Account?
|
||||
@ -24,7 +25,7 @@ final class WidgetDataContext {
|
||||
return .single(.notAuthorized)
|
||||
}
|
||||
|
||||
enum RecentPeers {
|
||||
enum CombinedRecentPeers {
|
||||
struct Unread {
|
||||
var count: Int32
|
||||
var isMuted: Bool
|
||||
@ -34,19 +35,47 @@ final class WidgetDataContext {
|
||||
case peers(peers: [Peer], unread: [PeerId: Unread])
|
||||
}
|
||||
|
||||
let recent: Signal<RecentPeers, NoError> = recentPeers(account: account)
|
||||
|> mapToSignal { recent -> Signal<RecentPeers, NoError> in
|
||||
let preferencesKey: PostboxViewKey = .preferences(keys: Set([
|
||||
ApplicationSpecificPreferencesKeys.widgetSettings
|
||||
]))
|
||||
let sourcePeers: Signal<RecentPeers, NoError> = account.postbox.combinedView(keys: [
|
||||
preferencesKey
|
||||
])
|
||||
|> mapToSignal { views -> Signal<RecentPeers, NoError> in
|
||||
let widgetSettings: WidgetSettings
|
||||
if let view = views.views[preferencesKey] as? PreferencesView, let value = view.values[ApplicationSpecificPreferencesKeys.widgetSettings] as? WidgetSettings {
|
||||
widgetSettings = value
|
||||
} else {
|
||||
widgetSettings = .default
|
||||
}
|
||||
|
||||
if widgetSettings.useHints {
|
||||
return recentPeers(account: account)
|
||||
} else {
|
||||
return account.postbox.transaction { transaction -> RecentPeers in
|
||||
return .peers(widgetSettings.peers.compactMap { peerId -> Peer? in
|
||||
guard let peer = transaction.getPeer(peerId) else {
|
||||
return nil
|
||||
}
|
||||
return peer
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let recent: Signal<CombinedRecentPeers, NoError> = sourcePeers
|
||||
|> mapToSignal { recent -> Signal<CombinedRecentPeers, NoError> in
|
||||
switch recent {
|
||||
case .disabled:
|
||||
return .single(.disabled)
|
||||
case let .peers(peers):
|
||||
return combineLatest(queue: .mainQueue(), peers.filter { !$0.isDeleted }.map { account.postbox.peerView(id: $0.id)}) |> mapToSignal { peerViews -> Signal<RecentPeers, NoError> in
|
||||
return combineLatest(queue: .mainQueue(), peers.filter { !$0.isDeleted }.map { account.postbox.peerView(id: $0.id)}) |> mapToSignal { peerViews -> Signal<CombinedRecentPeers, NoError> in
|
||||
return account.postbox.unreadMessageCountsView(items: peerViews.map {
|
||||
.peer($0.peerId)
|
||||
})
|
||||
|> map { values -> RecentPeers in
|
||||
|> map { values -> CombinedRecentPeers in
|
||||
var peers: [Peer] = []
|
||||
var unread: [PeerId: RecentPeers.Unread] = [:]
|
||||
var unread: [PeerId: CombinedRecentPeers.Unread] = [:]
|
||||
for peerView in peerViews {
|
||||
if let peer = peerViewMainPeer(peerView) {
|
||||
var isMuted: Bool = false
|
||||
@ -61,7 +90,7 @@ final class WidgetDataContext {
|
||||
|
||||
let unreadCount = values.count(for: .peer(peerView.peerId))
|
||||
if let unreadCount = unreadCount, unreadCount > 0 {
|
||||
unread[peerView.peerId] = RecentPeers.Unread(count: Int32(unreadCount), isMuted: isMuted)
|
||||
unread[peerView.peerId] = CombinedRecentPeers.Unread(count: Int32(unreadCount), isMuted: isMuted)
|
||||
}
|
||||
|
||||
peers.append(peer)
|
||||
@ -80,13 +109,10 @@ final class WidgetDataContext {
|
||||
return .disabled
|
||||
case let .peers(peers, unread):
|
||||
return .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in
|
||||
guard let user = peer as? TelegramUser else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var name: String = ""
|
||||
var lastName: String?
|
||||
|
||||
if let user = peer as? TelegramUser {
|
||||
if let firstName = user.firstName {
|
||||
name = firstName
|
||||
lastName = user.lastName
|
||||
@ -95,6 +121,9 @@ final class WidgetDataContext {
|
||||
} else if let phone = user.phone, !phone.isEmpty {
|
||||
name = phone
|
||||
}
|
||||
} else {
|
||||
name = peer.debugDisplayTitle
|
||||
}
|
||||
|
||||
var badge: WidgetDataPeer.Badge?
|
||||
if let unreadValue = unread[peer.id], unreadValue.count > 0 {
|
||||
@ -104,7 +133,7 @@ final class WidgetDataContext {
|
||||
)
|
||||
}
|
||||
|
||||
return WidgetDataPeer(id: user.id.toInt64(), name: name, lastName: lastName, letters: user.displayLetters, avatarPath: smallestImageRepresentation(user.photo).flatMap { representation in
|
||||
return WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
|
||||
return account.postbox.mediaBox.resourcePath(representation.resource)
|
||||
}, badge: badge)
|
||||
}))
|
||||
|
@ -7,12 +7,14 @@ private enum ApplicationSpecificPreferencesKeyValues: Int32 {
|
||||
case voipDerivedState = 16
|
||||
case chatArchiveSettings = 17
|
||||
case chatListFilterSettings = 18
|
||||
case widgetSettings = 19
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificPreferencesKeys {
|
||||
public static let voipDerivedState = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.voipDerivedState.rawValue)
|
||||
public static let chatArchiveSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.chatArchiveSettings.rawValue)
|
||||
public static let chatListFilterSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.chatListFilterSettings.rawValue)
|
||||
public static let widgetSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.widgetSettings.rawValue)
|
||||
}
|
||||
|
||||
private enum ApplicationSpecificSharedDataKeyValues: Int32 {
|
||||
|
@ -0,0 +1,61 @@
|
||||
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
public struct WidgetSettings: PreferencesEntry, Equatable {
|
||||
public var useHints: Bool
|
||||
public var peers: [PeerId]
|
||||
|
||||
public static var `default`: WidgetSettings {
|
||||
return WidgetSettings(
|
||||
useHints: true,
|
||||
peers: []
|
||||
)
|
||||
}
|
||||
|
||||
public init(
|
||||
useHints: Bool,
|
||||
peers: [PeerId]
|
||||
) {
|
||||
self.useHints = useHints
|
||||
self.peers = peers
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.useHints = decoder.decodeBoolForKey("useHints", orElse: true)
|
||||
self.peers = decoder.decodeInt64ArrayForKey("peers").map { PeerId($0) }
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeBool(self.useHints, forKey: "useHints")
|
||||
encoder.encodeInt64Array(self.peers.map { $0.toInt64() }, forKey: "peers")
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
if let to = to as? WidgetSettings {
|
||||
return self == to
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateWidgetSettingsInteractively(postbox: Postbox, _ f: @escaping (WidgetSettings) -> WidgetSettings) -> Signal<Never, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
updateWidgetSettingsInteractively(transaction: transaction, f)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func updateWidgetSettingsInteractively(transaction: Transaction, _ f: @escaping (WidgetSettings) -> WidgetSettings) {
|
||||
transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.widgetSettings, { entry in
|
||||
let currentSettings: WidgetSettings
|
||||
if let entry = entry as? WidgetSettings {
|
||||
currentSettings = entry
|
||||
} else {
|
||||
currentSettings = .default
|
||||
}
|
||||
return f(currentSettings)
|
||||
})
|
||||
}
|
25
submodules/WidgetSetupScreen/BUILD
Normal file
25
submodules/WidgetSetupScreen/BUILD
Normal file
@ -0,0 +1,25 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "WidgetSetupScreen",
|
||||
module_name = "WidgetSetupScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/SyncCore:SyncCore",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/ItemListUI:ItemListUI",
|
||||
"//submodules/ItemListPeerItem:ItemListPeerItem",
|
||||
"//submodules/ItemListPeerActionItem:ItemListPeerActionItem",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
453
submodules/WidgetSetupScreen/Sources/WidgetSetupScreen.swift
Normal file
453
submodules/WidgetSetupScreen/Sources/WidgetSetupScreen.swift
Normal file
@ -0,0 +1,453 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import ItemListPeerItem
|
||||
import ItemListPeerActionItem
|
||||
|
||||
private final class Arguments {
|
||||
let context: AccountContext
|
||||
|
||||
let updateUseHints: (Bool) -> Void
|
||||
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
||||
let removePeer: (PeerId) -> Void
|
||||
let addPeer: () -> Void
|
||||
let openPeer: (PeerId) -> Void
|
||||
|
||||
init(context: AccountContext, updateUseHints: @escaping (Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (PeerId) -> Void) {
|
||||
self.context = context
|
||||
self.updateUseHints = updateUseHints
|
||||
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
||||
self.removePeer = removePeer
|
||||
self.addPeer = addPeer
|
||||
self.openPeer = openPeer
|
||||
}
|
||||
}
|
||||
|
||||
private enum WidgetSetupScreenEntry: ItemListNodeEntry {
|
||||
enum Section: Int32 {
|
||||
case mode
|
||||
case peers
|
||||
}
|
||||
|
||||
enum StableId: Hashable {
|
||||
case useHints
|
||||
case peersHeaderItem
|
||||
case add
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
case useHints(String, Bool)
|
||||
case peersHeaderItem(String)
|
||||
case peerItem(Int32, PresentationDateTimeFormat, PresentationPersonNameOrder, SelectivePrivacyPeer, ItemListPeerItemEditing, Bool)
|
||||
case addItem(String, Bool)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .useHints:
|
||||
return Section.mode.rawValue
|
||||
case .peersHeaderItem, .peerItem:
|
||||
return Section.peers.rawValue
|
||||
case .addItem:
|
||||
return Section.peers.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: StableId {
|
||||
switch self {
|
||||
case .useHints:
|
||||
return .useHints
|
||||
case .peersHeaderItem:
|
||||
return .peersHeaderItem
|
||||
case let .peerItem(_, _, _, peer, _, _):
|
||||
return .peer(peer.peer.id)
|
||||
case .addItem:
|
||||
return .add
|
||||
}
|
||||
}
|
||||
|
||||
var sortIndex: Int32 {
|
||||
switch self {
|
||||
case .useHints:
|
||||
return 0
|
||||
case .peersHeaderItem:
|
||||
return 1
|
||||
case .addItem:
|
||||
return 2
|
||||
case let .peerItem(index, _, _, _, _, _):
|
||||
return 10 + index
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: WidgetSetupScreenEntry, rhs: WidgetSetupScreenEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .useHints(text, value):
|
||||
if case .useHints(text, value) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peersHeaderItem(text):
|
||||
if case .peersHeaderItem(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerItem(lhsIndex, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsEditing, lhsEnabled):
|
||||
if case let .peerItem(rhsIndex, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsEditing, rhsEnabled) = rhs {
|
||||
if lhsIndex != rhsIndex {
|
||||
return false
|
||||
}
|
||||
if lhsDateTimeFormat != rhsDateTimeFormat {
|
||||
return false
|
||||
}
|
||||
if lhsNameOrder != rhsNameOrder {
|
||||
return false
|
||||
}
|
||||
if lhsPeer != rhsPeer {
|
||||
return false
|
||||
}
|
||||
if lhsEditing != rhsEditing {
|
||||
return false
|
||||
}
|
||||
if lhsEnabled != rhsEnabled {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .addItem(lhsText, lhsEditing):
|
||||
if case let .addItem(rhsText, rhsEditing) = rhs, lhsText == rhsText, lhsEditing == rhsEditing {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: WidgetSetupScreenEntry, rhs: WidgetSetupScreenEntry) -> Bool {
|
||||
return lhs.sortIndex < rhs.sortIndex
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! Arguments
|
||||
switch self {
|
||||
case let .useHints(text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateUseHints(value)
|
||||
})
|
||||
case let .peersHeaderItem(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .peerItem(_, dateTimeFormat, nameOrder, peer, editing, enabled):
|
||||
var text: ItemListPeerItemText = .none
|
||||
if let group = peer.peer as? TelegramGroup {
|
||||
text = .text(presentationData.strings.Conversation_StatusMembers(Int32(group.participantCount)))
|
||||
} else if let channel = peer.peer as? TelegramChannel {
|
||||
if let participantCount = peer.participantCount {
|
||||
text = .text(presentationData.strings.Conversation_StatusMembers(Int32(participantCount)))
|
||||
} else {
|
||||
switch channel.info {
|
||||
case .group:
|
||||
text = .text(presentationData.strings.Group_Status)
|
||||
case .broadcast:
|
||||
text = .text(presentationData.strings.Channel_Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, context: arguments.context, peer: peer.peer, presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: {
|
||||
arguments.openPeer(peer.peer.id)
|
||||
}, setPeerIdWithRevealedOptions: { previousId, id in
|
||||
arguments.setPeerIdWithRevealedOptions(previousId, id)
|
||||
}, removePeer: { peerId in
|
||||
arguments.removePeer(peerId)
|
||||
})
|
||||
case let .addItem(text, editing):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: text, sectionId: self.section, editing: editing, action: {
|
||||
arguments.addPeer()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct WidgetSetupScreenControllerState: Equatable {
|
||||
var editing: Bool = false
|
||||
var peerIdWithRevealedOptions: PeerId? = nil
|
||||
}
|
||||
|
||||
private func selectivePrivacyPeersControllerEntries(presentationData: PresentationData, state: WidgetSetupScreenControllerState, useHints: Bool, peers: [SelectivePrivacyPeer]) -> [WidgetSetupScreenEntry] {
|
||||
var entries: [WidgetSetupScreenEntry] = []
|
||||
|
||||
entries.append(.useHints("Show Recent Chats", useHints))
|
||||
|
||||
if !useHints {
|
||||
entries.append(.peersHeaderItem(presentationData.strings.Privacy_ChatsTitle))
|
||||
entries.append(.addItem(presentationData.strings.Privacy_AddNewPeer, state.editing))
|
||||
var index: Int32 = 0
|
||||
for peer in peers {
|
||||
entries.append(.peerItem(index, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: state.editing, canBeReordered: state.editing, revealed: peer.peer.id == state.peerIdWithRevealedOptions), true))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
public func widgetSetupScreen(context: AccountContext) -> ViewController {
|
||||
let statePromise = ValuePromise(WidgetSetupScreenControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: WidgetSetupScreenControllerState())
|
||||
let updateState: ((WidgetSetupScreenControllerState) -> WidgetSetupScreenControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
let addPeerDisposable = MetaDisposable()
|
||||
actionsDisposable.add(addPeerDisposable)
|
||||
|
||||
let arguments = Arguments(context: context, updateUseHints: { value in
|
||||
let _ = (updateWidgetSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||
var settings = settings
|
||||
settings.useHints = value
|
||||
return settings
|
||||
})
|
||||
|> deliverOnMainQueue).start()
|
||||
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||
updateState { state in
|
||||
var state = state
|
||||
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
|
||||
state.peerIdWithRevealedOptions = peerId
|
||||
}
|
||||
return state
|
||||
}
|
||||
}, removePeer: { memberId in
|
||||
|
||||
}, addPeer: {
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: false), options: []))
|
||||
addPeerDisposable.set((controller.result
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] result in
|
||||
var peerIds: [ContactListPeerId] = []
|
||||
if case let .result(peerIdsValue, _) = result {
|
||||
peerIds = peerIdsValue
|
||||
}
|
||||
|
||||
let _ = (updateWidgetSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||
var settings = settings
|
||||
for peerId in peerIds {
|
||||
switch peerId {
|
||||
case let .peer(peerId):
|
||||
settings.peers.removeAll(where: { $0 == peerId })
|
||||
settings.peers.insert(peerId, at: 0)
|
||||
case .deviceContact:
|
||||
break
|
||||
}
|
||||
}
|
||||
return settings
|
||||
})
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
controller?.dismiss()
|
||||
})
|
||||
}))
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, openPeer: { peerId in
|
||||
let _ = (context.account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) else {
|
||||
return
|
||||
}
|
||||
pushControllerImpl?(controller)
|
||||
})
|
||||
})
|
||||
|
||||
var previousPeers: [SelectivePrivacyPeer]?
|
||||
var previousState: WidgetSetupScreenControllerState?
|
||||
|
||||
struct InputData {
|
||||
var settings: WidgetSettings
|
||||
var peers: [SelectivePrivacyPeer]
|
||||
}
|
||||
|
||||
let preferencesKey: PostboxViewKey = .preferences(keys: Set([
|
||||
ApplicationSpecificPreferencesKeys.widgetSettings
|
||||
]))
|
||||
|
||||
let inputData: Signal<InputData, NoError> = context.account.postbox.combinedView(keys: [
|
||||
preferencesKey
|
||||
])
|
||||
|> mapToSignal { views -> Signal<InputData, NoError> in
|
||||
let widgetSettings: WidgetSettings
|
||||
if let view = views.views[preferencesKey] as? PreferencesView, let value = view.values[ApplicationSpecificPreferencesKeys.widgetSettings] as? WidgetSettings {
|
||||
widgetSettings = value
|
||||
} else {
|
||||
widgetSettings = .default
|
||||
}
|
||||
|
||||
return context.account.postbox.transaction { transaction -> InputData in
|
||||
return InputData(
|
||||
settings: widgetSettings,
|
||||
peers: widgetSettings.peers.compactMap { peerId -> SelectivePrivacyPeer? in
|
||||
guard let peer = transaction.getPeer(peerId) else {
|
||||
return nil
|
||||
}
|
||||
return SelectivePrivacyPeer(peer: peer, participantCount: nil)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), inputData)
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, state, inputData -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
var rightNavigationButton: ItemListNavigationButton?
|
||||
if !inputData.peers.isEmpty {
|
||||
if state.editing {
|
||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.editing = false
|
||||
return state
|
||||
}
|
||||
})
|
||||
} else {
|
||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.editing = true
|
||||
return state
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let previous = previousPeers
|
||||
previousPeers = inputData.peers
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Widget"), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
|
||||
var animated = true
|
||||
if let previous = previous {
|
||||
if previous.count <= inputData.peers.count {
|
||||
if Set(previous.map { $0.peer.id }) == Set(inputData.peers.map { $0.peer.id }) && previous.map({ $0.peer.id }) != inputData.peers.map({ $0.peer.id }) {
|
||||
} else {
|
||||
animated = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
animated = false
|
||||
}
|
||||
if let previousState = previousState {
|
||||
if previousState.editing != state.editing {
|
||||
animated = true
|
||||
}
|
||||
} else {
|
||||
animated = false
|
||||
}
|
||||
|
||||
previousState = state
|
||||
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: selectivePrivacyPeersControllerEntries(presentationData: presentationData, state: state, useHints: inputData.settings.useHints, peers: inputData.peers), style: .blocks, emptyStateItem: nil, animateChanges: animated)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|> afterDisposed {
|
||||
actionsDisposable.dispose()
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
dismissImpl = { [weak controller] in
|
||||
if let controller = controller, let navigationController = controller.navigationController as? NavigationController {
|
||||
navigationController.filterController(controller, animated: true)
|
||||
}
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, p in
|
||||
if let controller = controller {
|
||||
controller.present(c, in: .window(.root), with: p)
|
||||
}
|
||||
}
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
if let navigationController = controller?.navigationController as? NavigationController {
|
||||
navigationController.pushViewController(c)
|
||||
}
|
||||
}
|
||||
|
||||
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [WidgetSetupScreenEntry]) -> Signal<Bool, NoError> in
|
||||
let fromEntry = entries[fromIndex]
|
||||
guard case let .peerItem(_, _, _, fromPeer, _, _) = fromEntry else {
|
||||
return .single(false)
|
||||
}
|
||||
var referencePeerId: PeerId?
|
||||
var beforeAll = false
|
||||
var afterAll = false
|
||||
if toIndex < entries.count {
|
||||
switch entries[toIndex] {
|
||||
case let .peerItem(_, _, _, peer, _, _):
|
||||
referencePeerId = peer.peer.id
|
||||
default:
|
||||
if entries[toIndex] < fromEntry {
|
||||
beforeAll = true
|
||||
} else {
|
||||
afterAll = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
afterAll = true
|
||||
}
|
||||
|
||||
return context.account.postbox.transaction { transaction -> Bool in
|
||||
var updatedOrder = false
|
||||
|
||||
updateWidgetSettingsInteractively(transaction: transaction, { settings in
|
||||
let initialPeers = settings.peers
|
||||
var settings = settings
|
||||
|
||||
if let index = settings.peers.firstIndex(of: fromPeer.peer.id) {
|
||||
settings.peers.remove(at: index)
|
||||
}
|
||||
if let referencePeerId = referencePeerId {
|
||||
var inserted = false
|
||||
for i in 0 ..< settings.peers.count {
|
||||
if settings.peers[i] == referencePeerId {
|
||||
if fromIndex < toIndex {
|
||||
settings.peers.insert(fromPeer.peer.id, at: i + 1)
|
||||
} else {
|
||||
settings.peers.insert(fromPeer.peer.id, at: i)
|
||||
}
|
||||
inserted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inserted {
|
||||
settings.peers.append(fromPeer.peer.id)
|
||||
}
|
||||
} else if beforeAll {
|
||||
settings.peers.insert(fromPeer.peer.id, at: 0)
|
||||
} else if afterAll {
|
||||
settings.peers.append(fromPeer.peer.id)
|
||||
}
|
||||
|
||||
if initialPeers != settings.peers {
|
||||
updatedOrder = true
|
||||
}
|
||||
return settings
|
||||
})
|
||||
|
||||
return updatedOrder
|
||||
}
|
||||
})
|
||||
|
||||
return controller
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user