Merge commit '4b34a6643f212dab03affe205b8636ae8a6c687e'

Update widget
This commit is contained in:
Ali 2021-01-15 22:40:32 +04:00
commit fa3be5d99b
18 changed files with 789 additions and 194 deletions

159
Makefile
View File

@ -1,159 +0,0 @@
.PHONY : kill_xcode clean bazel_app_debug_arm64 bazel_app_debug_sim_arm64 bazel_app_arm64 bazel_app_armv7 bazel_app check_sandbox_debug_build bazel_project bazel_project_noextensions
APP_VERSION="7.3"
CORE_COUNT=$(shell sysctl -n hw.logicalcpu)
CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1)
BAZEL=$(shell which bazel)
ifneq ($(BAZEL_HTTP_CACHE_URL),)
export BAZEL_CACHE_FLAGS=\
--remote_cache="$(BAZEL_HTTP_CACHE_URL)" --experimental_remote_downloader="$(BAZEL_HTTP_CACHE_URL)"
else ifneq ($(BAZEL_CACHE_DIR),)
export BAZEL_CACHE_FLAGS=\
--disk_cache="${BAZEL_CACHE_DIR}"
endif
ifneq ($(BAZEL_KEEP_GOING),)
export BAZEL_KEEP_GOING_FLAGS=\
-k
else ifneq ($(BAZEL_CACHE_DIR),)
export BAZEL_KEEP_GOING_FLAGS=
endif
BAZEL_COMMON_FLAGS=\
--announce_rc \
--features=swift.use_global_module_cache \
--features=swift.split_derived_files_generation \
--features=swift.skip_function_bodies_for_derived_files \
--jobs=${CORE_COUNT} \
${BAZEL_KEEP_GOING_FLAGS} \
BAZEL_DEBUG_FLAGS=\
--features=swift.enable_batch_mode \
--swiftcopt=-j${CORE_COUNT_MINUS_ONE} \
--experimental_guard_against_concurrent_changes \
BAZEL_SANDBOX_FLAGS=\
--strategy=Genrule=sandboxed \
--spawn_strategy=sandboxed \
--strategy=SwiftCompile=sandboxed \
# --num-threads 0 forces swiftc to generate one object file per module; it:
# 1. resolves issues with the linker caused by swift-objc mixing.
# 2. makes the resulting binaries significantly smaller (up to 9% for this project).
BAZEL_OPT_FLAGS=\
--features=swift.opt_uses_wmo \
--features=swift.opt_uses_osize \
--swiftcopt='-num-threads' --swiftcopt='0' \
--features=dead_strip \
--objc_enable_binary_stripping \
--apple_bitcode=watchos=embedded \
kill_xcode:
killall Xcode || true
clean:
"${BAZEL}" clean --expunge
bazel_app_debug_arm64:
APP_VERSION="${APP_VERSION}" \
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
TELEGRAM_DISABLE_EXTENSIONS="0" \
build-system/prepare-build.sh Telegram distribution
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_DEBUG_FLAGS} \
-c dbg \
--ios_multi_cpus=arm64 \
--watchos_cpus=armv7k,arm64_32 \
--verbose_failures
bazel_app_debug_sim_arm64:
APP_VERSION="${APP_VERSION}" \
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
TELEGRAM_DISABLE_EXTENSIONS="0" \
build-system/prepare-build.sh Telegram distribution
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_DEBUG_FLAGS} \
-c dbg \
--ios_multi_cpus=sim_arm64 \
--watchos_cpus=armv7k,arm64_32 \
--verbose_failures
bazel_app_arm64:
APP_VERSION="${APP_VERSION}" \
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
TELEGRAM_DISABLE_EXTENSIONS="0" \
build-system/prepare-build.sh Telegram distribution
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_OPT_FLAGS} \
-c opt \
--ios_multi_cpus=arm64 \
--watchos_cpus=armv7k,arm64_32 \
--apple_generate_dsym \
--output_groups=+dsyms \
--verbose_failures
bazel_app_armv7:
APP_VERSION="${APP_VERSION}" \
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
TELEGRAM_DISABLE_EXTENSIONS="0" \
build-system/prepare-build.sh Telegram distribution
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_OPT_FLAGS} \
-c opt \
--ios_multi_cpus=armv7 \
--watchos_cpus=armv7k,arm64_32 \
--apple_generate_dsym \
--output_groups=+dsyms \
--verbose_failures
bazel_app:
APP_VERSION="${APP_VERSION}" \
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
TELEGRAM_DISABLE_EXTENSIONS="0" \
build-system/prepare-build.sh Telegram distribution
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_OPT_FLAGS} \
-c opt \
--ios_multi_cpus=armv7,arm64 \
--watchos_cpus=armv7k,arm64_32 \
--apple_generate_dsym \
--output_groups=+dsyms \
--verbose_failures
check_sandbox_debug_build:
APP_VERSION="${APP_VERSION}" \
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
TELEGRAM_DISABLE_EXTENSIONS="0" \
build-system/prepare-build.sh Telegram distribution
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_DEBUG_FLAGS} ${BAZEL_SANDBOX_FLAGS} \
-c opt \
--ios_multi_cpus=arm64 \
--watchos_cpus=armv7k,arm64_32 \
--apple_generate_dsym \
--output_groups=+dsyms \
--verbose_failures
bazel_project: kill_xcode
APP_VERSION="${APP_VERSION}" \
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
TELEGRAM_DISABLE_EXTENSIONS="0" \
build-system/prepare-build.sh Telegram development
APP_VERSION="${APP_VERSION}" \
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
build-system/generate-xcode-project.sh Telegram
bazel_project_noextensions: kill_xcode
APP_VERSION="${APP_VERSION}" \
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
TELEGRAM_DISABLE_EXTENSIONS="1" \
build-system/prepare-build.sh Telegram development
APP_VERSION="${APP_VERSION}" \
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
build-system/generate-xcode-project.sh Telegram

100
README.md
View File

@ -1,16 +1,96 @@
# Telegram iOS Source Code Compilation Guide
1. Install the brew package manager, if you havent already.
2. Install the packages yasm, cmake:
```
brew install yasm cmake
```
3. Clone the project from GitHub:
We welcome all developers to use our API and source code to create applications on our platform.
There are several things we require from **all developers** for the moment.
# Creating your Telegram Application
1. [**Obtain your own api_id**](https://core.telegram.org/api/obtaining_api_id) for your application.
2. Please **do not** use the name Telegram for your app — or make sure your users understand that it is unofficial.
3. Kindly **do not** use our standard logo (white paper plane in a blue circle) as your app's logo.
3. Please study our [**security guidelines**](https://core.telegram.org/mtproto/security_guidelines) and take good care of your users' data and privacy.
4. Please remember to publish **your** code too in order to comply with the licences.
# Compilation Guide
1. Install Xcode (directly from https://developer.apple.com/download/more or using the App Store).
2. Clone the project from GitHub:
```
git clone --recursive https://github.com/TelegramMessenger/Telegram-iOS.git
```
4. Open Telegram-iOS.workspace.
5. Open the Telegram-iOS-Fork scheme.
6. Start the compilation process.
7. To run the app on your device, you will need to set the correct values for the signature, .entitlements files and package IDs in accordance with your developer account values.
3. Download Bazel 3.7.0
```
mkdir -p $HOME/bazel-dist
cd $HOME/bazel-dist
curl -O -L https://github.com/bazelbuild/bazel/releases/download/3.7.0/bazel-3.7.0-darwin-x86_64
mv bazel-3.7.0* bazel
```
Verify that it's working
```
chmod +x bazel
./bazel --version
```
4. Adjust configuration parameters
```
mkdir -p $HOME/telegram-configuration
cp -R build-system/example-configuration/* $HOME/telegram-configuration/
```
- Modify the values in `variables.bzl`
- Replace the provisioning profiles in `provisioning` with valid files
5. (Optional) Create a build cache directory to speed up rebuilds
```
mkdir -p "$HOME/telegram-bazel-cache"
```
5. Build the app
```
python3 build-system/Make/Make.py \
--bazel="$HOME/bazel-dist/bazel" \
--cacheDir="$HOME/telegram-bazel-cache" \
build \
--configurationPath="$HOME/telegram-configuration" \
--buildNumber=100001 \
--configuration=release_universal
```
6. (Optional) Generate an Xcode project
```
python3 build-system/Make/Make.py \
--bazel="$HOME/bazel-dist/bazel" \
--cacheDir="$HOME/telegram-bazel-cache" \
generateProject \
--configurationPath="$HOME/telegram-configuration" \
--disableExtensions
```
Tip: use `--disableExtensions` when developing to speed up development by not building application extensions.
# Tips
Bazel is used to build the app. To simplify the development setup a helper script is provided (`build-system/Make/Make.py`). See help:
```
python3 build-system/Make/Make.py --help
python3 build-system/Make/Make.py build --help
python3 build-system/Make/Make.py generateProject --help
```
Each release is built using specific Xcode and Bazel versions (see `versions.json`). The helper script checks the versions of installed software and reports an error if they don't match the ones specified in `versions.json`. There are flags that allow to bypass these checks:
```
python3 build-system/Make/Make.py --overrideBazelVersion build ... # Don't check the version of Bazel
python3 build-system/Make/Make.py --overrideXcodeVersion build ... # Don't check the version of Xcode
```

View File

@ -1126,25 +1126,35 @@ plist_fragment(
)
)
swift_library(
name = "GeneratedSources",
module_name = "GeneratedSources",
srcs = glob([
"Generated/**/*.swift",
]),
visibility = ["//visibility:public"],
)
swift_library(
name = "WidgetExtensionLib",
module_name = "WidgetExtensionLib",
srcs = glob([
"WidgetKitWidget/**/*.swift",
"Generated/**/*.swift",
]),
data = [
"SiriIntents/Intents.intentdefinition",
],
deps = [
"//submodules/BuildConfig:BuildConfig",
"//submodules/WidgetItems:WidgetItems",
"//submodules/WidgetItems:WidgetItems_iOS14",
"//submodules/WidgetItemsUtils:WidgetItemsUtils",
"//submodules/AppLockState:AppLockState",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/SyncCore:SyncCore",
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
":GeneratedSources",
],
)
@ -1221,7 +1231,6 @@ swift_library(
module_name = "IntentsExtensionLib",
srcs = glob([
"SiriIntents/**/*.swift",
"Generated/**/*.swift",
]),
data = [
"SiriIntents/Intents.intentdefinition",
@ -1235,6 +1244,7 @@ swift_library(
"//submodules/BuildConfig:BuildConfig",
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
"//submodules/AppLockState:AppLockState",
":GeneratedSources",
],
)

View File

@ -9,6 +9,7 @@ import Contacts
import OpenSSLEncryptionProvider
import AppLockState
import UIKit
import GeneratedSources
private var accountCache: Account?

View File

@ -13,6 +13,9 @@ import Postbox
import SyncCore
import TelegramCore
import OpenSSLEncryptionProvider
import WidgetItemsUtils
import GeneratedSources
private var installedSharedLogger = false
@ -156,9 +159,16 @@ struct Provider: IntentTimelineProvider {
)
}
var mappedMessage: WidgetDataPeer.Message?
if let index = transaction.getTopPeerMessageIndex(peerId: peer.id) {
if let message = transaction.getMessage(index.id) {
mappedMessage = WidgetDataPeer.Message(message: message)
}
}
peers.append(WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return postbox.mediaBox.resourcePath(representation.resource)
}, badge: badge))
}, badge: badge, message: mappedMessage))
}
}
return WidgetDataPeers(accountPeerId: widgetPeers.accountPeerId, peers: peers)
@ -195,12 +205,13 @@ struct AvatarItemView: View {
var accountPeerId: Int64
var peer: WidgetDataPeer
var itemSize: CGFloat
var displayBadge: Bool = true
var body: some View {
return ZStack {
Image(uiImage: avatarImage(accountPeerId: accountPeerId, peer: peer, size: CGSize(width: itemSize, height: itemSize)))
.clipShape(Circle())
if let badge = peer.badge, badge.count > 0 {
if displayBadge, let badge = peer.badge, badge.count > 0 {
Text("\(badge.count)")
.font(Font.system(size: 16.0))
.multilineTextAlignment(.center)
@ -219,6 +230,7 @@ struct AvatarItemView: View {
struct WidgetView: View {
@Environment(\.widgetFamily) private var widgetFamily
@Environment(\.colorScheme) private var colorScheme
let data: PeersWidgetData
func placeholder(geometry: GeometryProxy) -> some View {
@ -329,12 +341,150 @@ struct WidgetView: View {
}
}
var body: some View {
var body1: some View {
ZStack {
peerViews()
}
.padding(0.0)
}
func chatTopLine(_ peer: WidgetDataPeer) -> some View {
let dateText: String
if let message = peer.message {
dateText = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(message.timestamp)), dateStyle: .none, timeStyle: .short)
} else {
dateText = ""
}
return HStack(alignment: .center, spacing: 0.0, content: {
Text(peer.name).font(Font.system(size: 16.0, weight: .medium, design: .default)).foregroundColor(.primary)
Spacer()
Text(dateText).font(Font.system(size: 14.0, weight: .regular, design: .default)).foregroundColor(.secondary)
})
}
func chatBottomLine(_ peer: WidgetDataPeer) -> some View {
var text = peer.message?.text ?? ""
if let message = peer.message {
//TODO:localize
switch message.content {
case .text:
break
case .image:
text = "🖼 Photo"
case .video:
text = "📹 Video"
case .gif:
text = "Gif"
case let .file(file):
text = "📎 \(file.name)"
case let .music(music):
if !music.title.isEmpty && !music.artist.isEmpty {
text = "\(music.artist)\(music.title)"
} else if !music.title.isEmpty {
text = music.title
} else if !music.artist.isEmpty {
text = music.artist
} else {
text = "Music"
}
case .voiceMessage:
text = "🎤 Voice Message"
case .videoMessage:
text = "Video Message"
case let .sticker(sticker):
text = "\(sticker.altText) Sticker"
case let .call(call):
if call.isVideo {
text = "Video Call"
} else {
text = "Voice Call"
}
case .mapLocation:
text = "Location"
case let .game(game):
text = "🎮 \(game.title)"
case let .poll(poll):
text = "📊 \(poll.title)"
}
}
var hasBadge = false
if let badge = peer.badge, badge.count > 0 {
hasBadge = true
}
return HStack(alignment: .center, spacing: hasBadge ? 6.0 : 0.0, content: {
Text(text).lineLimit(nil).font(Font.system(size: 15.0, weight: .regular, design: .default)).foregroundColor(.secondary).multilineTextAlignment(.leading).frame(maxHeight: .infinity, alignment: .topLeading)
Spacer()
if let badge = peer.badge, badge.count > 0 {
VStack {
Spacer()
Text("\(badge.count)")
.font(Font.system(size: 14.0))
.multilineTextAlignment(.center)
.foregroundColor(.white)
.padding(.horizontal, 4.0)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(badge.isMuted ? Color.gray : Color.blue)
.frame(minWidth: 20, idealWidth: 20, maxWidth: .infinity, minHeight: 20, idealHeight: 20, maxHeight: 20.0, alignment: .center)
)
.padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 6.0, trailing: 3.0))
}
}
})
}
func chatContent(_ peer: WidgetDataPeer) -> some View {
return VStack(alignment: .leading, spacing: 2.0, content: {
chatTopLine(peer)
chatBottomLine(peer).frame(maxHeight: .infinity)
})
}
func chatContentView(_ index: Int) -> AnyView {
let peers: WidgetDataPeers
switch data {
case let .peers(peersValue):
peers = peersValue
if peers.peers.count <= index {
return AnyView(Spacer())
}
default:
return AnyView(Spacer())
}
return AnyView(
Link(destination: URL(string: linkForPeer(id: peers.peers[index].id))!, label: {
HStack(alignment: .center, spacing: 0.0, content: {
AvatarItemView(accountPeerId: peers.accountPeerId, peer: peers.peers[index], itemSize: 60.0, displayBadge: false).frame(width: 60.0, height: 60.0, alignment: .leading).padding(EdgeInsets(top: 0.0, leading: 10.0, bottom: 0.0, trailing: 10.0))
chatContent(peers.peers[index]).frame(maxWidth: .infinity).padding(EdgeInsets(top: 10.0, leading: 0.0, bottom: 10.0, trailing: 10.0))
})
})
)
}
func getSeparatorColor() -> Color {
switch colorScheme {
case .light:
return Color(.sRGB, red: 200.0 / 255.0, green: 199.0 / 255.0, blue: 204.0 / 255.0, opacity: 1.0)
case .dark:
return Color(.sRGB, red: 61.0 / 255.0, green: 61.0 / 255.0, blue: 64.0 / 255.0, opacity: 1.0)
@unknown default:
return .secondary
}
}
var body: some View {
GeometryReader(content: { geometry in
ZStack {
chatContentView(0).position(x: geometry.size.width / 2.0, y: geometry.size.height / 4.0).frame(width: geometry.size.width, height: geometry.size.height / 2.0, alignment: .leading)
chatContentView(1).position(x: geometry.size.width / 2.0, y: geometry.size.height / 2.0 + geometry.size.height / 4.0).frame(width: geometry.size.width, height: geometry.size.height / 2.0, alignment: .leading)
Rectangle().foregroundColor(getSeparatorColor()).position(x: geometry.size.width / 2.0, y: geometry.size.height / 4.0).frame(width: geometry.size.width, height: 0.33, alignment: .leading)
}
})
.padding(0.0)
}
}
private let buildConfig: BuildConfig = {
@ -429,7 +579,7 @@ struct Static_Widget: Widget {
return IntentConfiguration(kind: kind, intent: SelectFriendsIntent.self, provider: Provider(), content: { entry in
WidgetView(data: getWidgetData(contents: entry.contents))
})
.supportedFamilies([.systemSmall, .systemMedium])
.supportedFamilies([.systemMedium])
.configurationDisplayName(presentationData.widgetGalleryTitle)
.description(presentationData.widgetGalleryDescription)
}

View File

@ -66,7 +66,7 @@ def get_bazel_version(bazel_path):
command_result = run_executable_with_output(bazel_path, ['--version']).strip('\n')
if not command_result.startswith('bazel '):
raise Exception('{} is not a valid bazel binary'.format(bazel_path))
command_result.replace('bazel ', '')
command_result = command_result.replace('bazel ', '')
return command_result
@ -132,7 +132,7 @@ class BuildEnvironment:
self.bazel_version, actual_bazel_version, self.bazel_path))
self.bazel_version = actual_bazel_version
else:
print('Required bazel version is {}, but {} is reported by {}'.format(
print('Required bazel version is "{}", but "{}"" is reported by {}'.format(
self.bazel_version, actual_bazel_version, self.bazel_path))
exit(1)

View File

@ -136,7 +136,7 @@ class BazelCommandLine:
# Always build universal Watch binaries.
'--watchos_cpus=armv7k,arm64_32'
] + self.common_release_args
elif configuration == 'release':
elif configuration == 'release_universal':
self.configuration_args = [
# bazel optimized build configuration
'-c', 'opt',
@ -145,7 +145,7 @@ class BazelCommandLine:
'--ios_multi_cpus=armv7,arm64',
# Always build universal Watch binaries.
'--watchos_cpus=armv7k,arm64_32'
'--watchos_cpus=armv7k,arm64_32',
# Generate DSYM files when building.
'--apple_generate_dsym',
@ -266,9 +266,13 @@ def resolve_configuration(bazel_command_line: BazelCommandLine, arguments):
def generate_project(arguments):
bazel_x86_64_path = None
if is_apple_silicon():
bazel_x86_64_path = arguments.bazel_x86_64
bazel_command_line = BazelCommandLine(
bazel_path=arguments.bazel,
bazel_x86_64_path=arguments.bazel_x86_64,
bazel_x86_64_path=bazel_x86_64_path,
override_bazel_version=arguments.overrideBazelVersion,
override_xcode_version=arguments.overrideXcodeVersion
)

View File

@ -1,5 +1,5 @@
load(
"//build-input/data:variables.bzl",
"@build_configuration//:variables.bzl",
"telegram_api_id",
"telegram_api_hash",
"telegram_app_center_id",

View File

@ -0,0 +1,44 @@
import Foundation
final class MutableTopChatMessageView: MutablePostboxView {
private let peerIds: Set<PeerId>
fileprivate var messages: [PeerId: Message] = [:]
init(postbox: Postbox, peerIds: Set<PeerId>) {
self.peerIds = peerIds
for peerId in self.peerIds {
if let index = postbox.chatListIndexTable.get(peerId: peerId).topMessageIndex {
self.messages[peerId] = postbox.getMessage(index.id)
}
}
}
func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool {
var updated = false
for peerId in self.peerIds {
if transaction.currentOperationsByPeerId[peerId] != nil {
if let index = postbox.chatListIndexTable.get(peerId: peerId).topMessageIndex {
self.messages[peerId] = postbox.getMessage(index.id)
} else {
self.messages.removeValue(forKey: peerId)
}
updated = true
}
}
return updated
}
func immutableView() -> PostboxView {
return TopChatMessageView(self)
}
}
public final class TopChatMessageView: PostboxView {
public let messages: [PeerId: Message]
init(_ view: MutableTopChatMessageView) {
self.messages = view.messages
}
}

View File

@ -30,6 +30,7 @@ public enum PostboxViewKey: Hashable {
case basicPeer(PeerId)
case allChatListHoles(PeerGroupId)
case historyTagInfo(peerId: PeerId, tag: MessageTags)
case topChatMessage(peerIds: [PeerId])
public var hashValue: Int {
switch self {
@ -91,6 +92,8 @@ public enum PostboxViewKey: Hashable {
return groupId.hashValue
case let .historyTagInfo(peerId, tag):
return peerId.hashValue ^ tag.hashValue
case let .topChatMessage(peerIds):
return peerIds.hashValue
}
}
@ -270,6 +273,12 @@ public enum PostboxViewKey: Hashable {
} else {
return false
}
case let .topChatMessage(peerIds):
if case .topChatMessage(peerIds) = rhs {
return true
} else {
return false
}
}
}
}
@ -334,5 +343,7 @@ func postboxViewForKey(postbox: Postbox, key: PostboxViewKey) -> MutablePostboxV
return MutableAllChatListHolesView(postbox: postbox, groupId: groupId)
case let .historyTagInfo(peerId, tag):
return MutableHistoryTagInfoView(postbox: postbox, peerId: peerId, tag: tag)
case let .topChatMessage(peerIds):
return MutableTopChatMessageView(postbox: postbox, peerIds: Set(peerIds))
}
}

View File

@ -187,6 +187,7 @@ swift_library(
"//submodules/Markdown:Markdown",
"//submodules/SearchPeerMembers:SearchPeerMembers",
"//submodules/WidgetItems:WidgetItems",
"//submodules/WidgetItemsUtils:WidgetItemsUtils",
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
"//submodules/AppLock:AppLock",
@ -211,6 +212,7 @@ swift_library(
"//submodules/SlotMachineAnimationNode:SlotMachineAnimationNode",
"//submodules/AnimatedNavigationStripeNode:AnimatedNavigationStripeNode",
"//submodules/AudioBlob:AudioBlob",
"//Telegram:GeneratedSources",
],
visibility = [
"//visibility:public",

View File

@ -8,6 +8,9 @@ import TelegramPresentationData
import NotificationsPresentationData
import WidgetKit
import TelegramUIPreferences
import WidgetItemsUtils
import GeneratedSources
final class WidgetDataContext {
private var currentAccount: Account?
@ -32,9 +35,45 @@ final class WidgetDataContext {
}
case disabled
case peers(peers: [Peer], unread: [PeerId: Unread])
case peers(peers: [Peer], unread: [PeerId: Unread], messages: [PeerId: WidgetDataPeer.Message])
}
let updatedAdditionalPeerIds: Signal<Set<PeerId>, NoError> = Signal { subscriber in
if #available(iOSApplicationExtension 14.0, iOS 14.0, *) {
#if arch(arm64) || arch(i386) || arch(x86_64)
WidgetCenter.shared.getCurrentConfigurations({ result in
var peerIds = Set<PeerId>()
if case let .success(infos) = result {
for info in infos {
if let configuration = info.configuration as? SelectFriendsIntent {
if let items = configuration.friends {
for item in items {
guard let identifier = item.identifier, let peerIdValue = Int64(identifier) else {
continue
}
peerIds.insert(PeerId(peerIdValue))
}
}
}
}
}
subscriber.putNext(peerIds)
subscriber.putCompletion()
})
#else
subscriber.putNext(Set())
subscriber.putCompletion()
#endif
} else {
subscriber.putNext(Set())
subscriber.putCompletion()
}
return EmptyDisposable
}
|> runOn(.mainQueue())
let preferencesKey: PostboxViewKey = .preferences(keys: Set([
ApplicationSpecificPreferencesKeys.widgetSettings
]))
@ -69,13 +108,24 @@ final class WidgetDataContext {
case .disabled:
return .single(.disabled)
case let .peers(peers):
return combineLatest(queue: .mainQueue(), peers.filter { !$0.isDeleted }.map { account.postbox.peerView(id: $0.id)}) |> mapToSignal { peerViews -> Signal<CombinedRecentPeers, NoError> in
return account.postbox.unreadMessageCountsView(items: peerViews.map {
.peer($0.peerId)
return combineLatest(queue: .mainQueue(), peers.filter { !$0.isDeleted }.map { account.postbox.peerView(id: $0.id)})
|> mapToSignal { peerViews -> Signal<CombinedRecentPeers, NoError> in
let topMessagesKey: PostboxViewKey = .topChatMessage(peerIds: peerViews.map {
$0.peerId
})
|> map { values -> CombinedRecentPeers in
return combineLatest(queue: .mainQueue(),
account.postbox.unreadMessageCountsView(items: peerViews.map {
.peer($0.peerId)
}),
account.postbox.combinedView(keys: [topMessagesKey])
)
|> map { values, combinedView -> CombinedRecentPeers in
var peers: [Peer] = []
var unread: [PeerId: CombinedRecentPeers.Unread] = [:]
var messages: [PeerId: WidgetDataPeer.Message] = [:]
let topMessages = combinedView.views[topMessagesKey] as! TopChatMessageView
for peerView in peerViews {
if let peer = peerViewMainPeer(peerView) {
var isMuted: Bool = false
@ -93,21 +143,25 @@ final class WidgetDataContext {
unread[peerView.peerId] = CombinedRecentPeers.Unread(count: Int32(unreadCount), isMuted: isMuted)
}
if let message = topMessages.messages[peerView.peerId] {
messages[peerView.peerId] = WidgetDataPeer.Message(message: message)
}
peers.append(peer)
}
}
return .peers(peers: peers, unread: unread)
return .peers(peers: peers, unread: unread, messages: messages)
}
}
}
}
return recent
let processedRecent = recent
|> map { result -> WidgetData in
switch result {
case .disabled:
return WidgetData(accountId: account.id.int64, content: .disabled)
case let .peers(peers, unread):
case let .peers(peers, unread, messages):
return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in
var name: String = ""
var lastName: String?
@ -133,13 +187,110 @@ final class WidgetDataContext {
)
}
let message = messages[peer.id]
return WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return account.postbox.mediaBox.resourcePath(representation.resource)
}, badge: badge)
}, badge: badge, message: message)
})))
}
}
|> distinctUntilChanged
let additionalPeerIds = Signal<Set<PeerId>, NoError>.single(Set()) |> then(updatedAdditionalPeerIds)
let processedCustom: Signal<WidgetData, NoError> = additionalPeerIds
|> distinctUntilChanged
|> mapToSignal { additionalPeerIds -> Signal<CombinedRecentPeers, NoError> in
return combineLatest(queue: .mainQueue(), additionalPeerIds.map { account.postbox.peerView(id: $0) })
|> mapToSignal { peerViews -> Signal<CombinedRecentPeers, NoError> in
let topMessagesKey: PostboxViewKey = .topChatMessage(peerIds: peerViews.map {
$0.peerId
})
return combineLatest(queue: .mainQueue(),
account.postbox.unreadMessageCountsView(items: peerViews.map {
.peer($0.peerId)
}),
account.postbox.combinedView(keys: [topMessagesKey])
)
|> map { values, combinedView -> CombinedRecentPeers in
var peers: [Peer] = []
var unread: [PeerId: CombinedRecentPeers.Unread] = [:]
var messages: [PeerId: WidgetDataPeer.Message] = [:]
let topMessages = combinedView.views[topMessagesKey] as! TopChatMessageView
for peerView in peerViews {
if let peer = peerViewMainPeer(peerView) {
var isMuted: Bool = false
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
switch notificationSettings.muteState {
case .muted:
isMuted = true
default:
break
}
}
let unreadCount = values.count(for: .peer(peerView.peerId))
if let unreadCount = unreadCount, unreadCount > 0 {
unread[peerView.peerId] = CombinedRecentPeers.Unread(count: Int32(unreadCount), isMuted: isMuted)
}
if let message = topMessages.messages[peerView.peerId] {
messages[peerView.peerId] = WidgetDataPeer.Message(message: message)
}
peers.append(peer)
}
}
return .peers(peers: peers, unread: unread, messages: messages)
}
}
}
|> map { result -> WidgetData in
switch result {
case .disabled:
return WidgetData(accountId: account.id.int64, content: .disabled)
case let .peers(peers, unread, messages):
return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in
var name: String = ""
var lastName: String?
if let user = peer as? TelegramUser {
if let firstName = user.firstName {
name = firstName
lastName = user.lastName
} else if let lastName = user.lastName {
name = lastName
} else if let phone = user.phone, !phone.isEmpty {
name = phone
}
} else {
name = peer.debugDisplayTitle
}
var badge: WidgetDataPeer.Badge?
if let unreadValue = unread[peer.id], unreadValue.count > 0 {
badge = WidgetDataPeer.Badge(
count: Int(unreadValue.count),
isMuted: unreadValue.isMuted
)
}
let message = messages[peer.id]
return WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return account.postbox.mediaBox.resourcePath(representation.resource)
}, badge: badge, message: message)
})))
}
}
|> distinctUntilChanged
return combineLatest(processedRecent, processedCustom)
|> map { processedRecent, _ -> WidgetData in
return processedRecent
}
}).start(next: { widgetData in
let path = basePath + "/widget-data"
if let data = try? JSONEncoder().encode(widgetData) {

View File

@ -10,3 +10,14 @@ swift_library(
"//visibility:public",
],
)
swift_library(
name = "WidgetItems_iOS14",
module_name = "WidgetItems_iOS14",
srcs = glob([
"Sources/**/*.swift",
]),
visibility = [
"//visibility:public",
],
)

View File

@ -15,20 +15,223 @@ public struct WidgetDataPeer: Codable, Equatable {
}
}
public struct Message: Codable, Equatable {
public enum Content: Codable, Equatable {
public enum DecodingError: Error {
case generic
}
public struct Image: Codable, Equatable {
public init() {
}
}
public struct Video: Codable, Equatable {
public init() {
}
}
public struct File: Codable, Equatable {
public var name: String
public init(name: String) {
self.name = name
}
}
public struct Gif: Codable, Equatable {
public init() {
}
}
public struct Music: Codable, Equatable {
public var artist: String
public var title: String
public var duration: Int32
public init(artist: String, title: String, duration: Int32) {
self.artist = artist
self.title = title
self.duration = duration
}
}
public struct VoiceMessage: Codable, Equatable {
public var duration: Int32
public init(duration: Int32) {
self.duration = duration
}
}
public struct VideoMessage: Codable, Equatable {
public var duration: Int32
public init(duration: Int32) {
self.duration = duration
}
}
public struct Sticker: Codable, Equatable {
public var altText: String
public init(altText: String) {
self.altText = altText
}
}
public struct Call: Codable, Equatable {
public var isVideo: Bool
public init(isVideo: Bool) {
self.isVideo = isVideo
}
}
public struct MapLocation: Codable, Equatable {
public init() {
}
}
public struct Game: Codable, Equatable {
public var title: String
public init(title: String) {
self.title = title
}
}
public struct Poll: Codable, Equatable {
public var title: String
public init(title: String) {
self.title = title
}
}
enum CodingKeys: String, CodingKey {
case text
case image
case video
case file
case gif
case music
case voiceMessage
case videoMessage
case sticker
case call
case mapLocation
case game
case poll
}
case text
case image(Image)
case video(Video)
case file(File)
case gif(Gif)
case music(Music)
case voiceMessage(VoiceMessage)
case videoMessage(VideoMessage)
case sticker(Sticker)
case call(Call)
case mapLocation(MapLocation)
case game(Game)
case poll(Poll)
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let _ = try? container.decode(String.self, forKey: .text) {
self = .text
} else if let image = try? container.decode(Image.self, forKey: .image) {
self = .image(image)
} else if let video = try? container.decode(Video.self, forKey: .video) {
self = .video(video)
} else if let gif = try? container.decode(Gif.self, forKey: .gif) {
self = .gif(gif)
} else if let file = try? container.decode(File.self, forKey: .file) {
self = .file(file)
} else if let music = try? container.decode(Music.self, forKey: .voiceMessage) {
self = .music(music)
} else if let voiceMessage = try? container.decode(VoiceMessage.self, forKey: .voiceMessage) {
self = .voiceMessage(voiceMessage)
} else if let videoMessage = try? container.decode(VideoMessage.self, forKey: .videoMessage) {
self = .videoMessage(videoMessage)
} else if let sticker = try? container.decode(Sticker.self, forKey: .sticker) {
self = .sticker(sticker)
} else if let call = try? container.decode(Call.self, forKey: .call) {
self = .call(call)
} else if let mapLocation = try? container.decode(MapLocation.self, forKey: .mapLocation) {
self = .mapLocation(mapLocation)
} else if let game = try? container.decode(Game.self, forKey: .game) {
self = .game(game)
} else if let poll = try? container.decode(Poll.self, forKey: .poll) {
self = .poll(poll)
} else {
throw DecodingError.generic
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .text:
try container.encode("", forKey: .text)
case let .image(image):
try container.encode(image, forKey: .image)
case let .video(video):
try container.encode(video, forKey: .video)
case let .file(file):
try container.encode(file, forKey: .file)
case let .gif(gif):
try container.encode(gif, forKey: .gif)
case let .music(music):
try container.encode(music, forKey: .music)
case let .voiceMessage(voiceMessage):
try container.encode(voiceMessage, forKey: .voiceMessage)
case let .videoMessage(videoMessage):
try container.encode(videoMessage, forKey: .videoMessage)
case let .sticker(sticker):
try container.encode(sticker, forKey: .sticker)
case let .call(call):
try container.encode(call, forKey: .call)
case let .mapLocation(mapLocation):
try container.encode(mapLocation, forKey: .mapLocation)
case let .game(game):
try container.encode(game, forKey: .game)
case let .poll(poll):
try container.encode(poll, forKey: .poll)
}
}
}
public var text: String
public var content: Content
public var timestamp: Int32
public init(text: String, content: Content, timestamp: Int32) {
self.text = text
self.content = content
self.timestamp = timestamp
}
}
public var id: Int64
public var name: String
public var lastName: String?
public var letters: [String]
public var avatarPath: String?
public var badge: Badge?
public var message: Message?
public init(id: Int64, name: String, lastName: String?, letters: [String], avatarPath: String?, badge: Badge?) {
public init(id: Int64, name: String, lastName: String?, letters: [String], avatarPath: String?, badge: Badge?, message: Message?) {
self.id = id
self.name = name
self.lastName = lastName
self.letters = letters
self.avatarPath = avatarPath
self.badge = badge
self.message = message
}
}

View File

@ -0,0 +1,19 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "WidgetItemsUtils",
module_name = "WidgetItemsUtils",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Postbox:Postbox",
"//submodules/SyncCore:SyncCore",
"//submodules/TelegramCore:TelegramCore",
"//submodules/WidgetItems:WidgetItems",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,59 @@
import Foundation
import Postbox
import SyncCore
import TelegramCore
import WidgetItems
public extension WidgetDataPeer.Message {
init(message: Message) {
var content: WidgetDataPeer.Message.Content = .text
for media in message.media {
switch media {
case _ as TelegramMediaImage:
content = .image(WidgetDataPeer.Message.Content.Image())
case let file as TelegramMediaFile:
var fileName = "file"
for attribute in file.attributes {
if case let .FileName(value) = attribute {
fileName = value
break
}
}
content = .file(WidgetDataPeer.Message.Content.File(name: fileName))
for attribute in file.attributes {
switch attribute {
case let .Sticker(altText, _, _):
content = .sticker(WidgetDataPeer.Message.Content.Sticker(altText: altText))
case let .Video(duration, _, flags):
if flags.contains(.instantRoundVideo) {
content = .videoMessage(WidgetDataPeer.Message.Content.VideoMessage(duration: Int32(duration)))
} else {
content = .video(WidgetDataPeer.Message.Content.Video())
}
case let .Audio(isVoice, duration, title, performer, _):
if isVoice {
content = .voiceMessage(WidgetDataPeer.Message.Content.VoiceMessage(duration: Int32(duration)))
} else {
content = .music(WidgetDataPeer.Message.Content.Music(artist: performer ?? "", title: title ?? "", duration: Int32(duration)))
}
default:
break
}
}
case let action as TelegramMediaAction:
switch action.action {
case let .phoneCall(_, _, _, isVideo):
content = .call(WidgetDataPeer.Message.Content.Call(isVideo: isVideo))
default:
break
}
case _ as TelegramMediaMap:
content = .mapLocation(WidgetDataPeer.Message.Content.MapLocation())
default:
break
}
}
self.init(text: message.text, content: content, timestamp: message.timestamp)
}
}

View File

@ -55,6 +55,12 @@ genrule(
rm -rf "$$BUILD_DIR"
mkdir -p "$$BUILD_DIR"
YASM_DIR="$$BUILD_DIR/yasm"
rm -rf "$$YASM_DIR"
mkdir -p "$$YASM_DIR"
tar -xf "$(location //third-party/yasm:yasm.tar)" -C "$$YASM_DIR"
ABS_YASM_DIR="$$(pwd)/$$(dirname $$YASM_DIR)/$$(basename $$YASM_DIR)"
cp $(location :build-libvpx-bazel.sh) "$$BUILD_DIR/"
cp $(location :0001-Add-support-for-arm64-iphonesimulator-gcc.patch) "$$BUILD_DIR/"
@ -81,6 +87,9 @@ genrule(
outs = ["Public/vpx/" + x for x in headers] +
["Public/vpx/vpx_config.h"] +
["Public/vpx/lib{}.a".format(x) for x in libs],
tools = [
"//third-party/yasm:yasm.tar",
],
visibility = [
"//visibility:public",
]

View File

@ -1,5 +1,5 @@
{
"app": "7.3",
"app": "7.3.1",
"bazel": "3.7.0",
"xcode": "12.3"
}