Experimental widget settings

This commit is contained in:
Ali 2020-10-30 20:58:02 +04:00
parent 70f5732f5f
commit 08040c1598
13 changed files with 1691 additions and 1056 deletions

View File

@ -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";

View File

@ -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 {

View File

@ -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 {

View File

@ -86,6 +86,7 @@ swift_library(
"//submodules/OpenInExternalAppUI:OpenInExternalAppUI",
"//submodules/AccountUtils:AccountUtils",
"//submodules/AuthTransferUI:AuthTransferUI",
"//submodules/WidgetSetupScreen:WidgetSetupScreen",
],
visibility = [
"//visibility:public",

View File

@ -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)

View File

@ -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
}()

View File

@ -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)
}))

View File

@ -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 {

View File

@ -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)
})
}

View 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",
],
)

View 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
}