mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 08:49:47 +00:00
Various improvements
This commit is contained in:
parent
cd3c27fcc2
commit
d486f529a3
@ -14418,3 +14418,7 @@ Sorry for the inconvenience.";
|
||||
"Monoforum.DeleteTopic.Title" = "Delete %@";
|
||||
"Channel.RemoveFeeAlert.Text" = "Are you sure you want to allow **%@** to message your channel for free?";
|
||||
"StarsBalance.ChannelBalance" = "Your channel balance is %@";
|
||||
|
||||
"Chat.TitleJoinGroupCall" = "Join";
|
||||
"Invitation.JoinGroupCall.EnableMicrophone" = "Switch on the microphone";
|
||||
"Chat.PinnedGroupCallTitle" = "Pinned Group Call";
|
||||
|
||||
@ -996,8 +996,9 @@ public enum JoinSubjectScreenMode {
|
||||
public let members: [EnginePeer]
|
||||
public let totalMemberCount: Int
|
||||
public let info: JoinCallLinkInformation
|
||||
public let enableMicrophoneByDefault: Bool
|
||||
|
||||
public init(id: Int64, accessHash: Int64, slug: String, inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int, info: JoinCallLinkInformation) {
|
||||
public init(id: Int64, accessHash: Int64, slug: String, inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int, info: JoinCallLinkInformation, enableMicrophoneByDefault: Bool) {
|
||||
self.id = id
|
||||
self.accessHash = accessHash
|
||||
self.slug = slug
|
||||
@ -1005,6 +1006,7 @@ public enum JoinSubjectScreenMode {
|
||||
self.members = members
|
||||
self.totalMemberCount = totalMemberCount
|
||||
self.info = info
|
||||
self.enableMicrophoneByDefault = enableMicrophoneByDefault
|
||||
}
|
||||
}
|
||||
|
||||
@ -1376,7 +1378,7 @@ public protocol AccountContext: AnyObject {
|
||||
|
||||
func scheduleGroupCall(peerId: PeerId, parentController: ViewController)
|
||||
func joinGroupCall(peerId: PeerId, invite: String?, requestJoinAsPeerId: ((@escaping (PeerId?) -> Void) -> Void)?, activeCall: EngineGroupCallDescription)
|
||||
func joinConferenceCall(call: JoinCallLinkInformation, isVideo: Bool)
|
||||
func joinConferenceCall(call: JoinCallLinkInformation, isVideo: Bool, unmuteByDefault: Bool)
|
||||
func requestCall(peerId: PeerId, isVideo: Bool, completion: @escaping () -> Void)
|
||||
}
|
||||
|
||||
|
||||
@ -429,6 +429,18 @@ public struct PresentationGroupCallInvitedPeer: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct PresentationGroupCallPersistentSettings: Codable {
|
||||
public static let `default` = PresentationGroupCallPersistentSettings(
|
||||
isMicrophoneEnabledByDefault: true
|
||||
)
|
||||
|
||||
public var isMicrophoneEnabledByDefault: Bool
|
||||
|
||||
public init(isMicrophoneEnabledByDefault: Bool) {
|
||||
self.isMicrophoneEnabledByDefault = isMicrophoneEnabledByDefault
|
||||
}
|
||||
}
|
||||
|
||||
public protocol PresentationGroupCall: AnyObject {
|
||||
var account: Account { get }
|
||||
var accountContext: AccountContext { get }
|
||||
@ -580,6 +592,7 @@ public protocol PresentationCallManager: AnyObject {
|
||||
reference: InternalGroupCallReference,
|
||||
beginWithVideo: Bool,
|
||||
invitePeerIds: [EnginePeer.Id],
|
||||
endCurrentIfAny: Bool
|
||||
endCurrentIfAny: Bool,
|
||||
unmuteByDefault: Bool
|
||||
) -> JoinGroupCallManagerResult
|
||||
}
|
||||
|
||||
@ -278,7 +278,8 @@ public final class CallListController: TelegramBaseController {
|
||||
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
||||
beginWithVideo: isVideo,
|
||||
invitePeerIds: peerIds,
|
||||
endCurrentIfAny: true
|
||||
endCurrentIfAny: true,
|
||||
unmuteByDefault: true
|
||||
)
|
||||
completion?()
|
||||
}
|
||||
@ -714,7 +715,17 @@ public final class CallListController: TelegramBaseController {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.context.joinConferenceCall(call: resolvedCallLink, isVideo: conferenceCall.flags.contains(.isVideo))
|
||||
|
||||
let _ = (self.context.engine.calls.getGroupCallPersistentSettings(callId: resolvedCallLink.id)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let value: PresentationGroupCallPersistentSettings = value?.get(PresentationGroupCallPersistentSettings.self) ?? PresentationGroupCallPersistentSettings.default
|
||||
|
||||
self.context.joinConferenceCall(call: resolvedCallLink, isVideo: conferenceCall.flags.contains(.isVideo), unmuteByDefault: value.isMicrophoneEnabledByDefault)
|
||||
})
|
||||
}, error: { [weak self] error in
|
||||
guard let self else {
|
||||
return
|
||||
|
||||
@ -2163,8 +2163,14 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
switch peerValue.peer {
|
||||
case .user, .secretChat:
|
||||
if let peerPresence = peerPresence, case .present = peerPresence.status {
|
||||
inputActivities = inputActivitiesValue
|
||||
if let peerPresence = peerPresence {
|
||||
if case .present = peerPresence.status {
|
||||
inputActivities = inputActivitiesValue
|
||||
} else if item.context.sharedContext.immediateExperimentalUISettings.alwaysDisplayTyping {
|
||||
inputActivities = inputActivitiesValue
|
||||
} else {
|
||||
inputActivities = nil
|
||||
}
|
||||
} else {
|
||||
inputActivities = nil
|
||||
}
|
||||
|
||||
@ -72,6 +72,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case redactSensitiveData(PresentationTheme, Bool)
|
||||
case keepChatNavigationStack(PresentationTheme, Bool)
|
||||
case skipReadHistory(PresentationTheme, Bool)
|
||||
case alwaysDisplayTyping(Bool)
|
||||
case dustEffect(Bool)
|
||||
case crashOnSlowQueries(PresentationTheme, Bool)
|
||||
case crashOnMemoryPressure(PresentationTheme, Bool)
|
||||
@ -131,7 +132,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.logging.rawValue
|
||||
case .webViewInspection, .resetWebViewCache:
|
||||
return DebugControllerSection.web.rawValue
|
||||
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
|
||||
case .keepChatNavigationStack, .skipReadHistory, .alwaysDisplayTyping, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .allForumsHaveTabs, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
@ -182,8 +183,10 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 15
|
||||
case .skipReadHistory:
|
||||
return 16
|
||||
case .dustEffect:
|
||||
case .alwaysDisplayTyping:
|
||||
return 17
|
||||
case .dustEffect:
|
||||
return 18
|
||||
case .crashOnSlowQueries:
|
||||
return 20
|
||||
case .crashOnMemoryPressure:
|
||||
@ -959,6 +962,14 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return settings
|
||||
}).start()
|
||||
})
|
||||
case let .alwaysDisplayTyping(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Show Typing", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||
var settings = settings
|
||||
settings.alwaysDisplayTyping = value
|
||||
return settings
|
||||
}).start()
|
||||
})
|
||||
case let .dustEffect(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Dust Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||
@ -1494,6 +1505,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
||||
#if DEBUG
|
||||
entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory))
|
||||
#endif
|
||||
entries.append(.alwaysDisplayTyping(experimentalSettings.alwaysDisplayTyping))
|
||||
entries.append(.dustEffect(experimentalSettings.dustEffect))
|
||||
}
|
||||
entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries))
|
||||
|
||||
@ -1090,7 +1090,17 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.context.joinConferenceCall(call: resolvedCallLink, isVideo: conferenceCall.flags.contains(.isVideo))
|
||||
|
||||
let _ = (self.context.engine.calls.getGroupCallPersistentSettings(callId: resolvedCallLink.id)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let value: PresentationGroupCallPersistentSettings = value?.get(PresentationGroupCallPersistentSettings.self) ?? PresentationGroupCallPersistentSettings.default
|
||||
|
||||
self.context.joinConferenceCall(call: resolvedCallLink, isVideo: conferenceCall.flags.contains(.isVideo), unmuteByDefault: value.isMicrophoneEnabledByDefault)
|
||||
})
|
||||
}, error: { [weak self] error in
|
||||
guard let self else {
|
||||
return
|
||||
|
||||
@ -64,14 +64,23 @@ public final class CallKitIntegration {
|
||||
}
|
||||
|
||||
func answerCall(uuid: UUID) {
|
||||
#if DEBUG
|
||||
print("CallKitIntegration: Answer call \(uuid)")
|
||||
#endif
|
||||
sharedProviderDelegate?.answerCall(uuid: uuid)
|
||||
}
|
||||
|
||||
public func dropCall(uuid: UUID) {
|
||||
#if DEBUG
|
||||
print("CallKitIntegration: Drop call \(uuid)")
|
||||
#endif
|
||||
sharedProviderDelegate?.dropCall(uuid: uuid)
|
||||
}
|
||||
|
||||
public func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, phoneNumber: String?, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) {
|
||||
#if DEBUG
|
||||
print("CallKitIntegration: Report incoming call \(uuid)")
|
||||
#endif
|
||||
sharedProviderDelegate?.reportIncomingCall(uuid: uuid, stableId: stableId, handle: handle, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle, completion: completion)
|
||||
}
|
||||
|
||||
@ -183,6 +192,8 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
|
||||
}
|
||||
|
||||
func dropCall(uuid: UUID) {
|
||||
self.alreadyReportedIncomingCalls.insert(uuid)
|
||||
|
||||
Logger.shared.log("CallKitIntegration", "report call ended \(uuid)")
|
||||
|
||||
self.provider.reportCall(with: uuid, endedAt: nil, reason: CXCallEndedReason.remoteEnded)
|
||||
|
||||
@ -362,9 +362,58 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
)
|
||||
strongSelf.updateCurrentCall(call)
|
||||
}))
|
||||
} else if let currentCall = self.currentCall, currentCall.peerId == firstState.1.id, currentCall.peerId.id._internalGetInt64Value() < firstState.0.account.peerId.id._internalGetInt64Value() {
|
||||
let _ = currentCall.hangUp().startStandalone()
|
||||
|
||||
self.currentCallDisposable.set((combineLatest(
|
||||
firstState.0.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1),
|
||||
accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] preferences, sharedData in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.currentUpgradedToConferenceCallId == firstState.2.id {
|
||||
return
|
||||
}
|
||||
|
||||
let configuration = preferences.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue
|
||||
let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings]?.get(AutodownloadSettings.self) ?? .defaultSettings
|
||||
let experimentalSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) ?? .defaultSettings
|
||||
let appConfiguration = preferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||
|
||||
let call = PresentationCallImpl(
|
||||
context: firstState.0,
|
||||
audioSession: strongSelf.audioSession,
|
||||
callSessionManager: firstState.0.account.callSessionManager,
|
||||
callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings) : nil,
|
||||
serializedData: configuration.serializedData,
|
||||
dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings),
|
||||
getDeviceAccessData: strongSelf.getDeviceAccessData,
|
||||
initialState: nil,
|
||||
internalId: firstState.2.id,
|
||||
peerId: firstState.2.peerId,
|
||||
isOutgoing: false,
|
||||
incomingConferenceSource: firstState.2.conferenceSource,
|
||||
peer: EnginePeer(firstState.1),
|
||||
proxyServer: strongSelf.proxyServer,
|
||||
auxiliaryServers: [],
|
||||
currentNetworkType: firstState.4,
|
||||
updatedNetworkType: firstState.0.account.networkType,
|
||||
startWithVideo: firstState.2.isVideo,
|
||||
isVideoPossible: firstState.2.isVideoPossible,
|
||||
enableStunMarking: shouldEnableStunMarking(appConfiguration: appConfiguration),
|
||||
enableTCP: experimentalSettings.enableVoipTcp,
|
||||
preferredVideoCodec: experimentalSettings.preferredVideoCodec
|
||||
)
|
||||
strongSelf.updateCurrentCall(call)
|
||||
|
||||
call.answer()
|
||||
}))
|
||||
} else {
|
||||
for (context, _, state, _, _) in ringingStates {
|
||||
if state.id != self.currentCall?.internalId {
|
||||
self.callKitIntegration?.dropCall(uuid: state.id)
|
||||
context.account.callSessionManager.drop(internalId: state.id, reason: .busy, debugLog: .single(nil))
|
||||
}
|
||||
}
|
||||
@ -1085,7 +1134,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
reference: InternalGroupCallReference,
|
||||
beginWithVideo: Bool,
|
||||
invitePeerIds: [EnginePeer.Id],
|
||||
endCurrentIfAny: Bool
|
||||
endCurrentIfAny: Bool,
|
||||
unmuteByDefault: Bool
|
||||
) -> JoinGroupCallManagerResult {
|
||||
let begin: () -> Void = { [weak self] in
|
||||
guard let self else {
|
||||
@ -1114,7 +1164,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
conferenceSourceId: nil,
|
||||
isConference: true,
|
||||
beginWithVideo: beginWithVideo,
|
||||
sharedAudioContext: nil
|
||||
sharedAudioContext: nil,
|
||||
unmuteByDefault: unmuteByDefault
|
||||
)
|
||||
for peerId in invitePeerIds {
|
||||
let _ = call.invitePeer(peerId, isVideo: beginWithVideo)
|
||||
|
||||
@ -839,7 +839,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
conferenceSourceId: CallSessionInternalId?,
|
||||
isConference: Bool,
|
||||
beginWithVideo: Bool,
|
||||
sharedAudioContext: SharedCallAudioContext?
|
||||
sharedAudioContext: SharedCallAudioContext?,
|
||||
unmuteByDefault: Bool? = nil
|
||||
) {
|
||||
self.account = accountContext.account
|
||||
self.accountContext = accountContext
|
||||
@ -873,10 +874,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.beginWithVideo = beginWithVideo
|
||||
self.keyPair = keyPair
|
||||
|
||||
if self.isConference && conferenceSourceId == nil {
|
||||
self.isMutedValue = .unmuted
|
||||
self.isMutedPromise.set(self.isMutedValue)
|
||||
self.stateValue.muteState = nil
|
||||
if let unmuteByDefault {
|
||||
if unmuteByDefault {
|
||||
self.isMutedValue = .unmuted
|
||||
self.isMutedPromise.set(self.isMutedValue)
|
||||
self.stateValue.muteState = nil
|
||||
}
|
||||
} else {
|
||||
if self.isConference && conferenceSourceId == nil {
|
||||
self.isMutedValue = .unmuted
|
||||
self.isMutedPromise.set(self.isMutedValue)
|
||||
self.stateValue.muteState = nil
|
||||
}
|
||||
}
|
||||
|
||||
if let keyPair, let initialCall {
|
||||
@ -2959,6 +2968,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
self.genericCallContext?.setIsMuted(isEffectivelyMuted)
|
||||
|
||||
if let callId = self.callId {
|
||||
let context = self.accountContext
|
||||
let _ = (context.engine.calls.getGroupCallPersistentSettings(callId: callId)
|
||||
|> deliverOnMainQueue).startStandalone(next: { value in
|
||||
var value: PresentationGroupCallPersistentSettings = value?.get(PresentationGroupCallPersistentSettings.self) ?? PresentationGroupCallPersistentSettings.default
|
||||
value.isMicrophoneEnabledByDefault = !isVisuallyMuted
|
||||
if let entry = CodableEntry(value) {
|
||||
context.engine.calls.setGroupCallPersistentSettings(callId: callId, value: entry)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if isVisuallyMuted {
|
||||
self.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false)
|
||||
} else {
|
||||
|
||||
@ -142,6 +142,7 @@ public struct Namespaces {
|
||||
public static let recommendedBots: Int8 = 44
|
||||
public static let channelsForPublicReaction: Int8 = 45
|
||||
public static let cachedGroupsInCommon: Int8 = 46
|
||||
public static let groupCallPersistentSettings: Int8 = 47
|
||||
}
|
||||
|
||||
public struct UnorderedItemList {
|
||||
|
||||
@ -178,5 +178,21 @@ public extension TelegramEngine {
|
||||
public func getGroupCallStreamCredentials(peerId: EnginePeer.Id, revokePreviousCredentials: Bool) -> Signal<GroupCallStreamCredentials, GetGroupCallStreamCredentialsError> {
|
||||
return _internal_getGroupCallStreamCredentials(account: self.account, peerId: peerId, revokePreviousCredentials: revokePreviousCredentials)
|
||||
}
|
||||
|
||||
public func getGroupCallPersistentSettings(callId: Int64) -> Signal<CodableEntry?, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> CodableEntry? in
|
||||
let key = ValueBoxKey(length: 8)
|
||||
key.setInt64(0, value: callId)
|
||||
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.groupCallPersistentSettings, key: key))
|
||||
}
|
||||
}
|
||||
|
||||
public func setGroupCallPersistentSettings(callId: Int64, value: CodableEntry) {
|
||||
let _ = self.account.postbox.transaction({ transaction -> Void in
|
||||
let key = ValueBoxKey(length: 8)
|
||||
key.setInt64(0, value: callId)
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.groupCallPersistentSettings, key: key), entry: value)
|
||||
}).startStandalone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1387,15 +1387,21 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in
|
||||
navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall(
|
||||
id: resolvedCallLink.id,
|
||||
accessHash: resolvedCallLink.accessHash,
|
||||
slug: link,
|
||||
inviter: resolvedCallLink.inviter,
|
||||
members: resolvedCallLink.members,
|
||||
totalMemberCount: resolvedCallLink.totalMemberCount,
|
||||
info: resolvedCallLink
|
||||
))))
|
||||
let _ = (context.engine.calls.getGroupCallPersistentSettings(callId: resolvedCallLink.id)
|
||||
|> deliverOnMainQueue).startStandalone(next: { value in
|
||||
let value: PresentationGroupCallPersistentSettings = value?.get(PresentationGroupCallPersistentSettings.self) ?? PresentationGroupCallPersistentSettings.default
|
||||
|
||||
navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall(
|
||||
id: resolvedCallLink.id,
|
||||
accessHash: resolvedCallLink.accessHash,
|
||||
slug: link,
|
||||
inviter: resolvedCallLink.inviter,
|
||||
members: resolvedCallLink.members,
|
||||
totalMemberCount: resolvedCallLink.totalMemberCount,
|
||||
info: resolvedCallLink,
|
||||
enableMicrophoneByDefault: value.isMicrophoneEnabledByDefault
|
||||
))))
|
||||
})
|
||||
})
|
||||
case let .localization(identifier):
|
||||
strongSelf.presentController(LanguageLinkPreviewController(context: strongSelf.context, identifier: identifier), .window(.root), nil)
|
||||
|
||||
@ -29,6 +29,8 @@ swift_library(
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/TelegramUI/Components/CheckComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -12,12 +12,15 @@ import BalancedTextComponent
|
||||
import ButtonComponent
|
||||
import BundleIconComponent
|
||||
import Markdown
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AvatarNode
|
||||
import TelegramStringFormatting
|
||||
import AnimatedAvatarSetNode
|
||||
import UndoUI
|
||||
import PresentationDataUtils
|
||||
import CheckComponent
|
||||
import PlainButtonComponent
|
||||
|
||||
private final class JoinSubjectScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@ -83,6 +86,8 @@ private final class JoinSubjectScreenComponent: Component {
|
||||
private var previewPeersAvatarsNode: AnimatedAvatarSetNode?
|
||||
private var previewPeersAvatarsContext: AnimatedAvatarSetContext?
|
||||
|
||||
private var callMicrophoneOption: ComponentView<Empty>?
|
||||
|
||||
private let titleTransformContainer: UIView
|
||||
private let bottomPanelContainer: UIView
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
@ -102,6 +107,8 @@ private final class JoinSubjectScreenComponent: Component {
|
||||
private var topOffsetDistance: CGFloat?
|
||||
|
||||
private var cachedCloseImage: UIImage?
|
||||
|
||||
private var callMicrophoneIsEnabled: Bool = true
|
||||
|
||||
private var isJoining: Bool = false
|
||||
private var joinDisposable: Disposable?
|
||||
@ -394,7 +401,7 @@ private final class JoinSubjectScreenComponent: Component {
|
||||
self.environment?.controller()?.dismiss()
|
||||
})
|
||||
case let .groupCall(groupCall):
|
||||
component.context.joinConferenceCall(call: groupCall.info, isVideo: false)
|
||||
component.context.joinConferenceCall(call: groupCall.info, isVideo: false, unmuteByDefault: self.callMicrophoneIsEnabled)
|
||||
|
||||
self.environment?.controller()?.dismiss()
|
||||
}
|
||||
@ -414,6 +421,12 @@ private final class JoinSubjectScreenComponent: Component {
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
|
||||
if self.component == nil {
|
||||
switch component.mode {
|
||||
case .group:
|
||||
break
|
||||
case let .groupCall(groupCall):
|
||||
self.callMicrophoneIsEnabled = groupCall.enableMicrophoneByDefault
|
||||
}
|
||||
}
|
||||
|
||||
self.component = component
|
||||
@ -834,6 +847,85 @@ private final class JoinSubjectScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if case .groupCall = component.mode {
|
||||
let callMicrophoneOption: ComponentView<Empty>
|
||||
var callMicrophoneOptionTransition = transition
|
||||
if let current = self.callMicrophoneOption {
|
||||
callMicrophoneOption = current
|
||||
} else {
|
||||
callMicrophoneOptionTransition = callMicrophoneOptionTransition.withAnimation(.none)
|
||||
callMicrophoneOption = ComponentView()
|
||||
self.callMicrophoneOption = callMicrophoneOption
|
||||
}
|
||||
|
||||
let checkTheme = CheckComponent.Theme(
|
||||
backgroundColor: environment.theme.list.itemCheckColors.fillColor,
|
||||
strokeColor: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
borderColor: environment.theme.list.itemCheckColors.strokeColor,
|
||||
overlayBorder: false,
|
||||
hasInset: false,
|
||||
hasShadow: false
|
||||
)
|
||||
|
||||
let callMicrophoneOptionSize = callMicrophoneOption.update(
|
||||
transition: callMicrophoneOptionTransition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent(
|
||||
theme: checkTheme,
|
||||
size: CGSize(width: 18.0, height: 18.0),
|
||||
selected: self.callMicrophoneIsEnabled
|
||||
))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: environment.strings.Invitation_JoinGroupCall_EnableMicrophone, font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
)))
|
||||
], spacing: 10.0)),
|
||||
effectAlignment: .center,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.callMicrophoneIsEnabled = !self.callMicrophoneIsEnabled
|
||||
let callMicrophoneIsEnabled = self.callMicrophoneIsEnabled
|
||||
|
||||
if case let .groupCall(groupCall) = component.mode {
|
||||
let context = component.context
|
||||
let _ = (component.context.engine.calls.getGroupCallPersistentSettings(callId: groupCall.id)
|
||||
|> deliverOnMainQueue).startStandalone(next: { value in
|
||||
var value: PresentationGroupCallPersistentSettings = value?.get(PresentationGroupCallPersistentSettings.self) ?? PresentationGroupCallPersistentSettings.default
|
||||
value.isMicrophoneEnabledByDefault = callMicrophoneIsEnabled
|
||||
if let entry = CodableEntry(value) {
|
||||
context.engine.calls.setGroupCallPersistentSettings(callId: groupCall.id, value: entry)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
},
|
||||
animateAlpha: false,
|
||||
animateScale: false
|
||||
)),
|
||||
environment: {
|
||||
},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||
)
|
||||
let callMicrophoneOptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - callMicrophoneOptionSize.width) * 0.5), y: contentHeight), size: callMicrophoneOptionSize)
|
||||
if let callMicrophoneOptionView = callMicrophoneOption.view {
|
||||
if callMicrophoneOptionView.superview == nil {
|
||||
self.scrollContentView.addSubview(callMicrophoneOptionView)
|
||||
}
|
||||
callMicrophoneOptionTransition.setFrame(view: callMicrophoneOptionView, frame: callMicrophoneOptionFrame)
|
||||
}
|
||||
contentHeight += callMicrophoneOptionSize.height + 23.0
|
||||
} else {
|
||||
if let callMicrophoneOption = self.callMicrophoneOption {
|
||||
self.callMicrophoneOption = nil
|
||||
callMicrophoneOption.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
let actionButtonTitle: String
|
||||
switch component.mode {
|
||||
case .group:
|
||||
|
||||
@ -684,7 +684,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
}
|
||||
}
|
||||
|
||||
public func joinConferenceCall(call: JoinCallLinkInformation, isVideo: Bool) {
|
||||
public func joinConferenceCall(call: JoinCallLinkInformation, isVideo: Bool, unmuteByDefault: Bool) {
|
||||
guard let callManager = self.sharedContext.callManager else {
|
||||
return
|
||||
}
|
||||
@ -701,7 +701,8 @@ public final class AccountContextImpl: AccountContext {
|
||||
reference: call.reference,
|
||||
beginWithVideo: isVideo,
|
||||
invitePeerIds: [],
|
||||
endCurrentIfAny: false
|
||||
endCurrentIfAny: false,
|
||||
unmuteByDefault: unmuteByDefault
|
||||
)
|
||||
if case let .alreadyInProgress(currentPeerId) = result {
|
||||
let dataInput: Signal<EnginePeer?, NoError>
|
||||
@ -749,7 +750,8 @@ public final class AccountContextImpl: AccountContext {
|
||||
reference: call.reference,
|
||||
beginWithVideo: isVideo,
|
||||
invitePeerIds: [],
|
||||
endCurrentIfAny: true
|
||||
endCurrentIfAny: true,
|
||||
unmuteByDefault: unmuteByDefault
|
||||
)
|
||||
})]), on: .root)
|
||||
default:
|
||||
@ -772,7 +774,8 @@ public final class AccountContextImpl: AccountContext {
|
||||
reference: call.reference,
|
||||
beginWithVideo: isVideo,
|
||||
invitePeerIds: [],
|
||||
endCurrentIfAny: true
|
||||
endCurrentIfAny: true,
|
||||
unmuteByDefault: unmuteByDefault
|
||||
)
|
||||
})]), on: .root)
|
||||
}
|
||||
|
||||
@ -80,6 +80,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
|
||||
private var statusDisposable: Disposable?
|
||||
private var progressDisposable: Disposable?
|
||||
|
||||
private let animationCache: AnimationCache?
|
||||
private let animationRenderer: MultiAnimationRenderer?
|
||||
@ -234,6 +235,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.fetchDisposable.dispose()
|
||||
self.statusDisposable?.dispose()
|
||||
self.translationDisposable.dispose()
|
||||
self.progressDisposable?.dispose()
|
||||
}
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
@ -260,37 +262,10 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
return status == .pinnedMessage
|
||||
}
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] isLoading in
|
||||
guard let strongSelf = self else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
if isLoading {
|
||||
if strongSelf.activityIndicator.alpha.isZero {
|
||||
transition.updateAlpha(node: strongSelf.activityIndicator, alpha: 1.0)
|
||||
transition.updateSublayerTransformScale(node: strongSelf.activityIndicatorContainer, scale: 1.0)
|
||||
|
||||
transition.updateAlpha(node: strongSelf.buttonsContainer, alpha: 0.0)
|
||||
transition.updateSublayerTransformScale(node: strongSelf.buttonsContainer, scale: 0.1)
|
||||
|
||||
if let theme = strongSelf.theme {
|
||||
strongSelf.activityIndicator.transitionToState(.progress(color: theme.chat.inputPanel.panelControlAccentColor, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true), animated: false, completion: {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !strongSelf.activityIndicator.alpha.isZero {
|
||||
transition.updateAlpha(node: strongSelf.activityIndicator, alpha: 0.0, completion: { [weak self] completed in
|
||||
if completed {
|
||||
self?.activityIndicator.transitionToState(.none, animated: false, completion: {
|
||||
})
|
||||
}
|
||||
})
|
||||
transition.updateSublayerTransformScale(node: strongSelf.activityIndicatorContainer, scale: 0.1)
|
||||
|
||||
transition.updateAlpha(node: strongSelf.buttonsContainer, alpha: 1.0)
|
||||
transition.updateSublayerTransformScale(node: strongSelf.buttonsContainer, scale: 1.0)
|
||||
}
|
||||
}
|
||||
self.updateIsLoading(isLoading: isLoading)
|
||||
})
|
||||
}
|
||||
|
||||
@ -333,6 +308,14 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if actionTitle == nil {
|
||||
for media in message.message.media {
|
||||
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.type == "telegram_call" {
|
||||
actionTitle = interfaceState.strings.Chat_TitleJoinGroupCall
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
actionTitle = nil
|
||||
}
|
||||
@ -412,7 +395,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
|
||||
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
|
||||
|
||||
if let actionTitle = actionTitle {
|
||||
if let actionTitle {
|
||||
var actionButtonTransition = transition
|
||||
var animateButtonIn = false
|
||||
if self.actionButton.isHidden {
|
||||
@ -540,6 +523,37 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
return LayoutResult(backgroundHeight: panelHeight, insetHeight: panelHeight, hitTestSlop: 0.0)
|
||||
}
|
||||
|
||||
private func updateIsLoading(isLoading: Bool) {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
if isLoading {
|
||||
if self.activityIndicator.alpha.isZero {
|
||||
transition.updateAlpha(node: self.activityIndicator, alpha: 1.0)
|
||||
transition.updateSublayerTransformScale(node: self.activityIndicatorContainer, scale: 1.0)
|
||||
|
||||
transition.updateAlpha(node: self.buttonsContainer, alpha: 0.0)
|
||||
transition.updateSublayerTransformScale(node: self.buttonsContainer, scale: 0.1)
|
||||
|
||||
if let theme = self.theme {
|
||||
self.activityIndicator.transitionToState(.progress(color: theme.chat.inputPanel.panelControlAccentColor, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true), animated: false, completion: {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !self.activityIndicator.alpha.isZero {
|
||||
transition.updateAlpha(node: self.activityIndicator, alpha: 0.0, completion: { [weak self] completed in
|
||||
if completed {
|
||||
self?.activityIndicator.transitionToState(.none, animated: false, completion: {
|
||||
})
|
||||
}
|
||||
})
|
||||
transition.updateSublayerTransformScale(node: self.activityIndicatorContainer, scale: 0.1)
|
||||
|
||||
transition.updateAlpha(node: self.buttonsContainer, alpha: 1.0)
|
||||
transition.updateSublayerTransformScale(node: self.buttonsContainer, scale: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func enqueueTransition(width: CGFloat, panelHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, animation: PinnedMessageAnimation?, pinnedMessage: ChatPinnedMessage, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool, translateToLanguage: String?) {
|
||||
let message = pinnedMessage.message
|
||||
|
||||
@ -667,6 +681,9 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
if let media = media as? TelegramMediaInvoice {
|
||||
titleStrings = [.text(0, NSAttributedString(string: media.title, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
|
||||
break
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.type == "telegram_call" {
|
||||
titleStrings = [.text(0, NSAttributedString(string: strings.Chat_PinnedGroupCallTitle, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -903,20 +920,26 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
switch button.action {
|
||||
case .text:
|
||||
controllerInteraction.sendMessage(button.title)
|
||||
return
|
||||
case let .url(url):
|
||||
var isConcealed = true
|
||||
if url.hasPrefix("tg://") {
|
||||
isConcealed = false
|
||||
}
|
||||
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: isConcealed, progress: Promise()))
|
||||
return
|
||||
case .requestMap:
|
||||
controllerInteraction.shareCurrentLocation()
|
||||
return
|
||||
case .requestPhone:
|
||||
controllerInteraction.shareAccountContact()
|
||||
return
|
||||
case .openWebApp:
|
||||
controllerInteraction.requestMessageActionCallback(message.id, nil, true, false)
|
||||
return
|
||||
case let .callback(requiresPassword, data):
|
||||
controllerInteraction.requestMessageActionCallback(message.id, data, false, requiresPassword)
|
||||
return
|
||||
case let .switchInline(samePeer, query, peerTypes):
|
||||
var botPeer: Peer?
|
||||
|
||||
@ -940,10 +963,13 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
if let botPeer = botPeer, let addressName = botPeer.addressName {
|
||||
controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes)
|
||||
}
|
||||
return
|
||||
case .payment:
|
||||
controllerInteraction.openCheckoutOrReceipt(message.id, nil)
|
||||
return
|
||||
case let .urlAuth(url, buttonId):
|
||||
controllerInteraction.requestMessageActionUrlAuth(url, .message(id: message.id, buttonId: buttonId))
|
||||
return
|
||||
case .setupPoll:
|
||||
break
|
||||
case let .openUserProfile(peerId):
|
||||
@ -953,17 +979,47 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
controllerInteraction.openPeer(peer, .info(nil), nil, .default)
|
||||
}
|
||||
})
|
||||
return
|
||||
case let .openWebView(url, simple):
|
||||
controllerInteraction.openWebView(button.title, url, simple, .generic)
|
||||
return
|
||||
case .requestPeer:
|
||||
break
|
||||
case let .copyText(payload):
|
||||
controllerInteraction.copyText(payload)
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for media in message.media {
|
||||
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.type == "telegram_call" {
|
||||
var isConcealed = true
|
||||
if content.url.hasPrefix("tg://") {
|
||||
isConcealed = false
|
||||
}
|
||||
let progressPromise = Promise<Bool>()
|
||||
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(
|
||||
url: content.url,
|
||||
concealed: isConcealed,
|
||||
message: message,
|
||||
progress: progressPromise
|
||||
))
|
||||
|
||||
self.progressDisposable?.dispose()
|
||||
self.progressDisposable = (progressPromise.get()
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updateIsLoading(isLoading: value)
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,20 +396,26 @@ func openResolvedUrlImpl(
|
||||
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in
|
||||
if let currentGroupCallController = context.sharedContext.currentGroupCallController as? VoiceChatController, case let .group(groupCall) = currentGroupCallController.call, let currentCallId = groupCall.callId, currentCallId == resolvedCallLink.id {
|
||||
context.sharedContext.navigateToCurrentCall()
|
||||
return
|
||||
}
|
||||
|
||||
navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall(
|
||||
id: resolvedCallLink.id,
|
||||
accessHash: resolvedCallLink.accessHash,
|
||||
slug: link,
|
||||
inviter: resolvedCallLink.inviter,
|
||||
members: resolvedCallLink.members,
|
||||
totalMemberCount: resolvedCallLink.totalMemberCount,
|
||||
info: resolvedCallLink
|
||||
))))
|
||||
let _ = (context.engine.calls.getGroupCallPersistentSettings(callId: resolvedCallLink.id)
|
||||
|> deliverOnMainQueue).startStandalone(next: { value in
|
||||
let value: PresentationGroupCallPersistentSettings = value?.get(PresentationGroupCallPersistentSettings.self) ?? PresentationGroupCallPersistentSettings.default
|
||||
|
||||
if let currentGroupCallController = context.sharedContext.currentGroupCallController as? VoiceChatController, case let .group(groupCall) = currentGroupCallController.call, let currentCallId = groupCall.callId, currentCallId == resolvedCallLink.id {
|
||||
context.sharedContext.navigateToCurrentCall()
|
||||
return
|
||||
}
|
||||
|
||||
navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall(
|
||||
id: resolvedCallLink.id,
|
||||
accessHash: resolvedCallLink.accessHash,
|
||||
slug: link,
|
||||
inviter: resolvedCallLink.inviter,
|
||||
members: resolvedCallLink.members,
|
||||
totalMemberCount: resolvedCallLink.totalMemberCount,
|
||||
info: resolvedCallLink,
|
||||
enableMicrophoneByDefault: value.isMicrophoneEnabledByDefault
|
||||
))))
|
||||
})
|
||||
}, error: { _ in
|
||||
var elevatedLayout = true
|
||||
if case .chat = urlContext {
|
||||
|
||||
@ -2065,7 +2065,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
||||
beginWithVideo: isVideo,
|
||||
invitePeerIds: peerIds,
|
||||
endCurrentIfAny: true
|
||||
endCurrentIfAny: true,
|
||||
unmuteByDefault: true
|
||||
)
|
||||
completion?()
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
|
||||
public var keepChatNavigationStack: Bool
|
||||
public var skipReadHistory: Bool
|
||||
public var alwaysDisplayTyping: Bool
|
||||
public var crashOnLongQueries: Bool
|
||||
public var chatListPhotos: Bool
|
||||
public var knockoutWallpaper: Bool
|
||||
@ -72,6 +73,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
return ExperimentalUISettings(
|
||||
keepChatNavigationStack: false,
|
||||
skipReadHistory: false,
|
||||
alwaysDisplayTyping: false,
|
||||
crashOnLongQueries: false,
|
||||
chatListPhotos: false,
|
||||
knockoutWallpaper: false,
|
||||
@ -118,6 +120,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
public init(
|
||||
keepChatNavigationStack: Bool,
|
||||
skipReadHistory: Bool,
|
||||
alwaysDisplayTyping: Bool,
|
||||
crashOnLongQueries: Bool,
|
||||
chatListPhotos: Bool,
|
||||
knockoutWallpaper: Bool,
|
||||
@ -161,6 +164,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
) {
|
||||
self.keepChatNavigationStack = keepChatNavigationStack
|
||||
self.skipReadHistory = skipReadHistory
|
||||
self.alwaysDisplayTyping = alwaysDisplayTyping
|
||||
self.crashOnLongQueries = crashOnLongQueries
|
||||
self.chatListPhotos = chatListPhotos
|
||||
self.knockoutWallpaper = knockoutWallpaper
|
||||
@ -208,6 +212,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
|
||||
self.keepChatNavigationStack = (try container.decodeIfPresent(Int32.self, forKey: "keepChatNavigationStack") ?? 0) != 0
|
||||
self.skipReadHistory = (try container.decodeIfPresent(Int32.self, forKey: "skipReadHistory") ?? 0) != 0
|
||||
self.alwaysDisplayTyping = (try container.decodeIfPresent(Int32.self, forKey: "alwaysDisplayTyping") ?? 0) != 0
|
||||
self.crashOnLongQueries = (try container.decodeIfPresent(Int32.self, forKey: "crashOnLongQueries") ?? 0) != 0
|
||||
self.chatListPhotos = (try container.decodeIfPresent(Int32.self, forKey: "chatListPhotos") ?? 0) != 0
|
||||
self.knockoutWallpaper = (try container.decodeIfPresent(Int32.self, forKey: "knockoutWallpaper") ?? 0) != 0
|
||||
@ -255,6 +260,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
||||
|
||||
try container.encode((self.keepChatNavigationStack ? 1 : 0) as Int32, forKey: "keepChatNavigationStack")
|
||||
try container.encode((self.skipReadHistory ? 1 : 0) as Int32, forKey: "skipReadHistory")
|
||||
try container.encode((self.alwaysDisplayTyping ? 1 : 0) as Int32, forKey: "alwaysDisplayTyping")
|
||||
try container.encode((self.crashOnLongQueries ? 1 : 0) as Int32, forKey: "crashOnLongQueries")
|
||||
try container.encode((self.chatListPhotos ? 1 : 0) as Int32, forKey: "chatListPhotos")
|
||||
try container.encode((self.knockoutWallpaper ? 1 : 0) as Int32, forKey: "knockoutWallpaper")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user