diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index f3a8a75bc88..94dbe7e14a3 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -633,8 +633,8 @@ "bzlTransitiveDigest": "8/YWyYftd8THfVoADvrOmQLl45wUGfP2MVjLM5FFn50=", "usagesDigest": "voXBMcSNlo2fnK6JIvInIrncYhBKKG8nBeKvToaUA0Y=", "recordedFileInputs": { - "@@//Package.resolved": "232351144d600cb9820d1d4e0738344bd0e24914494e9d2305c12de09bd2555f", - "@@//Package.swift": "fb3cb1d48066e64f8bf17fe1a49f689b7a6bf4bfc07aa90b9b80a02188501951" + "@@//Package.resolved": "f049b74eda61fc6e07a89148727553ddc7cfacb9a0263076c286c139ccce4e86", + "@@//Package.swift": "9696be545e5e35e7ca27cb12df2537c71dd74d8e30c3ce548220ad63d293afaf" }, "recordedDirentsInputs": {}, "envVariables": {}, @@ -1138,7 +1138,7 @@ "ruleClassName": "swift_package", "attributes": { "bazel_package_name": "swiftpkg_nicegram_assistant_ios", - "commit": "9651613e557ba9f0c4aabf492eaf7620d54c597f", + "commit": "6a3a0634dc2f8174de1245a9a18227f78472c94f", "remote": "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", "version": "", "init_submodules": false, diff --git a/Nicegram/NGData/Sources/NGSettings.swift b/Nicegram/NGData/Sources/NGSettings.swift index 0d27ce1904a..017419e3fdd 100644 --- a/Nicegram/NGData/Sources/NGSettings.swift +++ b/Nicegram/NGData/Sources/NGSettings.swift @@ -185,6 +185,9 @@ public func checkPremium(completion: @escaping (Bool) -> Void) { } public func isPremium() -> Bool { +#if DEBUG + return true +#else if #available(iOS 13.0, *) { return PremiumContainer.shared .getPremiumStatusUseCase() @@ -192,6 +195,7 @@ public func isPremium() -> Bool { } else { return false } +#endif } public func usetrButton() -> [(Bool, [String])] { diff --git a/Nicegram/NGLab/Sources/RegDate.swift b/Nicegram/NGLab/Sources/RegDate.swift index 7513e274747..ba4de231f3d 100644 --- a/Nicegram/NGLab/Sources/RegDate.swift +++ b/Nicegram/NGLab/Sources/RegDate.swift @@ -142,6 +142,44 @@ public func makeNiceRegDateStr(_ date: String) -> String { } } +public func getDaysFromRegDate(with id: Int64) -> Signal { + if let dateString = getCachedRegDate(id) { + return .single(days(from: dateString)) + } else { + return getRegDate(id) + .skipError() + |> map { dateString in + days(from: dateString) + } + } +} + +private func days(from dateString: String, to: Date = Date()) -> Int? { + let monthDateFormatter = DateFormatter() + monthDateFormatter.dateFormat = "yyyy-MM" + + let dayDateFormatter = DateFormatter() + dayDateFormatter.dateFormat = "yyyy-MM-dd" + + var convertDateFormatter = DateFormatter() + + var date: Date? + if let monthDate = monthDateFormatter.date(from: dateString) { + date = monthDate + } else if let dayDate = dayDateFormatter.date(from: dateString) { + date = dayDate + } + + if let date { + let calendar = Calendar.current + let components = calendar.dateComponents([.day], from: date, to: to) + if let days = components.day { + return days + } + } + + return nil +} public func resetRegDateCache() -> Void { UserDefaults.standard.removePersistentDomain(forName: "CachedRegDate") diff --git a/Nicegram/NGPersonality/BUILD b/Nicegram/NGPersonality/BUILD new file mode 100644 index 00000000000..f9aa1dd7a5a --- /dev/null +++ b/Nicegram/NGPersonality/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "NGPersonality", + module_name = "NGPersonality", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/TelegramCore:TelegramCore", + "//submodules/TelegramApi:TelegramApi", + "//submodules/AccountContext:AccountContext", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AvatarNode:AvatarNode", + "//Nicegram/NGUtils:NGUtils", + "//Nicegram/NGLab:NGLab", + "@swiftpkg_nicegram_assistant_ios//:FeatPersonality" + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/Nicegram/NGPersonality/Sources/CollectActiveHours.swift b/Nicegram/NGPersonality/Sources/CollectActiveHours.swift new file mode 100644 index 00000000000..7a1e7a5a297 --- /dev/null +++ b/Nicegram/NGPersonality/Sources/CollectActiveHours.swift @@ -0,0 +1,32 @@ +import Foundation +import FeatPersonality + +private let container = PersonalityContainer.shared +private let checkPreferencesStateUseCase = container.checkPreferencesStateUseCase() +private let collectActiveHoursUseCase = container.collectActiveHoursUseCase() + +public func collectMessageActivity(with userId: Int64) { + guard checkPreferencesStateUseCase(with: userId, personality: .activeHours([])) else { return } + + Task { + await collectActiveHoursUseCase.collectMessageActivity(with: userId) + } +} + +public func collectScrollActivity(with userId: Int64) { + guard checkPreferencesStateUseCase(with: userId, personality: .activeHours([])) else { return } + + collectActiveHoursUseCase.collectScrollActivity(with: userId) +} + +public func collectCallActivity(with userId: Int64) { + guard checkPreferencesStateUseCase(with: userId, personality: .activeHours([])) else { return } + + collectActiveHoursUseCase.collectCallActivity(with: userId) +} + +public func collectVideoActivity(with userId: Int64) { + guard checkPreferencesStateUseCase(with: userId, personality: .activeHours([])) else { return } + + collectActiveHoursUseCase.collectVideoActivity(with: userId) +} diff --git a/Nicegram/NGPersonality/Sources/CollectContactsActivity.swift b/Nicegram/NGPersonality/Sources/CollectContactsActivity.swift new file mode 100644 index 00000000000..f8cbb7bb921 --- /dev/null +++ b/Nicegram/NGPersonality/Sources/CollectContactsActivity.swift @@ -0,0 +1,19 @@ +import Foundation +import FeatPersonality + +private let container = PersonalityContainer.shared +private let checkPreferencesStateUseCase = container.checkPreferencesStateUseCase() +private let collectContactsActivityUseCase = container.collectContactsActivityUseCase() + +public func collectContactsActivity( + with userId: Int64, + count: Int, + completion: @escaping () -> Void = {} +) { + guard checkPreferencesStateUseCase(with: userId, personality: .contactsActivity(.empty)) else { return } + + Task { + await collectContactsActivityUseCase(with: userId, count: count) + completion() + } +} diff --git a/Nicegram/NGPersonality/Sources/CollectDailyActivity.swift b/Nicegram/NGPersonality/Sources/CollectDailyActivity.swift new file mode 100644 index 00000000000..f2843ca240d --- /dev/null +++ b/Nicegram/NGPersonality/Sources/CollectDailyActivity.swift @@ -0,0 +1,15 @@ +import UIKit +import FeatPersonality + +private let container = PersonalityContainer.shared +private let checkPreferencesStateUseCase = container.checkPreferencesStateUseCase() +private let collectDailyActivityUseCases = container.collectDailyActivityUseCases() + +public func collectDailyActivity( + with userId: Int64, + notificationName: NSNotification.Name +) async { + guard checkPreferencesStateUseCase(with: userId, personality: .dailyActivity(.empty)) else { return } + + await collectDailyActivityUseCases(with: userId, notificationName: notificationName) +} diff --git a/Nicegram/NGPersonality/Sources/CollectGhostScore.swift b/Nicegram/NGPersonality/Sources/CollectGhostScore.swift new file mode 100644 index 00000000000..a214611a081 --- /dev/null +++ b/Nicegram/NGPersonality/Sources/CollectGhostScore.swift @@ -0,0 +1,37 @@ +import TelegramApi +import TelegramCore +import AccountContext +import SwiftSignalKit +import Network +import MtProtoKit +import Postbox +import FeatPersonality + +private let container = PersonalityContainer.shared +private let checkCollectStateUseCase = container.checkCollectStateUseCase() +private let checkPreferencesStateUseCase = container.checkPreferencesStateUseCase() +private let collectGhostScoreUseCase = container.collectGhostScoreUseCase() + +public func collectGhostScore( + with context: AccountContext +) async { + let id = context.account.peerId.toInt64() + guard checkPreferencesStateUseCase(with: id, personality: .ghostScore(.empty)) else { return } + guard checkCollectStateUseCase(with: id, personality: .ghostScore(.empty)) else { return } + + let count = await withCheckedContinuation { continuation in + _ = context.account.postbox.transaction { transaction -> ChatListTotalUnreadState in + transaction.getTotalUnreadState(groupId: .root) + } + .start { state in + let count = state.count(for: .filtered, in: .messages, with: .contact) + + continuation.resume(returning: count) + } + } + + await collectGhostScoreUseCase( + with: context.account.peerId.toInt64(), + count: count + ) +} diff --git a/Nicegram/NGPersonality/Sources/CollectInfluencerScore.swift b/Nicegram/NGPersonality/Sources/CollectInfluencerScore.swift new file mode 100644 index 00000000000..7510ea8eb9e --- /dev/null +++ b/Nicegram/NGPersonality/Sources/CollectInfluencerScore.swift @@ -0,0 +1,145 @@ +import TelegramApi +import TelegramCore +import AccountContext +import SwiftSignalKit +import Network +import MtProtoKit +import Postbox +import FeatPersonality + +private let container = PersonalityContainer.shared +private let checkCollectStateUseCase = container.checkCollectStateUseCase() +private let checkPreferencesStateUseCase = container.checkPreferencesStateUseCase() +private let collectInfluencerScoreUseCase = container.collectInfluencerScoreUseCase() + +public func collectInfluencerScore( + with context: AccountContext +) async { + let id = context.account.peerId.toInt64() + guard checkPreferencesStateUseCase(with: id, personality: .influencerScore(.empty)) else { return } + guard checkCollectStateUseCase(with: id, personality: .influencerScore(.empty)) else { return } + + let result = await withCheckedContinuation { continuation in + _ = influencerScore(with: context) + .start(next: { result in + continuation.resume(returning: result) + }) + } + + await collectInfluencerScoreUseCase( + with: context.account.peerId.toInt64(), + ownerChannelCount: result.0, + ownerChannelParticipantsCount: result.1, + ownerGroupCount: result.2, + ownerGroupParticipantsCount: result.3, + groupCount: result.4, + groupParticipantsCount: result.5 + ) +} + +private func influencerScore( + with context: AccountContext +) -> Signal<(Int32, Int32, Int32, Int32, Int32, Int32), NoError> { + dialogs(with: context) + |> mapToSignal { dialogs -> Signal<[Api.messages.ChatFull?], NoError> in + switch dialogs { + case let .dialogs(_, _, chats, _): + return fullChannels(with: context, chats: chats) + case let .dialogsSlice(_, _, _, chats, _): + return fullChannels(with: context, chats: chats) + default: return .single([]) + } + } + |> map { fullChannels -> (Int32, Int32, Int32, Int32, Int32, Int32) in + var ownerChannelCount: Int32 = 0 + var ownerChannelParticipantsCount: Int32 = 0 + var ownerGroupCount: Int32 = 0 + var ownerGroupParticipantsCount: Int32 = 0 + + var groupCount: Int32 = 0 + var groupParticipantsCount: Int32 = 0 + + fullChannels.forEach { chatFull in + switch chatFull { + case let .chatFull(fullChat, _, _): + switch fullChat { + case let .channelFull(flags, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + let channelFlags = TelegramChannelFlags(rawValue: flags) + if channelFlags.contains(.isCreator) { + if channelFlags.contains(.isMegagroup) || + channelFlags.contains(.isGigagroup) { + ownerGroupCount += 1 + ownerGroupParticipantsCount += (participantsCount ?? 0) + } else { + ownerChannelCount += 1 + ownerChannelParticipantsCount += (participantsCount ?? 0) + } + } + if channelFlags.contains(.isMegagroup) || + channelFlags.contains(.isGigagroup) { + groupCount += 1 + groupParticipantsCount += (participantsCount ?? 0) + } + default: break + } + default: break + } + } + + return ( + ownerChannelCount, + ownerChannelParticipantsCount, + ownerGroupCount, + ownerGroupParticipantsCount, + groupCount, + groupParticipantsCount + ) + } +} + +private func fullChannels( + with context: AccountContext, + chats: [Api.Chat] +) -> Signal<[Api.messages.ChatFull?], NoError> { + combineLatest( + chats.compactMap { chat in + switch chat { + case let .channel(_, _, id, accessHash, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + return fullChannel(with: context, channelId: id, accessHash: accessHash ?? 0) + default: return nil + } + } + ) +} + +private func fullChannel( + with context: AccountContext, + channelId: Int64, + accessHash: Int64 +) -> Signal { + context.account.network.request(Api.functions.channels.getFullChannel( + channel: .inputChannel(channelId: channelId, accessHash: accessHash) + )) + |> map(Optional.init) + |> `catch` { error -> Signal in + return .single(nil) + } +} + +private func dialogs( + with context: AccountContext +) -> Signal { + context.account.network.request(Api.functions.messages.getDialogs( + flags: 0, + folderId: nil, + offsetDate: 0, + offsetId: 0, + offsetPeer: .inputPeerSelf, + limit: .max, + hash: 0 + )) + |> map(Optional.init) + |> `catch` { error -> Signal in + return .single(nil) + } +} diff --git a/Nicegram/NGPersonality/Sources/CollectMessagesActivity.swift b/Nicegram/NGPersonality/Sources/CollectMessagesActivity.swift new file mode 100644 index 00000000000..6050b39f321 --- /dev/null +++ b/Nicegram/NGPersonality/Sources/CollectMessagesActivity.swift @@ -0,0 +1,108 @@ +import TelegramApi +import TelegramCore +import AccountContext +import SwiftSignalKit +import Network +import MtProtoKit +import Postbox +import FeatPersonality + +private let container = PersonalityContainer.shared +private let checkCollectStateUseCase = container.checkCollectStateUseCase() +private let checkPreferencesStateUseCase = container.checkPreferencesStateUseCase() +private let collectMessagesActivityUseCase = container.collectMessagesActivityUseCase() + +public func collectMessagesActivity( + with context: AccountContext +) async { + let id = context.account.peerId.toInt64() + guard checkPreferencesStateUseCase(with: id, personality: .messagesActivity(.empty)) else { return } + guard checkCollectStateUseCase(with: id, personality: .messagesActivity(.empty)) else { return } + + let result = await withCheckedContinuation { continuation in + _ = combineLatest( + searchGlobal(with: context), + search(with: context) + ) + .start { all, user in + var allCount: Int32 = 0 + var userCount: Int32 = 0 + + switch all { + case let .channelMessages(_, _, count, _, _, _, _, _): + allCount = count + case let .messagesSlice(_, count, _, _, _, _, _): + allCount = count + default: break + } + switch user { + case let .channelMessages(_, _, count, _, _, _, _, _): + userCount = count + case let .messagesSlice(_, count, _, _, _, _, _): + userCount = count + default: break + } + + continuation.resume(returning: (allCount, userCount)) + } + } + + await collectMessagesActivityUseCase( + with: context.account.peerId.toInt64(), + allMessagesCount: result.0, + userMessagesCount: result.1 + ) +} + +// limit = 0, return only count without messages +func search( + with context: AccountContext, + peer: Api.InputPeer = .inputPeerEmpty, + from: Api.InputPeer = .inputPeerSelf, + limit: Int32 = 0 +) -> Signal { + context.account.network.request(Api.functions.messages.search( + flags: 0, + peer: peer, + q: "", + fromId: from, + savedPeerId: nil, + savedReaction: nil, + topMsgId: nil, + filter: .inputMessagesFilterEmpty, + minDate: 0, + maxDate: 0, + offsetId: 0, + addOffset: 0, + limit: limit, + maxId: 0, + minId: 0, + hash: 0 + )) + |> map(Optional.init) + |> `catch` { error -> Signal in + return .single(nil) + } +} + +private func searchGlobal( + with context: AccountContext, + limit: Int32 = 0 +) -> Signal { + context.account.network.request(Api.functions.messages.searchGlobal( + flags: 0, + folderId: nil, + q: "", + filter: .inputMessagesFilterEmpty, + minDate: 0, + maxDate: 0, + offsetRate: 0, + offsetPeer: .inputPeerEmpty, + offsetId: 0, + limit: limit + )) + |> map(Optional.init) + |> `catch` { error -> Signal in + return .single(nil) + } +} diff --git a/Nicegram/NGPersonality/Sources/CollectPersonalityProviderImpl.swift b/Nicegram/NGPersonality/Sources/CollectPersonalityProviderImpl.swift new file mode 100644 index 00000000000..8614265786e --- /dev/null +++ b/Nicegram/NGPersonality/Sources/CollectPersonalityProviderImpl.swift @@ -0,0 +1,102 @@ +import MemberwiseInit +import NGCore +import Postbox +import SwiftSignalKit +import TelegramCore +import TelegramBridge +import TelegramCore +import NGUtils +import NGLab +import UIKit +import AccountContext +import AvatarNode +import FeatPersonality + +public final class PersonalityProviderImpl { + private let contextProvider: ContextProvider + + public init(contextProvider: ContextProvider) { + self.contextProvider = contextProvider + } +} + +extension PersonalityProviderImpl: PersonalityProvider { + public func loadInformation() async -> PersonalityInformation { + do { + let context = try contextProvider.context().unwrap() + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let result = try await loadUserInformation(with: context).awaitForFirstValue() + + return PersonalityInformation( + id: result.0, + displayName: result.1, + avatar: result.2, + daysFromCreation: result.3, + overallDarkAppearance: presentationData.theme.overallDarkAppearance + ) + } catch { + return PersonalityInformation(id: 0) + } + } + + public func collect(with id: Int64) async { + do { + let context = try contextProvider.context().unwrap() + + try await context.account.postbox.transaction { transaction in + let contactPeerIds = transaction.getContactPeerIds() + + collectContactsActivity(with: id, count: contactPeerIds.count) + }.awaitForFirstValue() + + await collectDailyActivity( + with: id, + notificationName: UIApplication.didBecomeActiveNotification + ) + await collectGhostScore(with: context) + await collectInfluencerScore(with: context) + await collectMessagesActivity(with: context) + } catch {} + } +} + +private extension PersonalityProviderImpl { + func loadUserInformation(with context: AccountContext) -> Signal<(Int64, String?, UIImage?, Int?), NoError> { + return context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal<(Int64, String?, UIImage?, Int?), NoError> in + if case let .user(user) = peer { + return peerAvatarCompleteImage( + account: context.account, + peer: EnginePeer(user), + forceProvidedRepresentation: false, + representation: nil, + size: CGSize(width: 50, height: 50) + ) + |> mapToSignal { image -> Signal<(Int64, String?, UIImage?, Int?), NoError> in + getDaysFromRegDate(with: user.id.toInt64()) + |> map { days -> (Int64, String?, UIImage?, Int?) in + var displayName = user.username + let firstName = user.firstName + let lastName = user.lastName + + if let firstName, + let lastName, + !firstName.isEmpty && + !lastName.isEmpty { + displayName = "\(firstName) \(lastName)" + } else if let firstName, + !firstName.isEmpty { + displayName = firstName + } + + return (user.id.toInt64() , displayName?.capitalized, image, days) + } + } + } + + return .single((0, nil, nil, nil)) + } + } +} diff --git a/Package.resolved b/Package.resolved index 037d5b43412..651e89eea24 100644 --- a/Package.resolved +++ b/Package.resolved @@ -113,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", "state" : { - "branch" : "develop", - "revision" : "9651613e557ba9f0c4aabf492eaf7620d54c597f" + "branch" : "feat/NCG-6903_personality", + "revision" : "6a3a0634dc2f8174de1245a9a18227f78472c94f" } }, { diff --git a/Package.swift b/Package.swift index fbff9b5bdb3..e36382e0b36 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "nicegram-package", dependencies: [ - .package(url: "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", branch: "develop"), + .package(url: "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", branch: "feat/NCG-6903_personality"), .package(url: "git@bitbucket.org:mobyrix/nicegram-wallet-ios.git", branch: "develop") ] ) diff --git a/ng-env.txt b/ng-env.txt index c23f7a80886..30d74d25844 100644 --- a/ng-env.txt +++ b/ng-env.txt @@ -1 +1 @@ -prod \ No newline at end of file +test \ No newline at end of file diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 779b886b364..abe88f2be23 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -19,7 +19,6 @@ import NGSpecialOffer import NGTranslate import NGEnv import NGCollectInformation -import NGLogging import SwiftSignalKit import AsyncDisplayKit @@ -791,6 +790,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if NGSettings.lastFolder != switchingToFilterId { NGSettings.lastFolder = switchingToFilterId } + } self.reloadFilters() } @@ -814,25 +814,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) self.updateNavigationMetadata() - -// MARK: Nicegram NCG-7102 bottom folders fix - _ = self.ready - .get() - .start { [weak self] flag in - guard let self else { return } - - ngLog("===NCG-7102=== ready flag: \(flag), lastFolder: \(NGSettings.lastFolder)") - - if flag, - NGSettings.rememberFolderOnExit, - NGData.isPremium() { - let lastFolder = NGSettings.lastFolder - if lastFolder != -1 { - self.selectTab(id: .filter(lastFolder)) - } - } - } -// } required public init(coder aDecoder: NSCoder) { @@ -4048,6 +4029,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if !notifiedFirstUpdate { notifiedFirstUpdate = true firstUpdate?() + // MARK: Nicegram folder after restart + if NGSettings.rememberFolderOnExit, NGData.isPremium() { + let lastFolder = NGSettings.lastFolder + if lastFolder != -1 { + strongSelf.selectTab(id: .filter(lastFolder)) + } + } } if resetCurrentEntry { diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 139b8bfb699..c957fdad00b 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -3,7 +3,6 @@ import NGData // // MARK: Nicegram ColorAlign import NGUtils -import NGLogging // import Foundation import UIKit @@ -810,9 +809,6 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele } public func switchToFilter(id: ChatListFilterTabEntryId, animated: Bool = true, completion: (() -> Void)? = nil) { -// MARK: Nicegram NCG-7102 bottom folders fix - ngLog("===NCG-7102=== switchToFilter: id = \(id), itemNodes = \(itemNodes), pendingItemNode = \(pendingItemNode)") -// self.onFilterSwitch?() if id != self.selectedId, let index = self.availableFilters.firstIndex(where: { $0.id == id }) { if let itemNode = self.itemNodes[id] { diff --git a/submodules/DebugSettingsUI/BUILD b/submodules/DebugSettingsUI/BUILD index 55e0cb03ce4..0f8c09d52af 100644 --- a/submodules/DebugSettingsUI/BUILD +++ b/submodules/DebugSettingsUI/BUILD @@ -1,7 +1,7 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") NGDEPS = [ -"//Nicegram/NGData:NGData" + "//Nicegram/NGData:NGData" ] swift_library( diff --git a/submodules/GalleryUI/BUILD b/submodules/GalleryUI/BUILD index 873b7181124..3fceb3e2417 100644 --- a/submodules/GalleryUI/BUILD +++ b/submodules/GalleryUI/BUILD @@ -2,6 +2,7 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") NGDEPS = [ "//Nicegram/NGData:NGData", + "//Nicegram/NGPersonality:NGPersonality" ] swift_library( diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 47da141849c..2f51d2f5983 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -33,6 +33,9 @@ import RasterizedCompositionComponent import BadgeComponent import ComponentFlow import ComponentDisplayAdapters +// MARK: Nicegram NCG-6903 Nicegram Personality +import NGPersonality +// public enum UniversalVideoGalleryItemContentInfo { case message(Message, Int?) @@ -1805,9 +1808,14 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } else { self.updateDisplayPlaceholder() } - - scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in +// MARK: Nicegram NCG-6903 Nicegram Personality, added [weak self] in + scrubberView.setStatusSignal(videoNode.status |> map { [weak self] value -> MediaPlayerStatus in if let value = value, !value.duration.isZero { +// MARK: Nicegram NCG-6903 Nicegram Personality + if let self { + collectVideoActivity(with: self.context.account.peerId.toInt64()) + } +// return value } else { return MediaPlayerStatus(generationTimestamp: 0.0, duration: max(Double(item.content.duration), 0.01), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_SwiftCombine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_SwiftCombine.swift index 19b17dc0323..09b96d1b041 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_SwiftCombine.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_SwiftCombine.swift @@ -43,3 +43,21 @@ public extension Signal { } } } + +public extension Signal { + func toAnyPublisher() -> AnyPublisher { + let subject = PassthroughSubject() + + let disposable = self.start(next: { value in + subject.send(value) + }, completed: { + subject.send(completion: .finished) + }) + + return subject + .handleEvents(receiveCancel: { + disposable.dispose() + }) + .eraseToAnyPublisher() + } +} diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 40f0b54895c..d32e91773f9 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -21,6 +21,8 @@ import LibYuvBinding // MARK: Nicegram NCG-5828 call recording import NGStrings import UndoUI +// MARK: Nicegram NCG-6903 Nicegram Personality +import NGPersonality // final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeProtocol { private struct PanGestureState { @@ -471,6 +473,9 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP signalInfo: PrivateCallScreen.State.SignalInfo(quality: Double(signalQuality ?? 0) / 4.0), emojiKey: self.resolvedEmojiKey(data: keyData) )) +// MARK: Nicegram NCG-6903 Nicegram Personality + collectCallActivity(with: account.peerId.toInt64()) +// case let .reconnecting(startTime, _, keyData): self.smoothSignalQuality = nil self.smoothSignalQualityTarget = nil @@ -491,7 +496,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP } else { duration = 0.0 } - + let mappedReason: PrivateCallScreen.State.TerminatedState.Reason if let reason { switch reason { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ContactManagement.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ContactManagement.swift index 6e4bae85f99..88bdfd78d89 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ContactManagement.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ContactManagement.swift @@ -49,7 +49,7 @@ func syncContactsOnce(network: Network, postbox: Postbox, accountPeerId: PeerId) let peerIds = Set(contactPeerIds.filter({ $0.namespace == Namespaces.Peer.CloudUser })) return hashForCountAndIds(count: totalCount, ids: peerIds.map({ $0.id._internalGetInt64Value() }).sorted()) } - + let updatedPeers = initialContactPeerIdsHash |> mapToSignal { hash -> Signal<(AccumulatedPeers, Int32)?, NoError> in return updatedRemoteContactPeers(network: network, hash: hash) diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 052b47b8e94..f34b4bff5f1 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -27,6 +27,7 @@ NGDEPS = [ "@swiftpkg_nicegram_assistant_ios//:FeatTgChatButton", "@swiftpkg_nicegram_assistant_ios//:NGEntryPoint", "@swiftpkg_nicegram_assistant_ios//:_NGRemoteConfig", + "@swiftpkg_nicegram_assistant_ios//:FeatPersonalityUI", "//Nicegram/NGCollectInformation:NGCollectInformation" ] diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index fc09b216a8f..ae3b2892583 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -17,7 +17,11 @@ import NGStats import NGStrings import NGUtils import NicegramWallet +import AvatarNode +import NGLab import NGCollectInformation +import Combine +import NGPersonality // import UIKit import SwiftSignalKit @@ -369,7 +373,7 @@ private class UserInterfaceStyleObserverWindow: UIWindow { private let voipDeviceToken = Promise(nil) private let regularDeviceToken = Promise(nil) - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { precondition(!testIsLaunched) testIsLaunched = true @@ -459,6 +463,9 @@ private class UserInterfaceStyleObserverWindow: UIWindow { urlOpener: { UrlOpenerImpl(contextProvider: contextProvider) }, + personalityProvider: { + PersonalityProviderImpl(contextProvider: contextProvider) + }, walletData: .init( env: { .init( @@ -491,14 +498,14 @@ private class UserInterfaceStyleObserverWindow: UIWindow { // MARK: Nicegram Unblock let _ = (self.context.get() |> take(1) - |> deliverOnMainQueue).start(next: { context in + |> deliverOnMainQueue).start(next: { [weak self] context in if let context = context { // MARK: Nicegram NCG-6326 Apple Speech2Text let setDefaultSpeech2TextSettingsUseCase = NicegramSettingsModule.shared.setDefaultSpeech2TextSettingsUseCase() setDefaultSpeech2TextSettingsUseCase(with: context.context.isPremium) // Queue().async { - self.fetchNGUserSettings(context.context.account.peerId.id._internalGetInt64Value()) + self?.fetchNGUserSettings(context.context.account.peerId.id._internalGetInt64Value()) } } }) @@ -2147,6 +2154,20 @@ private class UserInterfaceStyleObserverWindow: UIWindow { } func applicationDidEnterBackground(_ application: UIApplication) { +// MARK: Nicegram NCG-6903 Nicegram Personality + let _ = (self.context.get() + |> take(1) + |> deliverOnMainQueue).start(next: { context in + if let context = context { + Task { + await collectDailyActivity( + with: context.context.account.peerId.toInt64(), + notificationName: UIApplication.didEnterBackgroundNotification + ) + } + } + }) +// // MARK: Nicegram DB Changes let _ = (self.sharedContextPromise.get() @@ -2158,6 +2179,7 @@ private class UserInterfaceStyleObserverWindow: UIWindow { extendNow = true } } + if !sharedApplicationContext.sharedContext.energyUsageSettings.extendBackgroundWork { extendNow = false } @@ -2235,17 +2257,33 @@ private class UserInterfaceStyleObserverWindow: UIWindow { //self.fetchPremium() } // - +// MARK: Nicegram NCG-6903 Nicegram Personality + let getContext = (self.context.get() + |> take(1) + |> deliverOnMainQueue) + + let _ = getContext + .start(next: { authorizedApplicationContext in + if let authorizedApplicationContext { + Task { + await collectDailyActivity( + with: authorizedApplicationContext.context.account.peerId.toInt64(), + notificationName: UIApplication.didBecomeActiveNotification + ) + } + } + }) +// + SharedDisplayLinkDriver.shared.updateForegroundState(self.isActiveValue) // MARK: Nicegram NCG-6554 channels info - let _ = (self.context.get() - |> take(1) - |> deliverOnMainQueue).start(next: { authorizedApplicationContext in - if let authorizedApplicationContext { - collectChannelsInformation(with: authorizedApplicationContext.context) - } - }) + let _ = getContext + .start(next: { authorizedApplicationContext in + if let authorizedApplicationContext { + collectChannelsInformation(with: authorizedApplicationContext.context) + } + }) // } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index c24db90881a..c16691dc022 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -123,7 +123,9 @@ import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem import AdsInfoScreen - +// MARK: Nicegram NCG-6903 Nicegram Personality +import NGPersonality +// extension ChatControllerImpl { func loadDisplayNodeImpl() { if #available(iOS 18.0, *) { @@ -1333,7 +1335,9 @@ extension ChatControllerImpl { } shouldOpenScheduledMessages = true } - +// MARK: Nicegram NCG-6903 Nicegram Personality + collectMessageActivity(with: strongSelf.context.account.peerId.toInt64()) +// signal = enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: transformedMessages) } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index f2bed600a36..72c7e03fddf 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -1,12 +1,12 @@ // MARK: Nicegram ATT import ChatMessageNicegramAdNode import FeatAttentionEconomy -// // MARK: Nicegram AiChat import NGAiChatUI -// // MARK: Nicegram ChatBanner import FeatChatBanner +// MARK: Nicegram NCG-6903 Nicegram Personality +import NGPersonality // import Foundation import UIKit @@ -998,6 +998,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto atBottom = true offsetFromBottom = offsetValue } +// MARK: Nicegram NCG-6903 Nicegram Personality + collectScrollActivity(with: strongSelf.context.account.peerId.toInt64()) +// //print("offsetValue: \(offsetValue)") default: break diff --git a/submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift b/submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift index 7a20ceea7e1..cdc9b6d8ba8 100644 --- a/submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift +++ b/submodules/TelegramUI/Sources/Nicegram/NGDeeplinkHandler.swift @@ -20,6 +20,7 @@ import NGSpecialOffer import NGUI import TelegramPresentationData import UIKit +import FeatPersonalityUI @MainActor class NGDeeplinkHandler { @@ -28,6 +29,7 @@ class NGDeeplinkHandler { private let tgAccountContext: AccountContext private let navigationController: NavigationController? + private let analyticsManager = AnalyticsContainer.shared.analyticsManager() // MARK: - Lifecycle @@ -98,6 +100,16 @@ class NGDeeplinkHandler { AssistantTgHelper.routeToAssistant(source: .generic) } return true + case "personality": + if #available(iOS 15.0, *) { + analyticsManager.trackEvent( + "nice_personality_banner_clicked", + params: [:] + ) + + PersonalityPresenter().present() + } + return true default: return false }