Skip to content

Commit 191789f

Browse files
authored
Add renaming the current device in Active Sessions (#157)
1 parent 594c154 commit 191789f

10 files changed

Lines changed: 522 additions & 25 deletions

File tree

‎Telegram-Mac/app/AppDelegate.swift‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,12 +829,32 @@ class AppDelegate: NSResponder, NSApplicationDelegate, NSUserNotificationCenterD
829829
useBetaFeatures = false
830830
#endif
831831

832-
let networkArguments = NetworkInitializationArguments(apiId: ApiEnvironment.apiId, apiHash: ApiEnvironment.apiHash, languagesCategory: ApiEnvironment.language, appVersion: ApiEnvironment.version, voipMaxLayer: OngoingCallContext.maxLayer, voipVersions: voipVersions, appData: appData, externalRequestVerificationStream: .single([:]), externalRecaptchaRequestVerification: { _, _ in return .complete() }, autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: deviceModelPretty(), useBetaFeatures: useBetaFeatures, isICloudEnabled: false, networkEngineFactory: RustNetworkEngineFactory())
832+
let systemDeviceModel = deviceModelPretty()
833+
ApiEnvironment.systemDeviceModel = systemDeviceModel
834+
let networkArguments = NetworkInitializationArguments(apiId: ApiEnvironment.apiId, apiHash: ApiEnvironment.apiHash, languagesCategory: ApiEnvironment.language, appVersion: ApiEnvironment.version, voipMaxLayer: OngoingCallContext.maxLayer, voipVersions: voipVersions, appData: appData, externalRequestVerificationStream: .single([:]), externalRecaptchaRequestVerification: { _, _ in return .complete() }, autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: ApiEnvironment.customDeviceModel ?? systemDeviceModel, useBetaFeatures: useBetaFeatures, isICloudEnabled: false, networkEngineFactory: RustNetworkEngineFactory())
833835

834836
let sharedContext = SharedAccountContext(accountManager: accountManager, networkArguments: networkArguments, rootPath: rootPath, encryptionParameters: encryptionParameters, appEncryption: appEncryption, displayUpgradeProgress: displayUpgrade)
835837

836838
self.hangKeybind(sharedContext)
837839

840+
let deviceModel: Signal<String, NoError> = Signal { subscriber in
841+
subscriber.putNext(ApiEnvironment.customDeviceModel ?? systemDeviceModel)
842+
let observer = NotificationCenter.default.addObserver(forName: ApiEnvironment.deviceModelDidChange, object: nil, queue: nil, using: { _ in
843+
subscriber.putNext(ApiEnvironment.customDeviceModel ?? systemDeviceModel)
844+
})
845+
return ActionDisposable {
846+
NotificationCenter.default.removeObserver(observer)
847+
}
848+
}
849+
_ = combineLatest(sharedContext.activeAccounts, deviceModel).start(next: { accounts, deviceModel in
850+
for (_, account, _) in accounts.accounts {
851+
updateDeviceModel(account.network, deviceModel)
852+
}
853+
if let auth = accounts.currentAuth {
854+
updateDeviceModel(auth.network, deviceModel)
855+
}
856+
})
857+
838858

839859

840860

@@ -1794,3 +1814,18 @@ class AppDelegate: NSResponder, NSApplicationDelegate, NSUserNotificationCenterD
17941814
self.authContextValue?.applyExternalLoginCode(code)
17951815
}
17961816
}
1817+
1818+
private func updateDeviceModel(_ network: Network, _ deviceModel: String) {
1819+
guard class_getInstanceVariable(MTApiEnvironment.self, "_deviceModelName") != nil else {
1820+
return
1821+
}
1822+
network.context.updateApiEnvironment { current in
1823+
guard let current = current, current.deviceModel != deviceModel else {
1824+
return nil
1825+
}
1826+
let copy = current.withUpdatedLangPackCode(current.langPackCode)
1827+
copy.setValue(deviceModel, forKey: "deviceModelName")
1828+
let updated = copy.withUpdatedLangPackCode(copy.langPackCode)
1829+
return updated.deviceModel == deviceModel ? updated : nil
1830+
}
1831+
}

‎TelegramShare/ShareViewController.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class ShareViewController: NSViewController {
120120
useBetaFeatures = false
121121
#endif
122122

123-
let networkArguments = NetworkInitializationArguments(apiId: ApiEnvironment.apiId, apiHash: ApiEnvironment.apiHash, languagesCategory: ApiEnvironment.language, appVersion: ApiEnvironment.version, voipMaxLayer: 90, voipVersions: [], appData: appData, externalRequestVerificationStream: .single([:]), externalRecaptchaRequestVerification: { _, _ in return .complete() }, autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: deviceModelPretty(), useBetaFeatures: useBetaFeatures, isICloudEnabled: false)
123+
let networkArguments = NetworkInitializationArguments(apiId: ApiEnvironment.apiId, apiHash: ApiEnvironment.apiHash, languagesCategory: ApiEnvironment.language, appVersion: ApiEnvironment.version, voipMaxLayer: 90, voipVersions: [], appData: appData, externalRequestVerificationStream: .single([:]), externalRecaptchaRequestVerification: { _, _ in return .complete() }, autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: ApiEnvironment.customDeviceModel ?? deviceModelPretty(), useBetaFeatures: useBetaFeatures, isICloudEnabled: false)
124124

125125
let sharedContext = SharedAccountContext(accountManager: accountManager, networkArguments: networkArguments, rootPath: rootPath, encryptionParameters: encryptionParameters, appEncryption: appEncryption, displayUpgradeProgress: { _ in })
126126

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Foundation
2+
3+
public extension ApiEnvironment {
4+
static let deviceModelMaxLength = 32
5+
static let deviceModelDidChange = Notification.Name("ApiEnvironmentDeviceModelDidChange")
6+
static var systemDeviceModel: String?
7+
8+
private static var deviceModelURL: URL? {
9+
return containerURL?.appendingPathComponent("device-model")
10+
}
11+
12+
static var customDeviceModel: String? {
13+
get {
14+
guard let url = deviceModelURL, let value = try? String(contentsOf: url, encoding: .utf8) else {
15+
return nil
16+
}
17+
return normalizedDeviceModel(value)
18+
}
19+
set {
20+
guard let url = deviceModelURL else {
21+
return
22+
}
23+
if let value = newValue.flatMap(normalizedDeviceModel) {
24+
try? value.write(to: url, atomically: true, encoding: .utf8)
25+
} else {
26+
try? FileManager.default.removeItem(at: url)
27+
}
28+
NotificationCenter.default.post(name: deviceModelDidChange, object: nil)
29+
}
30+
}
31+
32+
static func normalizedDeviceModel(_ value: String) -> String? {
33+
let words = value.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty }
34+
let result = String(words.joined(separator: " ").prefix(deviceModelMaxLength)).trimmingCharacters(in: .whitespaces)
35+
return result.isEmpty ? nil : result
36+
}
37+
}

‎packages/SessionsUI/Package.swift‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ let deps: [(String, String)] = [
1717
("DateUtils", "../DateUtils"),
1818
("ColorPalette", "../ColorPalette"),
1919
("TelegramIconsTheme", "../TelegramIconsTheme"),
20+
("ApiCredentials", "../ApiCredentials"),
2021
]
2122

2223
let package = Package(

‎packages/SessionsUI/Sources/SessionsUI/RecentSessionRowItem.swift‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ public class RecentSessionRowItem: GeneralRowItem {
2626
public let handler:()->Void
2727
public let icon: (CGImage?, LocalAnimatedSticker?, NSColor?, [String]?)
2828

29-
public init(_ initialSize: NSSize, session:RecentAccountSession, stableId:AnyHashable, viewType: GeneralViewType, icon: (CGImage?, LocalAnimatedSticker?, NSColor?, [String]?), handler: @escaping()->Void) {
29+
public init(_ initialSize: NSSize, session:RecentAccountSession, deviceModel: String? = nil, stableId:AnyHashable, viewType: GeneralViewType, icon: (CGImage?, LocalAnimatedSticker?, NSColor?, [String]?), handler: @escaping()->Void) {
3030
self.session = session
3131
self.handler = handler
3232
self.icon = icon
33-
headerLayout = TextViewLayout(.initialize(string: session.deviceModel, color: theme.colors.text, font: .normal(.title)), maximumNumberOfLines: 1)
33+
headerLayout = TextViewLayout(.initialize(string: deviceModel ?? session.deviceModel, color: theme.colors.text, font: .normal(.title)), maximumNumberOfLines: 1)
3434

3535
let attr = NSMutableAttributedString()
3636

@@ -93,6 +93,12 @@ public class RecentSessionRowView : GeneralContainableRowView {
9393
private let descTextView = TextView()
9494
private let dateTextView = TextView()
9595
private let iconView: ImageView = ImageView()
96+
private let scrambleAnimation = ScrambleTextAnimation()
97+
private var scrambleLayout: TextViewLayout?
98+
99+
var titleView: NSView {
100+
return headerTextView
101+
}
96102
public required init(frame frameRect: NSRect) {
97103

98104
super.init(frame: frameRect)
@@ -136,11 +142,26 @@ public class RecentSessionRowView : GeneralContainableRowView {
136142

137143

138144
public override func set(item: TableRowItem, animated: Bool) {
145+
let previousTitle = (self.item as? RecentSessionRowItem)?.headerLayout.attributedString.string
139146
super.set(item: item)
140147

141148

142149

143150
if let item = item as? RecentSessionRowItem {
151+
let title = item.headerLayout.attributedString.string
152+
if animated, let previousTitle = previousTitle, previousTitle != title, !title.isEmpty {
153+
let width = item.headerLayout.constrainedWidth
154+
let attributes = item.headerLayout.attributedString.attributes(at: 0, effectiveRange: nil)
155+
scrambleAnimation.start(from: scrambleLayout?.attributedString.string ?? previousTitle, to: title, update: { [weak self] text in
156+
let layout = TextViewLayout(NSAttributedString(string: text, attributes: attributes), maximumNumberOfLines: 1)
157+
layout.measure(width: width)
158+
self?.scrambleLayout = text == title ? nil : layout
159+
self?.needsLayout = true
160+
})
161+
} else if previousTitle != title {
162+
scrambleAnimation.cancel()
163+
scrambleLayout = nil
164+
}
144165
self.iconView.image = item.icon.0
145166
self.iconView.sizeToFit()
146167
}
@@ -156,7 +177,7 @@ public class RecentSessionRowView : GeneralContainableRowView {
156177
let insets = item.viewType.innerInset
157178
self.iconView.setFrameOrigin(NSMakePoint(insets.left, insets.top))
158179
let left: CGFloat = (item.icon.0 != nil ? 38 : 0)
159-
self.headerTextView.update(item.headerLayout, origin: NSMakePoint(left + insets.left, insets.top - 2))
180+
self.headerTextView.update(scrambleLayout ?? item.headerLayout, origin: NSMakePoint(left + insets.left, insets.top - 2))
160181
self.descTextView.update(item.descLayout, origin: NSMakePoint(left + insets.left, headerTextView.frame.maxY + 4))
161182
self.dateTextView.update(item.dateLayout, origin: NSMakePoint(self.containerView.frame.width - insets.right - item.dateLayout.layoutSize.width, insets.top))
162183

‎packages/SessionsUI/Sources/SessionsUI/RecentSessionsController.swift‎

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ public func iconForSession(_ session: RecentAccountSession) -> (CGImage?, LocalA
2424
let platform = session.platform.lowercased()
2525
let device = session.deviceModel.lowercased()
2626
let systemVersion = session.systemVersion.lowercased()
27+
if session.isCurrent {
28+
return (NSImage(named: "Icon_Device_Apple")!.precomposed(), LocalAnimatedSticker.device_mac, NSColor(rgb: 0x0079ff), nil)
29+
}
2730
if device.contains("xbox") {
2831
return (NSImage(named: "Icon_Device_Xbox")!.precomposed(), nil, NSColor(rgb: 0x35c759), nil)
2932
}
@@ -48,7 +51,7 @@ public func iconForSession(_ session: RecentAccountSession) -> (CGImage?, LocalA
4851
if platform.contains("android") {
4952
return (NSImage(named: "Icon_Device_Android")!.precomposed(), LocalAnimatedSticker.device_android, NSColor(rgb: 0x35c759), ["Eye L.Eye L.Заливка 1", "Eye R.Eye R.Заливка 1"])
5053
}
51-
if (platform.contains("macos") || systemVersion.contains("macos")) && device.contains("mac") {
54+
if (platform.contains("macos") || systemVersion.contains("macos")) && !device.contains("iphone") && !device.contains("ipad") {
5255
return (NSImage(named: "Icon_Device_Apple")!.precomposed(), LocalAnimatedSticker.device_mac, NSColor(rgb: 0x0079ff), nil)
5356
}
5457
if device.contains("ipad") {
@@ -83,12 +86,14 @@ private final class RecentSessionsControllerArguments {
8386
public let terminateOthers:() -> Void
8487
public let toggleTtl:(Int)->Void
8588
public let preview:(RecentAccountSession, AnyHashable)->Void
86-
public init(context: any AccountContextProtocol, removeSession: @escaping (Int64) -> Void, terminateOthers: @escaping()->Void, toggleTtl:@escaping(Int)->Void, preview:@escaping(RecentAccountSession, AnyHashable)->Void) {
89+
public let rename:()->Void
90+
public init(context: any AccountContextProtocol, removeSession: @escaping (Int64) -> Void, terminateOthers: @escaping()->Void, toggleTtl:@escaping(Int)->Void, preview:@escaping(RecentAccountSession, AnyHashable)->Void, rename:@escaping()->Void) {
8791
self.context = context
8892
self.removeSession = removeSession
8993
self.terminateOthers = terminateOthers
9094
self.toggleTtl = toggleTtl
9195
self.preview = preview
96+
self.rename = rename
9297
}
9398
}
9499

@@ -113,7 +118,7 @@ public enum RecentSessionsEntryStableId: Hashable {
113118
private enum RecentSessionsEntry: Comparable, Identifiable {
114119
case loading(sectionId:Int)
115120
case currentSessionHeader(sectionId:Int, viewType: GeneralViewType)
116-
case currentSession(sectionId:Int, RecentAccountSession, viewType: GeneralViewType)
121+
case currentSession(sectionId:Int, RecentAccountSession, deviceModel: String?, viewType: GeneralViewType)
117122
case terminateOtherSessions(sectionId:Int, viewType: GeneralViewType)
118123
case currentSessionInfo(sectionId:Int, viewType: GeneralViewType)
119124

@@ -193,7 +198,7 @@ private enum RecentSessionsEntry: Comparable, Identifiable {
193198
return sectionId
194199
case let .currentSessionHeader(sectionId, _):
195200
return sectionId
196-
case let .currentSession(sectionId, _, _):
201+
case let .currentSession(sectionId, _, _, _):
197202
return sectionId
198203
case let .terminateOtherSessions(sectionId, _):
199204
return sectionId
@@ -222,7 +227,7 @@ private enum RecentSessionsEntry: Comparable, Identifiable {
222227
return (sectionId * 1000) + stableIndex
223228
case let .currentSessionHeader(sectionId, _):
224229
return (sectionId * 1000) + stableIndex
225-
case let .currentSession(sectionId, _, _):
230+
case let .currentSession(sectionId, _, _, _):
226231
return (sectionId * 1000) + stableIndex
227232
case let .terminateOtherSessions(sectionId, _):
228233
return (sectionId * 1000) + stableIndex
@@ -253,9 +258,9 @@ private enum RecentSessionsEntry: Comparable, Identifiable {
253258
public func item(_ arguments: RecentSessionsControllerArguments, initialSize: NSSize) -> TableRowItem {
254259
switch self {
255260
case let .currentSessionHeader(_, viewType):
256-
return GeneralTextRowItem(initialSize, stableId: stableId, text: strings().sessionsCurrentSessionHeader, viewType: viewType)
257-
case let .currentSession(_, session, viewType):
258-
return RecentSessionRowItem(initialSize, session: session, stableId: stableId, viewType: viewType, icon: iconForSession(session), handler: {
261+
return GeneralTextRowItem(initialSize, stableId: stableId, text: strings().sessionsCurrentSessionHeader, viewType: viewType, rightItem: InputDataGeneralTextRightData(isLoading: false, text: .initialize(string: "Rename", color: theme.colors.accent, font: .medium(.small)), action: arguments.rename)) //TODOLANG
262+
case let .currentSession(_, session, deviceModel, viewType):
263+
return RecentSessionRowItem(initialSize, session: session, deviceModel: deviceModel, stableId: stableId, viewType: viewType, icon: iconForSession(session), handler: {
259264
arguments.preview(session, stableId)
260265
})
261266
case let .terminateOtherSessions(_, viewType):
@@ -356,7 +361,7 @@ private struct RecentSessionsControllerState: Equatable {
356361
}
357362
}
358363

359-
private func recentSessionsControllerEntries(state: RecentSessionsControllerState, sessions: ActiveSessionsContextState) -> [RecentSessionsEntry] {
364+
private func recentSessionsControllerEntries(state: RecentSessionsControllerState, sessions: ActiveSessionsContextState, deviceModel: String?) -> [RecentSessionsEntry] {
360365
var entries: [RecentSessionsEntry] = []
361366

362367

@@ -368,7 +373,7 @@ private func recentSessionsControllerEntries(state: RecentSessionsControllerStat
368373
entries.append(.currentSessionHeader(sectionId: sectionId, viewType: .textTopItem))
369374
if let index = sessions.sessions.firstIndex(where: { $0.hash == 0 }) {
370375
existingSessionIds.insert(sessions.sessions[index].hash)
371-
entries.append(.currentSession(sectionId: sectionId, sessions.sessions[index], viewType: .firstItem))
376+
entries.append(.currentSession(sectionId: sectionId, sessions.sessions[index], deviceModel: deviceModel, viewType: .firstItem))
372377
}
373378
entries.append(.terminateOtherSessions(sectionId: sectionId, viewType: existingSessionIds.isEmpty ? .singleItem : .lastItem))
374379
entries.append(.currentSessionInfo(sectionId: sectionId, viewType: .textBottomItem))
@@ -526,6 +531,9 @@ open class RecentSessionsController<Context: AccountContextProtocol>: TableViewC
526531
showModal(with: SessionModalController(context: context, session: session), for: context.window, morphSource: {
527532
(self?.genericView.item(stableId: stableId)?.view as? GeneralContainableRowView)?.containerView
528533
})
534+
}, rename: { [weak self] in
535+
let source = (self?.genericView.item(stableId: RecentSessionsEntryStableId.index(2))?.view as? RecentSessionRowView)?.titleView
536+
showModal(with: RenameDeviceController(context: context), for: context.window, morphSource: source.map { source in { [weak source] in source } })
529537
})
530538

531539
let sessionsSignal: Signal<ActiveSessionsContextState, NoError> = context.activeSessionsContext.state
@@ -534,10 +542,10 @@ open class RecentSessionsController<Context: AccountContextProtocol>: TableViewC
534542

535543
let previousSessions: Atomic<[AppearanceWrapperEntry<RecentSessionsEntry>]> = Atomic(value: [])
536544

537-
let signal = combineLatest(statePromise.get(), sessionsPromise.get(), appearanceSignal)
545+
let signal = combineLatest(statePromise.get(), sessionsPromise.get(), renamedDeviceModel.get(), appearanceSignal)
538546
|> deliverOnMainQueue
539-
|> map { state, sessions, appearance -> TableUpdateTransition in
540-
let entries = recentSessionsControllerEntries(state: state, sessions: sessions).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)}
547+
|> map { state, sessions, deviceModel, appearance -> TableUpdateTransition in
548+
let entries = recentSessionsControllerEntries(state: state, sessions: sessions, deviceModel: deviceModel).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)}
541549
return prepareSessions(left: previousSessions.swap(entries), right: entries, arguments: arguments, initialSize: initialSize.modify {$0})
542550
} |> afterDisposed {
543551
actionsDisposable.dispose()

0 commit comments

Comments
 (0)