mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-05 05:51:42 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
9b1798e92b
24
Makefile
24
Makefile
@ -398,6 +398,7 @@ project: check_env kill_xcode
|
|||||||
|
|
||||||
bazel_app_debug_arm64:
|
bazel_app_debug_arm64:
|
||||||
APP_VERSION="${APP_VERSION}" \
|
APP_VERSION="${APP_VERSION}" \
|
||||||
|
TELEGRAM_DISABLE_EXTENSIONS="0" \
|
||||||
build-system/prepare-build.sh Telegram distribution
|
build-system/prepare-build.sh Telegram distribution
|
||||||
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_DEBUG_FLAGS} \
|
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_DEBUG_FLAGS} \
|
||||||
-c dbg \
|
-c dbg \
|
||||||
@ -409,6 +410,7 @@ bazel_app_arm64:
|
|||||||
APP_VERSION="${APP_VERSION}" \
|
APP_VERSION="${APP_VERSION}" \
|
||||||
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
||||||
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
||||||
|
TELEGRAM_DISABLE_EXTENSIONS="0" \
|
||||||
build-system/prepare-build.sh Telegram distribution
|
build-system/prepare-build.sh Telegram distribution
|
||||||
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_OPT_FLAGS} \
|
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_OPT_FLAGS} \
|
||||||
-c opt \
|
-c opt \
|
||||||
@ -422,6 +424,7 @@ bazel_app_armv7:
|
|||||||
APP_VERSION="${APP_VERSION}" \
|
APP_VERSION="${APP_VERSION}" \
|
||||||
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
||||||
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
||||||
|
TELEGRAM_DISABLE_EXTENSIONS="0" \
|
||||||
build-system/prepare-build.sh Telegram distribution
|
build-system/prepare-build.sh Telegram distribution
|
||||||
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_OPT_FLAGS} \
|
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_OPT_FLAGS} \
|
||||||
-c opt \
|
-c opt \
|
||||||
@ -435,6 +438,7 @@ bazel_app:
|
|||||||
APP_VERSION="${APP_VERSION}" \
|
APP_VERSION="${APP_VERSION}" \
|
||||||
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
||||||
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
||||||
|
TELEGRAM_DISABLE_EXTENSIONS="0" \
|
||||||
build-system/prepare-build.sh Telegram distribution
|
build-system/prepare-build.sh Telegram distribution
|
||||||
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_OPT_FLAGS} \
|
"${BAZEL}" build Telegram/Telegram ${BAZEL_CACHE_FLAGS} ${BAZEL_COMMON_FLAGS} ${BAZEL_OPT_FLAGS} \
|
||||||
-c opt \
|
-c opt \
|
||||||
@ -444,28 +448,24 @@ bazel_app:
|
|||||||
--output_groups=+dsyms \
|
--output_groups=+dsyms \
|
||||||
--verbose_failures
|
--verbose_failures
|
||||||
|
|
||||||
bazel_prepare_development_build:
|
bazel_project: kill_xcode
|
||||||
APP_VERSION="${APP_VERSION}" \
|
APP_VERSION="${APP_VERSION}" \
|
||||||
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
||||||
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
||||||
|
TELEGRAM_DISABLE_EXTENSIONS="0" \
|
||||||
build-system/prepare-build.sh Telegram development
|
build-system/prepare-build.sh Telegram development
|
||||||
|
|
||||||
bazel_project: kill_xcode bazel_prepare_development_build
|
|
||||||
APP_VERSION="${APP_VERSION}" \
|
APP_VERSION="${APP_VERSION}" \
|
||||||
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
||||||
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
||||||
build-system/generate-xcode-project.sh Telegram
|
build-system/generate-xcode-project.sh Telegram
|
||||||
|
|
||||||
bazel_soft_project: bazel_prepare_development_build
|
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}" \
|
APP_VERSION="${APP_VERSION}" \
|
||||||
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
||||||
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
||||||
build-system/generate-xcode-project.sh Telegram
|
build-system/generate-xcode-project.sh Telegram
|
||||||
|
|
||||||
bazel_opt_project: bazel_prepare_development_build
|
|
||||||
APP_VERSION="${APP_VERSION}" \
|
|
||||||
BAZEL_CACHE_DIR="${BAZEL_CACHE_DIR}" \
|
|
||||||
BAZEL_HTTP_CACHE_URL="${BAZEL_HTTP_CACHE_URL}" \
|
|
||||||
GENERATE_OPT_PROJECT=1 \
|
|
||||||
build-system/generate-xcode-project.sh Telegram
|
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ load(
|
|||||||
"telegram_bundle_id",
|
"telegram_bundle_id",
|
||||||
"telegram_aps_environment",
|
"telegram_aps_environment",
|
||||||
"telegram_team_id",
|
"telegram_team_id",
|
||||||
|
"telegram_disable_extensions",
|
||||||
)
|
)
|
||||||
|
|
||||||
config_setting(
|
config_setting(
|
||||||
@ -1528,7 +1529,7 @@ ios_application(
|
|||||||
strings = [
|
strings = [
|
||||||
":AppStringResources",
|
":AppStringResources",
|
||||||
],
|
],
|
||||||
extensions = [
|
extensions = [] if telegram_disable_extensions else [
|
||||||
":ShareExtension",
|
":ShareExtension",
|
||||||
":NotificationContentExtension",
|
":NotificationContentExtension",
|
||||||
":NotificationServiceExtension",
|
":NotificationServiceExtension",
|
||||||
|
|||||||
@ -5919,6 +5919,7 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"VoiceChat.StatusSpeaking" = "speaking";
|
"VoiceChat.StatusSpeaking" = "speaking";
|
||||||
"VoiceChat.StatusListening" = "listening";
|
"VoiceChat.StatusListening" = "listening";
|
||||||
|
"VoiceChat.StatusInvited" = "invited";
|
||||||
|
|
||||||
"VoiceChat.Connecting" = "Connecting...";
|
"VoiceChat.Connecting" = "Connecting...";
|
||||||
"VoiceChat.Reconnecting" = "Reconnecting...";
|
"VoiceChat.Reconnecting" = "Reconnecting...";
|
||||||
@ -5949,6 +5950,7 @@ Sorry for the inconvenience.";
|
|||||||
"VoiceChat.PanelJoin" = "Join";
|
"VoiceChat.PanelJoin" = "Join";
|
||||||
"VoiceChat.Title" = "Voice Chat";
|
"VoiceChat.Title" = "Voice Chat";
|
||||||
|
|
||||||
|
"VoiceChat.InviteMember" = "Invite Member";
|
||||||
"VoiceChat.UserInvited" = "You invited **%@** to the voice chat";
|
"VoiceChat.UserInvited" = "You invited **%@** to the voice chat";
|
||||||
|
|
||||||
"Notification.VoiceChatInvitation" = "%1$@ invited %2$@ to the voice chat";
|
"Notification.VoiceChatInvitation" = "%1$@ invited %2$@ to the voice chat";
|
||||||
@ -5974,6 +5976,7 @@ Sorry for the inconvenience.";
|
|||||||
"ChannelInfo.CreateVoiceChat" = "Start Voice Chat";
|
"ChannelInfo.CreateVoiceChat" = "Start Voice Chat";
|
||||||
|
|
||||||
"VoiceChat.AnonymousDisabledAlertText" = "Sorry, you can't join voice chat as an anonymous admin.";
|
"VoiceChat.AnonymousDisabledAlertText" = "Sorry, you can't join voice chat as an anonymous admin.";
|
||||||
|
"VoiceChat.ChatFullAlertText" = "Sorry, this voice chat has too many participants at the moment.";
|
||||||
|
|
||||||
"VoiceChat.EndConfirmationTitle" = "End voice chat";
|
"VoiceChat.EndConfirmationTitle" = "End voice chat";
|
||||||
"VoiceChat.EndConfirmationText" = "Are you sure you want to end this voice chat?";
|
"VoiceChat.EndConfirmationText" = "Are you sure you want to end this voice chat?";
|
||||||
|
|||||||
@ -1,104 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
copy_provisioning_profiles () {
|
|
||||||
if [ "$CODESIGNING_DATA_PATH" = "" ]; then
|
|
||||||
>&2 echo "CODESIGNING_DATA_PATH not defined"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
PROFILES_TYPE="$1"
|
|
||||||
case "$PROFILES_TYPE" in
|
|
||||||
development)
|
|
||||||
EXPECTED_VARIABLES=(\
|
|
||||||
WALLET_DEVELOPMENT_PROVISIONING_PROFILE_APP \
|
|
||||||
)
|
|
||||||
;;
|
|
||||||
distribution)
|
|
||||||
EXPECTED_VARIABLES=(\
|
|
||||||
WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP \
|
|
||||||
)
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown build provisioning type: $PROFILES_TYPE"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
EXPECTED_VARIABLE_NAMES=(\
|
|
||||||
Wallet \
|
|
||||||
)
|
|
||||||
|
|
||||||
local SEARCH_NAMES=()
|
|
||||||
|
|
||||||
local MISSING_VARIABLES="0"
|
|
||||||
for VARIABLE_NAME in ${EXPECTED_VARIABLES[@]}; do
|
|
||||||
if [ "${!VARIABLE_NAME}" = "" ]; then
|
|
||||||
echo "$VARIABLE_NAME not defined"
|
|
||||||
MISSING_VARIABLES="1"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$MISSING_VARIABLES" == "1" ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local VARIABLE_COUNT=${#EXPECTED_VARIABLES[@]}
|
|
||||||
for (( i=0; i<$VARIABLE_COUNT; i=i+1 )); do
|
|
||||||
VARIABLE_NAME="${EXPECTED_VARIABLES[$(($i))]}"
|
|
||||||
SEARCH_NAMES=("${SEARCH_NAMES[@]}" "${EXPECTED_VARIABLE_NAMES[$i]}" "${!VARIABLE_NAME}")
|
|
||||||
done
|
|
||||||
|
|
||||||
local DATA_PATH="build-input/data"
|
|
||||||
|
|
||||||
local OUTPUT_DIRECTORY="$DATA_PATH/provisioning-profiles"
|
|
||||||
rm -rf "$OUTPUT_DIRECTORY"
|
|
||||||
mkdir -p "$OUTPUT_DIRECTORY"
|
|
||||||
|
|
||||||
local BUILD_PATH="$OUTPUT_DIRECTORY/BUILD"
|
|
||||||
touch "$BUILD_PATH"
|
|
||||||
|
|
||||||
echo "exports_files([" >> "$BUILD_PATH"
|
|
||||||
|
|
||||||
local ELEMENT_COUNT=${#SEARCH_NAMES[@]}
|
|
||||||
local REMAINDER=$(($ELEMENT_COUNT % 2))
|
|
||||||
|
|
||||||
if [ $REMAINDER != 0 ]; then
|
|
||||||
>&2 echo "Expecting key-value pairs"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for PROFILE in `find "$CODESIGNING_DATA_PATH" -type f -name "*.mobileprovision"`; do
|
|
||||||
PROFILE_DATA=$(security cms -D -i "$PROFILE")
|
|
||||||
PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print :Name" /dev/stdin <<< $(echo $PROFILE_DATA))
|
|
||||||
for (( i=0; i<$ELEMENT_COUNT; i=i+2 )); do
|
|
||||||
ID=${SEARCH_NAMES[$i]}
|
|
||||||
SEARCH_NAME=${SEARCH_NAMES[$(($i + 1))]}
|
|
||||||
if [ "$PROFILE_NAME" = "$SEARCH_NAME" ]; then
|
|
||||||
VARIABLE_NAME="FOUND_PROFILE_$ID"
|
|
||||||
if [ "${!VARIABLE_NAME}" = "" ]; then
|
|
||||||
eval "FOUND_PROFILE_$ID=\"$PROFILE\""
|
|
||||||
else
|
|
||||||
>&2 echo "Found multiple profiles with name \"$SEARCH_NAME\""
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
for (( i=0; i<$ELEMENT_COUNT; i=i+2 )); do
|
|
||||||
ID=${SEARCH_NAMES[$i]}
|
|
||||||
SEARCH_NAME=${SEARCH_NAMES[$(($i + 1))]}
|
|
||||||
VARIABLE_NAME="FOUND_PROFILE_$ID"
|
|
||||||
FOUND_PROFILE="${!VARIABLE_NAME}"
|
|
||||||
if [ "$FOUND_PROFILE" = "" ]; then
|
|
||||||
>&2 echo "Profile \"$SEARCH_NAME\" not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp "$FOUND_PROFILE" "$OUTPUT_DIRECTORY/$ID.mobileprovision"
|
|
||||||
echo " \"$ID.mobileprovision\"," >> $BUILD_PATH
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "])" >> "$BUILD_PATH"
|
|
||||||
}
|
|
||||||
@ -35,6 +35,7 @@ prepare_build_variables () {
|
|||||||
IS_APPSTORE_BUILD \
|
IS_APPSTORE_BUILD \
|
||||||
APPSTORE_ID \
|
APPSTORE_ID \
|
||||||
APP_SPECIFIC_URL_SCHEME \
|
APP_SPECIFIC_URL_SCHEME \
|
||||||
|
TELEGRAM_DISABLE_EXTENSIONS \
|
||||||
)
|
)
|
||||||
|
|
||||||
local MISSING_VARIABLES="0"
|
local MISSING_VARIABLES="0"
|
||||||
@ -66,4 +67,10 @@ prepare_build_variables () {
|
|||||||
echo "telegram_appstore_id = \"$APPSTORE_ID\"" >> "$VARIABLES_PATH"
|
echo "telegram_appstore_id = \"$APPSTORE_ID\"" >> "$VARIABLES_PATH"
|
||||||
echo "telegram_app_specific_url_scheme = \"$APP_SPECIFIC_URL_SCHEME\"" >> "$VARIABLES_PATH"
|
echo "telegram_app_specific_url_scheme = \"$APP_SPECIFIC_URL_SCHEME\"" >> "$VARIABLES_PATH"
|
||||||
echo "telegram_aps_environment = \"$APS_ENVIRONMENT\"" >> "$VARIABLES_PATH"
|
echo "telegram_aps_environment = \"$APS_ENVIRONMENT\"" >> "$VARIABLES_PATH"
|
||||||
|
|
||||||
|
if [ "$TELEGRAM_DISABLE_EXTENSIONS" == "1" ]; then
|
||||||
|
echo "telegram_disable_extensions = True" >> "$VARIABLES_PATH"
|
||||||
|
else
|
||||||
|
echo "telegram_disable_extensions = False" >> "$VARIABLES_PATH"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
prepare_build_variables () {
|
|
||||||
BUILD_TYPE="$1"
|
|
||||||
case "$BUILD_TYPE" in
|
|
||||||
development)
|
|
||||||
APS_ENVIRONMENT="development"
|
|
||||||
;;
|
|
||||||
distribution)
|
|
||||||
APS_ENVIRONMENT="production"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown build provisioning type: $BUILD_TYPE"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
local BAZEL="$(which bazel)"
|
|
||||||
if [ "$BAZEL" = "" ]; then
|
|
||||||
echo "bazel not found in PATH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local EXPECTED_VARIABLES=(\
|
|
||||||
BUILD_NUMBER \
|
|
||||||
WALLET_APP_VERSION \
|
|
||||||
WALLET_BUNDLE_ID \
|
|
||||||
WALLET_DEVELOPMENT_TEAM \
|
|
||||||
)
|
|
||||||
|
|
||||||
local MISSING_VARIABLES="0"
|
|
||||||
for VARIABLE_NAME in ${EXPECTED_VARIABLES[@]}; do
|
|
||||||
if [ "${!VARIABLE_NAME}" = "" ]; then
|
|
||||||
echo "$VARIABLE_NAME not defined"
|
|
||||||
MISSING_VARIABLES="1"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$MISSING_VARIABLES" == "1" ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local VARIABLES_DIRECTORY="build-input/data"
|
|
||||||
mkdir -p "$VARIABLES_DIRECTORY"
|
|
||||||
local VARIABLES_PATH="$VARIABLES_DIRECTORY/variables.bzl"
|
|
||||||
rm -f "$VARIABLES_PATH"
|
|
||||||
|
|
||||||
echo "wallet_build_number = \"$BUILD_NUMBER\"" >> "$VARIABLES_PATH"
|
|
||||||
echo "wallet_version = \"$WALLET_APP_VERSION\"" >> "$VARIABLES_PATH"
|
|
||||||
echo "wallet_bundle_id = \"$WALLET_BUNDLE_ID\"" >> "$VARIABLES_PATH"
|
|
||||||
echo "wallet_api_id = \"$WALLET_API_ID\"" >> "$VARIABLES_PATH"
|
|
||||||
echo "wallet_team_id = \"$WALLET_DEVELOPMENT_TEAM\"" >> "$VARIABLES_PATH"
|
|
||||||
|
|
||||||
echo "telegram_api_id = \"1\"" >> "$VARIABLES_PATH"
|
|
||||||
echo "telegram_api_hash = \"1\"" >> "$VARIABLES_PATH"
|
|
||||||
echo "telegram_app_center_id = \"1\"" >> "$VARIABLES_PATH"
|
|
||||||
echo "telegram_appstore_id = \"1\"" >> "$VARIABLES_PATH"
|
|
||||||
echo "telegram_is_internal_build = \"false\"" >> "$VARIABLES_PATH"
|
|
||||||
echo "telegram_is_appstore_build = \"true\"" >> "$VARIABLES_PATH"
|
|
||||||
echo "telegram_app_specific_url_scheme = \"\"" >> "$VARIABLES_PATH"
|
|
||||||
}
|
|
||||||
@ -12,10 +12,6 @@ import Display
|
|||||||
import DeviceLocationManager
|
import DeviceLocationManager
|
||||||
import TemporaryCachedPeerDataManager
|
import TemporaryCachedPeerDataManager
|
||||||
|
|
||||||
#if ENABLE_WALLET
|
|
||||||
import WalletCore
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public final class TelegramApplicationOpenUrlCompletion {
|
public final class TelegramApplicationOpenUrlCompletion {
|
||||||
public let completion: (Bool) -> Void
|
public let completion: (Bool) -> Void
|
||||||
|
|
||||||
@ -647,14 +643,16 @@ public final class TonContext {
|
|||||||
public protocol ChatLocationContextHolder: class {
|
public protocol ChatLocationContextHolder: class {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public protocol AccountGroupCallContext: class {
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol AccountGroupCallContextCache: class {
|
||||||
|
}
|
||||||
|
|
||||||
public protocol AccountContext: class {
|
public protocol AccountContext: class {
|
||||||
var sharedContext: SharedAccountContext { get }
|
var sharedContext: SharedAccountContext { get }
|
||||||
var account: Account { get }
|
var account: Account { get }
|
||||||
|
|
||||||
#if ENABLE_WALLET
|
|
||||||
var tonContext: StoredTonContext? { get }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
var liveLocationManager: LiveLocationManager? { get }
|
var liveLocationManager: LiveLocationManager? { get }
|
||||||
var peersNearbyManager: PeersNearbyManager? { get }
|
var peersNearbyManager: PeersNearbyManager? { get }
|
||||||
var fetchManager: FetchManager { get }
|
var fetchManager: FetchManager { get }
|
||||||
@ -663,15 +661,12 @@ public protocol AccountContext: class {
|
|||||||
var wallpaperUploadManager: WallpaperUploadManager? { get }
|
var wallpaperUploadManager: WallpaperUploadManager? { get }
|
||||||
var watchManager: WatchManager? { get }
|
var watchManager: WatchManager? { get }
|
||||||
|
|
||||||
#if ENABLE_WALLET
|
|
||||||
var hasWallets: Signal<Bool, NoError> { get }
|
|
||||||
var hasWalletAccess: Signal<Bool, NoError> { get }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
var currentLimitsConfiguration: Atomic<LimitsConfiguration> { get }
|
var currentLimitsConfiguration: Atomic<LimitsConfiguration> { get }
|
||||||
var currentContentSettings: Atomic<ContentSettings> { get }
|
var currentContentSettings: Atomic<ContentSettings> { get }
|
||||||
var currentAppConfiguration: Atomic<AppConfiguration> { get }
|
var currentAppConfiguration: Atomic<AppConfiguration> { get }
|
||||||
|
|
||||||
|
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
|
||||||
|
|
||||||
func storeSecureIdPassword(password: String)
|
func storeSecureIdPassword(password: String)
|
||||||
func getStoredSecureIdPassword() -> String?
|
func getStoredSecureIdPassword() -> String?
|
||||||
|
|
||||||
|
|||||||
@ -194,20 +194,20 @@ public struct PresentationGroupCallSummaryState: Equatable {
|
|||||||
public var participantCount: Int
|
public var participantCount: Int
|
||||||
public var callState: PresentationGroupCallState
|
public var callState: PresentationGroupCallState
|
||||||
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
||||||
public var numberOfActiveSpeakers: Int
|
public var activeSpeakers: Set<PeerId>
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
info: GroupCallInfo,
|
info: GroupCallInfo,
|
||||||
participantCount: Int,
|
participantCount: Int,
|
||||||
callState: PresentationGroupCallState,
|
callState: PresentationGroupCallState,
|
||||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||||
numberOfActiveSpeakers: Int
|
activeSpeakers: Set<PeerId>
|
||||||
) {
|
) {
|
||||||
self.info = info
|
self.info = info
|
||||||
self.participantCount = participantCount
|
self.participantCount = participantCount
|
||||||
self.callState = callState
|
self.callState = callState
|
||||||
self.topParticipants = topParticipants
|
self.topParticipants = topParticipants
|
||||||
self.numberOfActiveSpeakers = numberOfActiveSpeakers
|
self.activeSpeakers = activeSpeakers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,7 +286,7 @@ public protocol PresentationGroupCall: class {
|
|||||||
func updateMuteState(peerId: PeerId, isMuted: Bool)
|
func updateMuteState(peerId: PeerId, isMuted: Bool)
|
||||||
|
|
||||||
func invitePeer(_ peerId: PeerId)
|
func invitePeer(_ peerId: PeerId)
|
||||||
var invitedPeers: Signal<Set<PeerId>, NoError> { get }
|
var invitedPeers: Signal<[PeerId], NoError> { get }
|
||||||
|
|
||||||
var sourcePanel: ASDisplayNode? { get set }
|
var sourcePanel: ASDisplayNode? { get set }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ swift_library(
|
|||||||
"//submodules/SyncCore:SyncCore",
|
"//submodules/SyncCore:SyncCore",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
"//submodules/AvatarNode:AvatarNode",
|
"//submodules/AvatarNode:AvatarNode",
|
||||||
|
"//submodules/AudioBlob:AudioBlob",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import Postbox
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SyncCore
|
import SyncCore
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import AudioBlob
|
||||||
|
|
||||||
public final class AnimatedAvatarSetContext {
|
public final class AnimatedAvatarSetContext {
|
||||||
public final class Content {
|
public final class Content {
|
||||||
@ -45,10 +46,6 @@ public final class AnimatedAvatarSetContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func update(peers: [Peer], animated: Bool) -> Content {
|
public func update(peers: [Peer], animated: Bool) -> Content {
|
||||||
for peer in peers {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var items: [(Content.Item.Key, Content.Item)] = []
|
var items: [(Content.Item.Key, Content.Item)] = []
|
||||||
for peer in peers {
|
for peer in peers {
|
||||||
items.append((Content.Item.Key(peerId: peer.id), Content.Item(peer: peer)))
|
items.append((Content.Item.Key(peerId: peer.id), Content.Item(peer: peer)))
|
||||||
@ -60,6 +57,7 @@ public final class AnimatedAvatarSetContext {
|
|||||||
private let avatarFont = avatarPlaceholderFont(size: 12.0)
|
private let avatarFont = avatarPlaceholderFont(size: 12.0)
|
||||||
|
|
||||||
private final class ContentNode: ASDisplayNode {
|
private final class ContentNode: ASDisplayNode {
|
||||||
|
private var audioLevelView: VoiceBlobView?
|
||||||
private let unclippedNode: ASImageNode
|
private let unclippedNode: ASImageNode
|
||||||
private let clippedNode: ASImageNode
|
private let clippedNode: ASImageNode
|
||||||
|
|
||||||
@ -137,6 +135,51 @@ private final class ContentNode: ASDisplayNode {
|
|||||||
self.clippedNode.alpha = isClipped ? 1.0 : 0.0
|
self.clippedNode.alpha = isClipped ? 1.0 : 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAudioLevel(color: UIColor, value: Float) {
|
||||||
|
if self.audioLevelView == nil, value > 0.0 {
|
||||||
|
let blobFrame = self.unclippedNode.bounds.insetBy(dx: -8.0, dy: -8.0)
|
||||||
|
|
||||||
|
let audioLevelView = VoiceBlobView(
|
||||||
|
frame: blobFrame,
|
||||||
|
maxLevel: 0.3,
|
||||||
|
smallBlobRange: (0, 0),
|
||||||
|
mediumBlobRange: (0.7, 0.8),
|
||||||
|
bigBlobRange: (0.8, 0.9)
|
||||||
|
)
|
||||||
|
|
||||||
|
let maskRect = CGRect(origin: .zero, size: blobFrame.size)
|
||||||
|
let playbackMaskLayer = CAShapeLayer()
|
||||||
|
playbackMaskLayer.frame = maskRect
|
||||||
|
playbackMaskLayer.fillRule = .evenOdd
|
||||||
|
let maskPath = UIBezierPath()
|
||||||
|
maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 12, dy: 12), cornerRadius: 22))
|
||||||
|
maskPath.append(UIBezierPath(rect: maskRect))
|
||||||
|
playbackMaskLayer.path = maskPath.cgPath
|
||||||
|
audioLevelView.layer.mask = playbackMaskLayer
|
||||||
|
|
||||||
|
audioLevelView.setColor(color)
|
||||||
|
self.audioLevelView = audioLevelView
|
||||||
|
self.view.insertSubview(audioLevelView, at: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let level = min(1.0, max(0.0, CGFloat(value)))
|
||||||
|
if let audioLevelView = self.audioLevelView {
|
||||||
|
audioLevelView.updateLevel(CGFloat(value) * 2.0)
|
||||||
|
|
||||||
|
let avatarScale: CGFloat
|
||||||
|
if value > 0.0 {
|
||||||
|
audioLevelView.startAnimating()
|
||||||
|
avatarScale = 1.03 + level * 0.07
|
||||||
|
} else {
|
||||||
|
audioLevelView.stopAnimating(duration: 0.5)
|
||||||
|
avatarScale = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||||
|
transition.updateSublayerTransformScale(node: self, scale: CGPoint(x: avatarScale, y: avatarScale), beginWithCurrentState: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class AnimatedAvatarSetNode: ASDisplayNode {
|
public final class AnimatedAvatarSetNode: ASDisplayNode {
|
||||||
@ -159,7 +202,9 @@ public final class AnimatedAvatarSetNode: ASDisplayNode {
|
|||||||
|
|
||||||
var validKeys: [AnimatedAvatarSetContext.Content.Item.Key] = []
|
var validKeys: [AnimatedAvatarSetContext.Content.Item.Key] = []
|
||||||
var index = 0
|
var index = 0
|
||||||
for (key, item) in content.items {
|
for i in 0 ..< content.items.count {
|
||||||
|
let (key, item) = content.items[i]
|
||||||
|
|
||||||
validKeys.append(key)
|
validKeys.append(key)
|
||||||
|
|
||||||
let itemFrame = CGRect(origin: CGPoint(x: contentWidth, y: 0.0), size: itemSize)
|
let itemFrame = CGRect(origin: CGPoint(x: contentWidth, y: 0.0), size: itemSize)
|
||||||
@ -180,6 +225,7 @@ public final class AnimatedAvatarSetNode: ASDisplayNode {
|
|||||||
itemNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
|
itemNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
itemNode.zPosition = CGFloat(100 - i)
|
||||||
contentWidth += itemSize.width - 10.0
|
contentWidth += itemSize.width - 10.0
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
@ -201,4 +247,14 @@ public final class AnimatedAvatarSetNode: ASDisplayNode {
|
|||||||
|
|
||||||
return CGSize(width: contentWidth, height: contentHeight)
|
return CGSize(width: contentWidth, height: contentHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func updateAudioLevels(color: UIColor, levels: [PeerId: Float]) {
|
||||||
|
for (key, itemNode) in self.contentNodes {
|
||||||
|
if let value = levels[key.peerId] {
|
||||||
|
itemNode.updateAudioLevel(color: color, value: value)
|
||||||
|
} else {
|
||||||
|
itemNode.updateAudioLevel(color: color, value: 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -153,7 +153,6 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class BlobView: UIView {
|
final class BlobView: UIView {
|
||||||
|
|
||||||
let pointsCount: Int
|
let pointsCount: Int
|
||||||
let smoothness: CGFloat
|
let smoothness: CGFloat
|
||||||
|
|
||||||
@ -169,7 +168,6 @@ final class BlobView: UIView {
|
|||||||
|
|
||||||
var scaleLevelsToBalance = [CGFloat]()
|
var scaleLevelsToBalance = [CGFloat]()
|
||||||
|
|
||||||
// If true ignores randomness and pointsCount
|
|
||||||
let isCircle: Bool
|
let isCircle: Bool
|
||||||
|
|
||||||
var level: CGFloat = 0 {
|
var level: CGFloat = 0 {
|
||||||
|
|||||||
@ -2618,6 +2618,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
private final class ChatListTabBarContextExtractedContentSource: ContextExtractedContentSource {
|
private final class ChatListTabBarContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
let keepInPlace: Bool = true
|
let keepInPlace: Bool = true
|
||||||
let ignoreContentTouches: Bool = true
|
let ignoreContentTouches: Bool = true
|
||||||
|
let blurBackground: Bool = true
|
||||||
|
|
||||||
private let controller: ChatListController
|
private let controller: ChatListController
|
||||||
private let sourceNode: ContextExtractedContentContainingNode
|
private let sourceNode: ContextExtractedContentContainingNode
|
||||||
@ -2639,6 +2640,7 @@ private final class ChatListTabBarContextExtractedContentSource: ContextExtracte
|
|||||||
private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource {
|
private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
let keepInPlace: Bool
|
let keepInPlace: Bool
|
||||||
let ignoreContentTouches: Bool = true
|
let ignoreContentTouches: Bool = true
|
||||||
|
let blurBackground: Bool = true
|
||||||
|
|
||||||
private let controller: ChatListController
|
private let controller: ChatListController
|
||||||
private let sourceNode: ContextExtractedContentContainingNode
|
private let sourceNode: ContextExtractedContentContainingNode
|
||||||
|
|||||||
@ -956,6 +956,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
private final class MessageContextExtractedContentSource: ContextExtractedContentSource {
|
private final class MessageContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
let keepInPlace: Bool = false
|
let keepInPlace: Bool = false
|
||||||
let ignoreContentTouches: Bool = true
|
let ignoreContentTouches: Bool = true
|
||||||
|
let blurBackground: Bool = true
|
||||||
|
|
||||||
private let sourceNode: ContextExtractedContentContainingNode
|
private let sourceNode: ContextExtractedContentContainingNode
|
||||||
|
|
||||||
|
|||||||
@ -66,7 +66,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void) {
|
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.feedbackTap = feedbackTap
|
self.feedbackTap = feedbackTap
|
||||||
|
|
||||||
@ -95,6 +95,9 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
|||||||
self.cornerRadius = 14.0
|
self.cornerRadius = 14.0
|
||||||
|
|
||||||
self.backgroundColor = presentationData.theme.contextMenu.backgroundColor
|
self.backgroundColor = presentationData.theme.contextMenu.backgroundColor
|
||||||
|
if !blurBackground {
|
||||||
|
self.backgroundColor = self.backgroundColor?.withAlphaComponent(1.0)
|
||||||
|
}
|
||||||
|
|
||||||
self.itemNodes.forEach({ itemNode in
|
self.itemNodes.forEach({ itemNode in
|
||||||
switch itemNode {
|
switch itemNode {
|
||||||
@ -402,8 +405,8 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, displayTextSelectionTip: Bool) {
|
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, displayTextSelectionTip: Bool, blurBackground: Bool) {
|
||||||
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items, getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap)
|
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items, getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
|
||||||
if displayTextSelectionTip {
|
if displayTextSelectionTip {
|
||||||
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData)
|
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData)
|
||||||
textSelectionTipNode.isUserInteractionEnabled = false
|
textSelectionTipNode.isUserInteractionEnabled = false
|
||||||
|
|||||||
@ -137,6 +137,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
|
|
||||||
private let itemsDisposable = MetaDisposable()
|
private let itemsDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private let blurBackground: Bool
|
||||||
|
|
||||||
init(account: Account, controller: ContextController, presentationData: PresentationData, source: ContextContentSource, items: Signal<[ContextMenuItem], NoError>, reactionItems: [ReactionContextItem], beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?, gesture: ContextGesture?, reactionSelected: @escaping (ReactionContextItem.Reaction) -> Void, beganAnimatingOut: @escaping () -> Void, attemptTransitionControllerIntoNavigation: @escaping () -> Void, displayTextSelectionTip: Bool) {
|
init(account: Account, controller: ContextController, presentationData: PresentationData, source: ContextContentSource, items: Signal<[ContextMenuItem], NoError>, reactionItems: [ReactionContextItem], beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?, gesture: ContextGesture?, reactionSelected: @escaping (ReactionContextItem.Reaction) -> Void, beganAnimatingOut: @escaping () -> Void, attemptTransitionControllerIntoNavigation: @escaping () -> Void, displayTextSelectionTip: Bool) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.source = source
|
self.source = source
|
||||||
@ -188,13 +190,19 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
|
|
||||||
var feedbackTap: (() -> Void)?
|
var feedbackTap: (() -> Void)?
|
||||||
|
|
||||||
|
var blurBackground = true
|
||||||
|
if case let .extracted(extractedSource) = source, !extractedSource.blurBackground {
|
||||||
|
blurBackground = false
|
||||||
|
}
|
||||||
|
self.blurBackground = blurBackground
|
||||||
|
|
||||||
self.actionsContainerNode = ContextActionsContainerNode(presentationData: presentationData, items: [], getController: { [weak controller] in
|
self.actionsContainerNode = ContextActionsContainerNode(presentationData: presentationData, items: [], getController: { [weak controller] in
|
||||||
return controller
|
return controller
|
||||||
}, actionSelected: { result in
|
}, actionSelected: { result in
|
||||||
beginDismiss(result)
|
beginDismiss(result)
|
||||||
}, feedbackTap: {
|
}, feedbackTap: {
|
||||||
feedbackTap?()
|
feedbackTap?()
|
||||||
}, displayTextSelectionTip: self.displayTextSelectionTip)
|
}, displayTextSelectionTip: self.displayTextSelectionTip, blurBackground: blurBackground)
|
||||||
|
|
||||||
if !reactionItems.isEmpty {
|
if !reactionItems.isEmpty {
|
||||||
let reactionContextNode = ReactionContextNode(account: account, theme: presentationData.theme, items: reactionItems)
|
let reactionContextNode = ReactionContextNode(account: account, theme: presentationData.theme, items: reactionItems)
|
||||||
@ -211,9 +219,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
|
|
||||||
self.scrollNode.view.delegate = self
|
self.scrollNode.view.delegate = self
|
||||||
|
|
||||||
self.view.addSubview(self.effectView)
|
if blurBackground {
|
||||||
self.addSubnode(self.dimNode)
|
self.view.addSubview(self.effectView)
|
||||||
self.addSubnode(self.withoutBlurDimNode)
|
self.addSubnode(self.dimNode)
|
||||||
|
self.addSubnode(self.withoutBlurDimNode)
|
||||||
|
}
|
||||||
|
|
||||||
self.addSubnode(self.clippingNode)
|
self.addSubnode(self.clippingNode)
|
||||||
|
|
||||||
@ -1028,7 +1038,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
self?.beginDismiss(result)
|
self?.beginDismiss(result)
|
||||||
}, feedbackTap: { [weak self] in
|
}, feedbackTap: { [weak self] in
|
||||||
self?.hapticFeedback.tap()
|
self?.hapticFeedback.tap()
|
||||||
}, displayTextSelectionTip: self.displayTextSelectionTip)
|
}, displayTextSelectionTip: self.displayTextSelectionTip, blurBackground: self.blurBackground)
|
||||||
self.scrollNode.insertSubnode(self.actionsContainerNode, aboveSubnode: previousActionsContainerNode)
|
self.scrollNode.insertSubnode(self.actionsContainerNode, aboveSubnode: previousActionsContainerNode)
|
||||||
|
|
||||||
if let layout = self.validLayout {
|
if let layout = self.validLayout {
|
||||||
@ -1074,7 +1084,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
|
|
||||||
switch layout.metrics.widthClass {
|
switch layout.metrics.widthClass {
|
||||||
case .compact:
|
case .compact:
|
||||||
if self.effectView.superview == nil {
|
if case let .extracted(extractedSource) = self.source, !extractedSource.blurBackground {
|
||||||
|
} else if self.effectView.superview == nil {
|
||||||
self.view.insertSubview(self.effectView, at: 0)
|
self.view.insertSubview(self.effectView, at: 0)
|
||||||
if #available(iOS 10.0, *) {
|
if #available(iOS 10.0, *) {
|
||||||
if let propertyAnimator = self.propertyAnimator {
|
if let propertyAnimator = self.propertyAnimator {
|
||||||
@ -1088,7 +1099,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
self.dimNode.isHidden = false
|
self.dimNode.isHidden = false
|
||||||
self.withoutBlurDimNode.isHidden = true
|
self.withoutBlurDimNode.isHidden = true
|
||||||
case .regular:
|
case .regular:
|
||||||
if self.effectView.superview != nil {
|
if case let .extracted(extractedSource) = self.source, !extractedSource.blurBackground {
|
||||||
|
} else if self.effectView.superview != nil {
|
||||||
self.effectView.removeFromSuperview()
|
self.effectView.removeFromSuperview()
|
||||||
self.withoutBlurDimNode.alpha = 1.0
|
self.withoutBlurDimNode.alpha = 1.0
|
||||||
}
|
}
|
||||||
@ -1471,6 +1483,7 @@ public final class ContextControllerPutBackViewInfo {
|
|||||||
public protocol ContextExtractedContentSource: class {
|
public protocol ContextExtractedContentSource: class {
|
||||||
var keepInPlace: Bool { get }
|
var keepInPlace: Bool { get }
|
||||||
var ignoreContentTouches: Bool { get }
|
var ignoreContentTouches: Bool { get }
|
||||||
|
var blurBackground: Bool { get }
|
||||||
|
|
||||||
func takeView() -> ContextControllerTakeViewInfo?
|
func takeView() -> ContextControllerTakeViewInfo?
|
||||||
func putBack() -> ContextControllerPutBackViewInfo?
|
func putBack() -> ContextControllerPutBackViewInfo?
|
||||||
@ -1528,7 +1541,11 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
|||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = .Hide
|
if case let .extracted(extractedSource) = source, !extractedSource.blurBackground {
|
||||||
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
|
} else {
|
||||||
|
self.statusBar.statusBarStyle = .Hide
|
||||||
|
}
|
||||||
self.lockOrientation = true
|
self.lockOrientation = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1009,8 +1009,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset, topItemFound {
|
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset, topItemFound {
|
||||||
completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset - effectiveInsets.bottom - effectiveInsets.top)
|
if !self.stackFromBottom {
|
||||||
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
|
completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset - effectiveInsets.bottom - effectiveInsets.top)
|
||||||
|
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
|
||||||
|
} else {
|
||||||
|
effectiveInsets.top = self.visibleSize.height - completeHeight
|
||||||
|
completeHeight = max(completeHeight, self.visibleSize.height)
|
||||||
|
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var transition: ContainedViewLayoutTransition = .immediate
|
var transition: ContainedViewLayoutTransition = .immediate
|
||||||
@ -1413,8 +1419,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset {
|
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset {
|
||||||
completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset)
|
if !self.stackFromBottom {
|
||||||
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
|
completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset)
|
||||||
|
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.stackFromBottom {
|
if self.stackFromBottom {
|
||||||
@ -1435,11 +1443,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
self.ignoreScrollingEvents = true
|
self.ignoreScrollingEvents = true
|
||||||
if topItemFound && bottomItemFound {
|
if topItemFound && bottomItemFound {
|
||||||
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: completeHeight)
|
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: completeHeight)
|
||||||
if self.stackFromBottom {
|
self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge)
|
||||||
self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge)
|
|
||||||
} else {
|
|
||||||
self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge)
|
|
||||||
}
|
|
||||||
self.scroller.contentOffset = self.lastContentOffset
|
self.scroller.contentOffset = self.lastContentOffset
|
||||||
} else if topItemFound {
|
} else if topItemFound {
|
||||||
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0)
|
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0)
|
||||||
|
|||||||
@ -405,7 +405,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
inCallStatusBar.callStatusBarNode?.update(size: inCallStatusBarFrame.size)
|
inCallStatusBar.callStatusBarNode?.update(size: inCallStatusBarFrame.size)
|
||||||
inCallStatusBar.callStatusBarNode?.frame = inCallStatusBarFrame
|
inCallStatusBar.callStatusBarNode?.frame = inCallStatusBarFrame
|
||||||
layout.statusBarHeight = inCallStatusBarFrame.height
|
layout.statusBarHeight = inCallStatusBarFrame.height
|
||||||
self.inCallStatusBar?.frame = inCallStatusBarFrame
|
inCallStatusBar.frame = inCallStatusBarFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
if let globalOverlayContainerParent = self.globalOverlayContainerParent {
|
if let globalOverlayContainerParent = self.globalOverlayContainerParent {
|
||||||
@ -553,9 +553,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
self.displayNode.insertSubnode(overlayContainer, belowSubnode: previousOverlayContainer)
|
self.displayNode.insertSubnode(overlayContainer, belowSubnode: previousOverlayContainer)
|
||||||
} else if let globalScrollToTopNode = self.globalScrollToTopNode {
|
} else if let globalScrollToTopNode = self.globalScrollToTopNode {
|
||||||
self.displayNode.insertSubnode(overlayContainer, belowSubnode: globalScrollToTopNode)
|
self.displayNode.insertSubnode(overlayContainer, belowSubnode: globalScrollToTopNode)
|
||||||
} else if let globalOverlayContainerParent = self.globalOverlayContainerParent {
|
} else if let globalOverlayContainerParent = self.globalOverlayContainerParent {
|
||||||
self.displayNode.insertSubnode(overlayContainer, belowSubnode: globalOverlayContainerParent)
|
self.displayNode.insertSubnode(overlayContainer, belowSubnode: globalOverlayContainerParent)
|
||||||
}else {
|
} else {
|
||||||
self.displayNode.addSubnode(overlayContainer)
|
self.displayNode.addSubnode(overlayContainer)
|
||||||
}
|
}
|
||||||
overlayContainer.transitionIn()
|
overlayContainer.transitionIn()
|
||||||
@ -608,7 +608,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
}
|
}
|
||||||
|
|
||||||
let effectiveModalTransition: CGFloat
|
let effectiveModalTransition: CGFloat
|
||||||
if visibleModalCount == 0 {
|
if visibleModalCount == 0 || navigationLayout.modal[i].isFlat {
|
||||||
effectiveModalTransition = 0.0
|
effectiveModalTransition = 0.0
|
||||||
} else if visibleModalCount == 1 {
|
} else if visibleModalCount == 1 {
|
||||||
effectiveModalTransition = 1.0 - topModalDismissProgress
|
effectiveModalTransition = 1.0 - topModalDismissProgress
|
||||||
@ -635,7 +635,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if modalContainer.supernode != nil {
|
if modalContainer.supernode != nil {
|
||||||
if !hasVisibleStandaloneModal && !isStandaloneModal {
|
if !hasVisibleStandaloneModal && !isStandaloneModal && !modalContainer.isFlat {
|
||||||
visibleModalCount += 1
|
visibleModalCount += 1
|
||||||
}
|
}
|
||||||
if isStandaloneModal {
|
if isStandaloneModal {
|
||||||
@ -643,6 +643,8 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
visibleModalCount = 0
|
visibleModalCount = 0
|
||||||
}
|
}
|
||||||
if previousModalContainer == nil {
|
if previousModalContainer == nil {
|
||||||
|
topModalIsFlat = modalContainer.isFlat
|
||||||
|
|
||||||
topModalDismissProgress = modalContainer.dismissProgress
|
topModalDismissProgress = modalContainer.dismissProgress
|
||||||
if case .compact = layout.metrics.widthClass {
|
if case .compact = layout.metrics.widthClass {
|
||||||
modalContainer.keyboardViewManager = self.keyboardViewManager
|
modalContainer.keyboardViewManager = self.keyboardViewManager
|
||||||
@ -665,8 +667,6 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
topModalIsFlat = modalContainer.isFlat
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch navigationLayout.root {
|
switch navigationLayout.root {
|
||||||
@ -1357,7 +1357,6 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
inCallStatusBar.inCallNavigate = { [weak self] in
|
inCallStatusBar.inCallNavigate = { [weak self] in
|
||||||
self?.scrollToTop(.master)
|
self?.scrollToTop(.master)
|
||||||
}
|
}
|
||||||
inCallStatusBar.alpha = 0.0
|
|
||||||
self.inCallStatusBar = inCallStatusBar
|
self.inCallStatusBar = inCallStatusBar
|
||||||
|
|
||||||
var bottomOverlayContainer: NavigationOverlayContainer?
|
var bottomOverlayContainer: NavigationOverlayContainer?
|
||||||
@ -1375,15 +1374,17 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
} else {
|
} else {
|
||||||
self.displayNode.addSubnode(inCallStatusBar)
|
self.displayNode.addSubnode(inCallStatusBar)
|
||||||
}
|
}
|
||||||
transition.updateAlpha(node: inCallStatusBar, alpha: 1.0)
|
if case let .animated(duration, _) = transition {
|
||||||
|
inCallStatusBar.layer.animatePosition(from: CGPoint(x: 0.0, y: -64.0), to: CGPoint(), duration: duration, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, additive: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let layout = self.validLayout {
|
if let layout = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout, transition: transition)
|
|
||||||
inCallStatusBar.updateState(statusBar: nil, withSafeInsets: !layout.safeInsets.top.isZero, inCallNode: forceInCallStatusBar, animated: false)
|
inCallStatusBar.updateState(statusBar: nil, withSafeInsets: !layout.safeInsets.top.isZero, inCallNode: forceInCallStatusBar, animated: false)
|
||||||
|
self.containerLayoutUpdated(layout, transition: transition)
|
||||||
}
|
}
|
||||||
} else if let inCallStatusBar = self.inCallStatusBar {
|
} else if let inCallStatusBar = self.inCallStatusBar {
|
||||||
self.inCallStatusBar = nil
|
self.inCallStatusBar = nil
|
||||||
transition.updateAlpha(node: inCallStatusBar, alpha: 0.0, completion: { [weak inCallStatusBar] _ in
|
transition.updatePosition(node: inCallStatusBar, position: CGPoint(x: inCallStatusBar.position.x, y: -64.0), completion: { [weak inCallStatusBar] _ in
|
||||||
inCallStatusBar?.removeFromSupernode()
|
inCallStatusBar?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
if let layout = self.validLayout {
|
if let layout = self.validLayout {
|
||||||
|
|||||||
@ -109,9 +109,10 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
|||||||
panRecognizer.delegate = self
|
panRecognizer.delegate = self
|
||||||
panRecognizer.delaysTouchesBegan = false
|
panRecognizer.delaysTouchesBegan = false
|
||||||
panRecognizer.cancelsTouchesInView = true
|
panRecognizer.cancelsTouchesInView = true
|
||||||
self.view.addGestureRecognizer(panRecognizer)
|
if !self.isFlat {
|
||||||
|
self.view.addGestureRecognizer(panRecognizer)
|
||||||
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
@ -335,9 +336,13 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
|||||||
switch layout.metrics.widthClass {
|
switch layout.metrics.widthClass {
|
||||||
case .compact:
|
case .compact:
|
||||||
self.panRecognizer?.isEnabled = true
|
self.panRecognizer?.isEnabled = true
|
||||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
|
|
||||||
self.container.clipsToBounds = true
|
self.container.clipsToBounds = true
|
||||||
if isStandaloneModal || isLandscape {
|
if self.isFlat {
|
||||||
|
self.dim.backgroundColor = .clear
|
||||||
|
} else {
|
||||||
|
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
|
||||||
|
}
|
||||||
|
if isStandaloneModal || isLandscape || self.isFlat {
|
||||||
self.container.cornerRadius = 0.0
|
self.container.cornerRadius = 0.0
|
||||||
} else {
|
} else {
|
||||||
self.container.cornerRadius = 10.0
|
self.container.cornerRadius = 10.0
|
||||||
@ -361,10 +366,9 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
|||||||
containerFrame = unscaledFrame
|
containerFrame = unscaledFrame
|
||||||
} else {
|
} else {
|
||||||
topInset = 10.0
|
topInset = 10.0
|
||||||
if self.isFlat, let preferredSize = controllers.last?.preferredContentSizeForLayout(layout) {
|
if self.isFlat {
|
||||||
topInset = layout.size.height - preferredSize.height
|
topInset = 0.0
|
||||||
}
|
} else if let statusBarHeight = layout.statusBarHeight {
|
||||||
if let statusBarHeight = layout.statusBarHeight {
|
|
||||||
topInset += statusBarHeight
|
topInset += statusBarHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,11 +382,20 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
|||||||
}
|
}
|
||||||
case .regular:
|
case .regular:
|
||||||
self.panRecognizer?.isEnabled = false
|
self.panRecognizer?.isEnabled = false
|
||||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
|
if self.isFlat {
|
||||||
self.container.clipsToBounds = true
|
self.dim.backgroundColor = .clear
|
||||||
self.container.cornerRadius = 10.0
|
self.container.clipsToBounds = true
|
||||||
if #available(iOS 11.0, *) {
|
self.container.cornerRadius = 0.0
|
||||||
self.container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
if #available(iOS 11.0, *) {
|
||||||
|
self.container.layer.maskedCorners = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
|
||||||
|
self.container.clipsToBounds = true
|
||||||
|
self.container.cornerRadius = 10.0
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
self.container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let verticalInset: CGFloat = 44.0
|
let verticalInset: CGFloat = 44.0
|
||||||
@ -408,6 +421,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
|||||||
|
|
||||||
func animateIn(transition: ContainedViewLayoutTransition) {
|
func animateIn(transition: ContainedViewLayoutTransition) {
|
||||||
if let controller = self.container.controllers.first, case .standaloneModal = controller.navigationPresentation {
|
if let controller = self.container.controllers.first, case .standaloneModal = controller.navigationPresentation {
|
||||||
|
} else if self.isFlat {
|
||||||
} else {
|
} else {
|
||||||
transition.updateAlpha(node: self.dim, alpha: 1.0)
|
transition.updateAlpha(node: self.dim, alpha: 1.0)
|
||||||
transition.animatePositionAdditive(node: self.container, offset: CGPoint(x: 0.0, y: self.bounds.height + self.container.bounds.height / 2.0 - (self.container.position.y - self.bounds.height)))
|
transition.animatePositionAdditive(node: self.container, offset: CGPoint(x: 0.0, y: self.bounds.height + self.container.bounds.height / 2.0 - (self.container.position.y - self.bounds.height)))
|
||||||
@ -429,7 +443,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
|||||||
completion()
|
completion()
|
||||||
return transition
|
return transition
|
||||||
} else {
|
} else {
|
||||||
if transition.isAnimated {
|
if transition.isAnimated && !self.isFlat {
|
||||||
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||||
let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||||
alphaTransition.updateAlpha(node: self.dim, alpha: 0.0, beginWithCurrentState: true)
|
alphaTransition.updateAlpha(node: self.dim, alpha: 0.0, beginWithCurrentState: true)
|
||||||
|
|||||||
@ -918,9 +918,10 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.backButtonNode.alpha = 1.0
|
self.backButtonNode.alpha = 1.0
|
||||||
self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize)
|
transition.updateFrame(node: self.backButtonNode, frame: CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize))
|
||||||
|
|
||||||
self.backButtonArrow.alpha = 1.0
|
self.backButtonArrow.alpha = 1.0
|
||||||
self.backButtonArrow.frame = CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0))
|
transition.updateFrame(node: self.backButtonArrow, frame: CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)))
|
||||||
self.badgeNode.alpha = 1.0
|
self.badgeNode.alpha = 1.0
|
||||||
}
|
}
|
||||||
} else if self.leftButtonNode.supernode != nil {
|
} else if self.leftButtonNode.supernode != nil {
|
||||||
@ -928,18 +929,18 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
leftTitleInset += leftButtonSize.width + leftButtonInset + 1.0
|
leftTitleInset += leftButtonSize.width + leftButtonInset + 1.0
|
||||||
|
|
||||||
self.leftButtonNode.alpha = 1.0
|
self.leftButtonNode.alpha = 1.0
|
||||||
self.leftButtonNode.frame = CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize)
|
transition.updateFrame(node: self.leftButtonNode, frame: CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
let badgeSize = self.badgeNode.measure(CGSize(width: 200.0, height: 100.0))
|
let badgeSize = self.badgeNode.measure(CGSize(width: 200.0, height: 100.0))
|
||||||
let backButtonArrowFrame = self.backButtonArrow.frame
|
let backButtonArrowFrame = self.backButtonArrow.frame
|
||||||
self.badgeNode.frame = CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 7.0, dy: -9.0), size: badgeSize)
|
transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 7.0, dy: -9.0), size: badgeSize))
|
||||||
|
|
||||||
if self.rightButtonNode.supernode != nil {
|
if self.rightButtonNode.supernode != nil {
|
||||||
let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)))
|
let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)))
|
||||||
rightTitleInset += rightButtonSize.width + leftButtonInset + 1.0
|
rightTitleInset += rightButtonSize.width + leftButtonInset + 1.0
|
||||||
self.rightButtonNode.alpha = 1.0
|
self.rightButtonNode.alpha = 1.0
|
||||||
self.rightButtonNode.frame = CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize)
|
transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let transitionState = self.transitionState {
|
if let transitionState = self.transitionState {
|
||||||
@ -1000,18 +1001,18 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
let finalX: CGFloat = floor((size.width - titleSize.width) / 2.0)
|
let finalX: CGFloat = floor((size.width - titleSize.width) / 2.0)
|
||||||
|
|
||||||
self.titleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
|
self.titleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
|
||||||
self.titleNode.alpha = progress * progress
|
self.titleNode.alpha = progress * progress
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.titleNode.alpha = 1.0
|
self.titleNode.alpha = 1.0
|
||||||
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let titleView = self.titleView {
|
if let titleView = self.titleView {
|
||||||
let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)
|
let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
|
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
|
||||||
titleView.frame = titleFrame
|
transition.updateFrame(view: titleView, frame: titleFrame)
|
||||||
|
|
||||||
if let titleView = titleView as? NavigationBarTitleView {
|
if let titleView = titleView as? NavigationBarTitleView {
|
||||||
let titleWidth = size.width - (leftTitleInset > 0.0 ? leftTitleInset : rightTitleInset) - (rightTitleInset > 0.0 ? rightTitleInset : leftTitleInset)
|
let titleWidth = size.width - (leftTitleInset > 0.0 ? leftTitleInset : rightTitleInset) - (rightTitleInset > 0.0 ? rightTitleInset : leftTitleInset)
|
||||||
@ -1048,7 +1049,7 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
titleView.alpha = 1.0
|
titleView.alpha = 1.0
|
||||||
titleView.frame = titleFrame
|
transition.updateFrame(view: titleView, frame: titleFrame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,31 +31,6 @@ open class CallStatusBarNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addInCallAnimation(_ layer: CALayer) {
|
|
||||||
let animation = CAKeyframeAnimation(keyPath: "opacity")
|
|
||||||
animation.keyTimes = [0.0 as NSNumber, 0.1 as NSNumber, 0.5 as NSNumber, 0.9 as NSNumber, 1.0 as NSNumber]
|
|
||||||
animation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.0 as NSNumber, 1.0 as NSNumber, 1.0 as NSNumber]
|
|
||||||
animation.duration = 1.8
|
|
||||||
animation.autoreverses = true
|
|
||||||
animation.repeatCount = Float.infinity
|
|
||||||
animation.beginTime = 1.0
|
|
||||||
layer.add(animation, forKey: "blink")
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class StatusBarLabelNode: ImmediateTextNode {
|
|
||||||
override func willEnterHierarchy() {
|
|
||||||
super.willEnterHierarchy()
|
|
||||||
|
|
||||||
addInCallAnimation(self.layer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func didExitHierarchy() {
|
|
||||||
super.didExitHierarchy()
|
|
||||||
|
|
||||||
self.layer.removeAnimation(forKey: "blink")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class StatusBarView: UITracingLayerView {
|
private final class StatusBarView: UITracingLayerView {
|
||||||
weak var node: StatusBar?
|
weak var node: StatusBar?
|
||||||
|
|
||||||
@ -178,19 +153,9 @@ public final class StatusBar: ASDisplayNode {
|
|||||||
if (resolvedCallStatusBarNode != nil) != (self.callStatusBarNode != nil) {
|
if (resolvedCallStatusBarNode != nil) != (self.callStatusBarNode != nil) {
|
||||||
if let resolvedCallStatusBarNode = resolvedCallStatusBarNode {
|
if let resolvedCallStatusBarNode = resolvedCallStatusBarNode {
|
||||||
self.addSubnode(resolvedCallStatusBarNode)
|
self.addSubnode(resolvedCallStatusBarNode)
|
||||||
if animated {
|
|
||||||
resolvedCallStatusBarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
||||||
}
|
|
||||||
} else if let callStatusBarNode = self.callStatusBarNode {
|
} else if let callStatusBarNode = self.callStatusBarNode {
|
||||||
self.callStatusBarNode = nil
|
self.callStatusBarNode = nil
|
||||||
|
callStatusBarNode.removeFromSupernode()
|
||||||
if animated {
|
|
||||||
callStatusBarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak callStatusBarNode] _ in
|
|
||||||
callStatusBarNode?.removeFromSupernode()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
callStatusBarNode.removeFromSupernode()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -303,6 +303,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||||||
private let emptyQueryDisposable = MetaDisposable()
|
private let emptyQueryDisposable = MetaDisposable()
|
||||||
private let searchDisposable = MetaDisposable()
|
private let searchDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private let forceTheme: PresentationTheme?
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
@ -310,12 +311,16 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||||||
|
|
||||||
private let presentationDataPromise: Promise<PresentationData>
|
private let presentationDataPromise: Promise<PresentationData>
|
||||||
|
|
||||||
public init(context: AccountContext, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) {
|
public init(context: AccountContext, forceTheme: PresentationTheme?, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.openPeer = openPeer
|
self.openPeer = openPeer
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
self.forceTheme = forceTheme
|
||||||
|
if let forceTheme = self.forceTheme {
|
||||||
|
self.presentationData = self.presentationData.withUpdated(theme: forceTheme)
|
||||||
|
}
|
||||||
self.presentationDataPromise = Promise(self.presentationData)
|
self.presentationDataPromise = Promise(self.presentationData)
|
||||||
|
|
||||||
self.emptyQueryListNode = ListView()
|
self.emptyQueryListNode = ListView()
|
||||||
@ -622,9 +627,20 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||||||
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError>
|
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError>
|
||||||
switch mode {
|
switch mode {
|
||||||
case .inviteActions, .banAndPromoteActions:
|
case .inviteActions, .banAndPromoteActions:
|
||||||
foundContacts = context.account.postbox.searchContacts(query: query.lowercased())
|
if filters.contains(where: { filter in
|
||||||
foundRemotePeers = .single(([], [])) |> then(searchPeers(account: context.account, query: query)
|
if case .excludeNonMembers = filter {
|
||||||
|> delay(0.2, queue: Queue.concurrentDefaultQueue()))
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
foundContacts = .single(([], [:]))
|
||||||
|
foundRemotePeers = .single(([], []))
|
||||||
|
} else {
|
||||||
|
foundContacts = context.account.postbox.searchContacts(query: query.lowercased())
|
||||||
|
foundRemotePeers = .single(([], [])) |> then(searchPeers(account: context.account, query: query)
|
||||||
|
|> delay(0.2, queue: Queue.concurrentDefaultQueue()))
|
||||||
|
}
|
||||||
case .searchMembers, .searchBanned, .searchKicked, .searchAdmins:
|
case .searchMembers, .searchBanned, .searchKicked, .searchAdmins:
|
||||||
foundContacts = .single(([], [:]))
|
foundContacts = .single(([], [:]))
|
||||||
foundRemotePeers = .single(([], []))
|
foundRemotePeers = .single(([], []))
|
||||||
@ -639,7 +655,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||||||
switch filter {
|
switch filter {
|
||||||
case let .exclude(ids):
|
case let .exclude(ids):
|
||||||
existingPeerIds = existingPeerIds.union(ids)
|
existingPeerIds = existingPeerIds.union(ids)
|
||||||
case .disable:
|
case .disable, .excludeNonMembers:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -927,7 +943,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||||||
switch filter {
|
switch filter {
|
||||||
case let .exclude(ids):
|
case let .exclude(ids):
|
||||||
existingPeerIds = existingPeerIds.union(ids)
|
existingPeerIds = existingPeerIds.union(ids)
|
||||||
case .disable:
|
case .disable, .excludeNonMembers:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1157,9 +1173,15 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
|||||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
var presentationData = presentationData
|
||||||
|
|
||||||
let previousTheme = strongSelf.presentationData.theme
|
let previousTheme = strongSelf.presentationData.theme
|
||||||
let previousStrings = strongSelf.presentationData.strings
|
let previousStrings = strongSelf.presentationData.strings
|
||||||
|
|
||||||
|
if let forceTheme = strongSelf.forceTheme {
|
||||||
|
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.presentationData = presentationData
|
strongSelf.presentationData = presentationData
|
||||||
|
|
||||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||||
|
|||||||
@ -9,17 +9,19 @@ import TelegramPresentationData
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import SearchUI
|
import SearchUI
|
||||||
|
|
||||||
enum ChannelMembersSearchControllerMode {
|
public enum ChannelMembersSearchControllerMode {
|
||||||
case promote
|
case promote
|
||||||
case ban
|
case ban
|
||||||
|
case inviteToCall
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ChannelMembersSearchFilter {
|
public enum ChannelMembersSearchFilter {
|
||||||
case exclude([PeerId])
|
case exclude([PeerId])
|
||||||
case disable([PeerId])
|
case disable([PeerId])
|
||||||
|
case excludeNonMembers
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChannelMembersSearchController: ViewController {
|
public final class ChannelMembersSearchController: ViewController {
|
||||||
private let queue = Queue()
|
private let queue = Queue()
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@ -28,6 +30,7 @@ final class ChannelMembersSearchController: ViewController {
|
|||||||
private let filters: [ChannelMembersSearchFilter]
|
private let filters: [ChannelMembersSearchFilter]
|
||||||
private let openPeer: (Peer, RenderedChannelParticipant?) -> Void
|
private let openPeer: (Peer, RenderedChannelParticipant?) -> Void
|
||||||
|
|
||||||
|
private let forceTheme: PresentationTheme?
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
private var didPlayPresentationAnimation = false
|
private var didPlayPresentationAnimation = false
|
||||||
@ -38,13 +41,17 @@ final class ChannelMembersSearchController: ViewController {
|
|||||||
|
|
||||||
private var searchContentNode: NavigationBarSearchContentNode?
|
private var searchContentNode: NavigationBarSearchContentNode?
|
||||||
|
|
||||||
init(context: AccountContext, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter] = [], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) {
|
public init(context: AccountContext, peerId: PeerId, forceTheme: PresentationTheme? = nil, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter] = [], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.openPeer = openPeer
|
self.openPeer = openPeer
|
||||||
self.filters = filters
|
self.filters = filters
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
self.forceTheme = forceTheme
|
||||||
|
if let forceTheme = forceTheme {
|
||||||
|
self.presentationData = self.presentationData.withUpdated(theme: forceTheme)
|
||||||
|
}
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||||
|
|
||||||
@ -74,8 +81,8 @@ final class ChannelMembersSearchController: ViewController {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = ChannelMembersSearchControllerNode(context: self.context, presentationData: self.presentationData, peerId: self.peerId, mode: self.mode, filters: self.filters)
|
self.displayNode = ChannelMembersSearchControllerNode(context: self.context, presentationData: self.presentationData, forceTheme: self.forceTheme, peerId: self.peerId, mode: self.mode, filters: self.filters)
|
||||||
self.controllerNode.navigationBar = self.navigationBar
|
self.controllerNode.navigationBar = self.navigationBar
|
||||||
self.controllerNode.requestActivateSearch = { [weak self] in
|
self.controllerNode.requestActivateSearch = { [weak self] in
|
||||||
self?.activateSearch()
|
self?.activateSearch()
|
||||||
@ -105,7 +112,7 @@ final class ChannelMembersSearchController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments, !self.didPlayPresentationAnimation {
|
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments, !self.didPlayPresentationAnimation {
|
||||||
@ -143,7 +150,7 @@ final class ChannelMembersSearchController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func cancelPressed() {
|
@objc private func cancelPressed() {
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -112,18 +112,23 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)?
|
var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)?
|
||||||
var pushController: ((ViewController) -> Void)?
|
var pushController: ((ViewController) -> Void)?
|
||||||
|
|
||||||
|
private let forceTheme: PresentationTheme?
|
||||||
var presentationData: PresentationData
|
var presentationData: PresentationData
|
||||||
|
|
||||||
private var disposable: Disposable?
|
private var disposable: Disposable?
|
||||||
private var listControl: PeerChannelMemberCategoryControl?
|
private var listControl: PeerChannelMemberCategoryControl?
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) {
|
init(context: AccountContext, presentationData: PresentationData, forceTheme: PresentationTheme?, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.listNode = ListView()
|
self.listNode = ListView()
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.filters = filters
|
self.filters = filters
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
self.forceTheme = forceTheme
|
||||||
|
if let forceTheme = forceTheme {
|
||||||
|
self.presentationData = self.presentationData.withUpdated(theme: forceTheme)
|
||||||
|
}
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -190,7 +195,11 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
if ids.contains(peer.id) {
|
if ids.contains(peer.id) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case .disable:
|
case let .disable(ids):
|
||||||
|
if ids.contains(peer.id) {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
case .excludeNonMembers:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,7 +213,11 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
if ids.contains(peer.id) {
|
if ids.contains(peer.id) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case .disable:
|
case let .disable(ids):
|
||||||
|
if ids.contains(peer.id) {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
case .excludeNonMembers:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,6 +225,24 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
|
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
|
case .inviteToCall:
|
||||||
|
if peer.id == context.account.peerId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for filter in filters {
|
||||||
|
switch filter {
|
||||||
|
case let .exclude(ids):
|
||||||
|
if ids.contains(peer.id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case let .disable(ids):
|
||||||
|
if ids.contains(peer.id) {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
case .excludeNonMembers:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let renderedParticipant: RenderedChannelParticipant
|
let renderedParticipant: RenderedChannelParticipant
|
||||||
switch participant {
|
switch participant {
|
||||||
@ -262,7 +293,11 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
if ids.contains(participant.peer.id) {
|
if ids.contains(participant.peer.id) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case .disable:
|
case let .disable(ids):
|
||||||
|
if ids.contains(participant.peer.id) {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
case .excludeNonMembers:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,7 +311,11 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
if ids.contains(participant.peer.id) {
|
if ids.contains(participant.peer.id) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case .disable:
|
case let .disable(ids):
|
||||||
|
if ids.contains(participant.peer.id) {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
case .excludeNonMembers:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,6 +323,24 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
|
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
|
case .inviteToCall:
|
||||||
|
if participant.peer.id == context.account.peerId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for filter in filters {
|
||||||
|
switch filter {
|
||||||
|
case let .exclude(ids):
|
||||||
|
if ids.contains(participant.peer.id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case let .disable(ids):
|
||||||
|
if ids.contains(participant.peer.id) {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
case .excludeNonMembers:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
entries.append(.peer(index, participant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled))
|
entries.append(.peer(index, participant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled))
|
||||||
index += 1
|
index += 1
|
||||||
@ -316,7 +373,10 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
func updatePresentationData(_ presentationData: PresentationData) {
|
func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.searchDisplayController?.updatePresentationData(presentationData)
|
if let forceTheme = forceTheme {
|
||||||
|
self.presentationData = self.presentationData.withUpdated(theme: forceTheme)
|
||||||
|
}
|
||||||
|
self.searchDisplayController?.updatePresentationData(self.presentationData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
@ -350,7 +410,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChannelMembersSearchContainerNode(context: self.context, peerId: self.peerId, mode: .banAndPromoteActions, filters: self.filters, searchContext: nil, openPeer: { [weak self] peer, participant in
|
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChannelMembersSearchContainerNode(context: self.context, forceTheme: self.forceTheme, peerId: self.peerId, mode: .banAndPromoteActions, filters: self.filters, searchContext: nil, openPeer: { [weak self] peer, participant in
|
||||||
self?.requestOpenPeerFromSearch?(peer, participant)
|
self?.requestOpenPeerFromSearch?(peer, participant)
|
||||||
}, updateActivity: { value in
|
}, updateActivity: { value in
|
||||||
|
|
||||||
|
|||||||
@ -87,7 +87,7 @@ private final class ChannelMembersSearchItemNode: ItemListControllerSearchNode {
|
|||||||
private let containerNode: ChannelMembersSearchContainerNode
|
private let containerNode: ChannelMembersSearchContainerNode
|
||||||
|
|
||||||
init(context: AccountContext, peerId: PeerId, searchMode: ChannelMembersSearchMode, searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, dismissInput: @escaping () -> Void) {
|
init(context: AccountContext, peerId: PeerId, searchMode: ChannelMembersSearchMode, searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, dismissInput: @escaping () -> Void) {
|
||||||
self.containerNode = ChannelMembersSearchContainerNode(context: context, peerId: peerId, mode: searchMode, filters: [], searchContext: searchContext, openPeer: { peer, participant in
|
self.containerNode = ChannelMembersSearchContainerNode(context: context, forceTheme: nil, peerId: peerId, mode: searchMode, filters: [], searchContext: searchContext, openPeer: { peer, participant in
|
||||||
openPeer(peer, participant)
|
openPeer(peer, participant)
|
||||||
}, updateActivity: updateActivity, pushController: pushController)
|
}, updateActivity: updateActivity, pushController: pushController)
|
||||||
self.containerNode.cancel = {
|
self.containerNode.cancel = {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ private final class VoiceChatIndicatorNode: ASDisplayNode {
|
|||||||
private let centerLine: ASDisplayNode
|
private let centerLine: ASDisplayNode
|
||||||
private let rightLine: ASDisplayNode
|
private let rightLine: ASDisplayNode
|
||||||
|
|
||||||
private var isCurrentlyInHierarchy = false
|
private var isCurrentlyInHierarchy = true
|
||||||
private var shouldBeAnimating = false
|
private var shouldBeAnimating = false
|
||||||
|
|
||||||
var color: UIColor = UIColor(rgb: 0xffffff) {
|
var color: UIColor = UIColor(rgb: 0xffffff) {
|
||||||
@ -45,6 +45,8 @@ private final class VoiceChatIndicatorNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.leftLine)
|
self.addSubnode(self.leftLine)
|
||||||
self.addSubnode(self.centerLine)
|
self.addSubnode(self.centerLine)
|
||||||
self.addSubnode(self.rightLine)
|
self.addSubnode(self.rightLine)
|
||||||
|
|
||||||
|
self.updateAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didEnterHierarchy() {
|
override func didEnterHierarchy() {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import SearchBarNode
|
|||||||
import SearchUI
|
import SearchUI
|
||||||
import ChatListSearchItemHeader
|
import ChatListSearchItemHeader
|
||||||
|
|
||||||
extension NavigationBarSearchContentNode: ItemListControllerSearchNavigationContentNode {
|
/*extension NavigationBarSearchContentNode: ItemListControllerSearchNavigationContentNode {
|
||||||
public func activate() {
|
public func activate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ extension NavigationBarSearchContentNode: ItemListControllerSearchNavigationCont
|
|||||||
|
|
||||||
public func setQueryUpdated(_ f: @escaping (String) -> Void) {
|
public func setQueryUpdated(_ f: @escaping (String) -> Void) {
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
extension SettingsSearchableItemIcon {
|
extension SettingsSearchableItemIcon {
|
||||||
func image() -> UIImage? {
|
func image() -> UIImage? {
|
||||||
|
|||||||
@ -26,6 +26,155 @@ public enum LocationBroadcastPanelSource {
|
|||||||
case peer(PeerId)
|
case peer(PeerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||||
|
final class Proxy {
|
||||||
|
let context: AccountGroupCallContextImpl
|
||||||
|
let removed: () -> Void
|
||||||
|
|
||||||
|
init(context: AccountGroupCallContextImpl, removed: @escaping () -> Void) {
|
||||||
|
self.context = context
|
||||||
|
self.removed = removed
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.removed()
|
||||||
|
}
|
||||||
|
|
||||||
|
func keep() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var disposable: Disposable?
|
||||||
|
var participantsContext: GroupCallParticipantsContext?
|
||||||
|
|
||||||
|
private let panelDataPromise = Promise<GroupCallPanelData>()
|
||||||
|
var panelData: Signal<GroupCallPanelData, NoError> {
|
||||||
|
return self.panelDataPromise.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(account: Account, peerId: PeerId, call: CachedChannelData.ActiveCall) {
|
||||||
|
self.panelDataPromise.set(.single(GroupCallPanelData(
|
||||||
|
peerId: peerId,
|
||||||
|
info: GroupCallInfo(
|
||||||
|
id: call.id,
|
||||||
|
accessHash: call.accessHash,
|
||||||
|
participantCount: 0,
|
||||||
|
clientParams: nil
|
||||||
|
),
|
||||||
|
topParticipants: [],
|
||||||
|
participantCount: 0,
|
||||||
|
activeSpeakers: Set(),
|
||||||
|
groupCall: nil
|
||||||
|
)))
|
||||||
|
|
||||||
|
self.disposable = (getGroupCallParticipants(account: account, callId: call.id, accessHash: call.accessHash, offset: "", limit: 100)
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||||
|
guard let strongSelf = self, let state = state else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let context = GroupCallParticipantsContext(
|
||||||
|
account: account,
|
||||||
|
peerId: peerId,
|
||||||
|
id: call.id,
|
||||||
|
accessHash: call.accessHash,
|
||||||
|
state: state
|
||||||
|
)
|
||||||
|
strongSelf.participantsContext = context
|
||||||
|
strongSelf.panelDataPromise.set(combineLatest(queue: .mainQueue(),
|
||||||
|
context.state,
|
||||||
|
context.activeSpeakers
|
||||||
|
)
|
||||||
|
|> map { state, activeSpeakers -> GroupCallPanelData in
|
||||||
|
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||||
|
for participant in state.participants {
|
||||||
|
if topParticipants.count >= 3 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
topParticipants.append(participant)
|
||||||
|
}
|
||||||
|
return GroupCallPanelData(
|
||||||
|
peerId: peerId,
|
||||||
|
info: GroupCallInfo(id: call.id, accessHash: call.accessHash, participantCount: state.totalCount, clientParams: nil),
|
||||||
|
topParticipants: topParticipants,
|
||||||
|
participantCount: state.totalCount,
|
||||||
|
activeSpeakers: activeSpeakers,
|
||||||
|
groupCall: nil
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable?.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class AccountGroupCallContextCacheImpl: AccountGroupCallContextCache {
|
||||||
|
class Impl {
|
||||||
|
private class Record {
|
||||||
|
let context: AccountGroupCallContextImpl
|
||||||
|
let subscribers = Bag<Void>()
|
||||||
|
var removeTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
|
init(context: AccountGroupCallContextImpl) {
|
||||||
|
self.context = context
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let queue: Queue
|
||||||
|
private var contexts: [Int64: Record] = [:]
|
||||||
|
|
||||||
|
init(queue: Queue) {
|
||||||
|
self.queue = queue
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(account: Account, peerId: PeerId, call: CachedChannelData.ActiveCall) -> AccountGroupCallContextImpl.Proxy {
|
||||||
|
let result: Record
|
||||||
|
if let current = self.contexts[call.id] {
|
||||||
|
result = current
|
||||||
|
} else {
|
||||||
|
let context = AccountGroupCallContextImpl(account: account, peerId: peerId, call: call)
|
||||||
|
result = Record(context: context)
|
||||||
|
self.contexts[call.id] = result
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = result.subscribers.add(Void())
|
||||||
|
result.removeTimer?.invalidate()
|
||||||
|
result.removeTimer = nil
|
||||||
|
return AccountGroupCallContextImpl.Proxy(context: result.context, removed: { [weak self, weak result] in
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
if let strongResult = result, let strongSelf = self, strongSelf.contexts[call.id] === strongResult {
|
||||||
|
strongResult.subscribers.remove(index)
|
||||||
|
if strongResult.subscribers.isEmpty {
|
||||||
|
let removeTimer = SwiftSignalKit.Timer(timeout: 30, repeat: false, completion: {
|
||||||
|
if let result = result, let strongSelf = self, strongSelf.contexts[call.id] === result, result.subscribers.isEmpty {
|
||||||
|
strongSelf.contexts.removeValue(forKey: call.id)
|
||||||
|
}
|
||||||
|
}, queue: .mainQueue())
|
||||||
|
strongResult.removeTimer = removeTimer
|
||||||
|
removeTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let queue: Queue = .mainQueue()
|
||||||
|
let impl: QueueLocalObject<Impl>
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
let queue = self.queue
|
||||||
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
|
return Impl(queue: queue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) {
|
private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) {
|
||||||
let presentImpl: (Message?) -> Void = { [weak controller] message in
|
let presentImpl: (Message?) -> Void = { [weak controller] message in
|
||||||
if let message = message, let strongController = controller {
|
if let message = message, let strongController = controller {
|
||||||
@ -265,36 +414,15 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
case .none, .all:
|
case .none, .all:
|
||||||
break
|
break
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
let currentGroupCall: Signal<GroupCallPanelData?, NoError> = callManager.currentGroupCallSignal
|
let currentGroupCall: Signal<PresentationGroupCall?, NoError> = callManager.currentGroupCallSignal
|
||||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||||
return lhs?.internalId == rhs?.internalId
|
return lhs?.internalId == rhs?.internalId
|
||||||
})
|
})
|
||||||
|> mapToSignal { call -> Signal<GroupCallPanelData?, NoError> in
|
|> map { call -> PresentationGroupCall? in
|
||||||
guard let call = call, call.peerId == peerId else {
|
guard let call = call, call.peerId == peerId else {
|
||||||
return .single(nil)
|
return nil
|
||||||
}
|
}
|
||||||
return call.summaryState
|
return call
|
||||||
|> filter { $0 != nil }
|
|
||||||
|> map { summary -> GroupCallPanelData? in
|
|
||||||
guard let summary = summary else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return GroupCallPanelData(
|
|
||||||
peerId: call.peerId,
|
|
||||||
info: summary.info,
|
|
||||||
topParticipants: summary.topParticipants,
|
|
||||||
participantCount: summary.participantCount,
|
|
||||||
numberOfActiveSpeakers: summary.numberOfActiveSpeakers,
|
|
||||||
groupCall: call
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|> take(until: { summary in
|
|
||||||
if summary != nil {
|
|
||||||
return SignalTakeAction(passthrough: true, complete: true)
|
|
||||||
} else {
|
|
||||||
return SignalTakeAction(passthrough: true, complete: false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let availableGroupCall: Signal<GroupCallPanelData?, NoError>
|
let availableGroupCall: Signal<GroupCallPanelData?, NoError>
|
||||||
@ -311,7 +439,30 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
guard let activeCall = activeCall else {
|
guard let activeCall = activeCall else {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
return getGroupCallParticipants(account: context.account, callId: activeCall.id, accessHash: activeCall.accessHash, offset: "", limit: 10)
|
|
||||||
|
return Signal { [weak context] subscriber in
|
||||||
|
guard let context = context, let callContextCache = context.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl else {
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
callContextCache.impl.syncWith { impl in
|
||||||
|
let callContext = impl.get(account: context.account, peerId: peerId, call: activeCall)
|
||||||
|
disposable.set((callContext.context.panelData
|
||||||
|
|> deliverOnMainQueue).start(next: { panelData in
|
||||||
|
callContext.keep()
|
||||||
|
subscriber.putNext(panelData)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(.mainQueue())
|
||||||
|
|
||||||
|
/*let updatedData: Signal<GroupCallPanelData?, NoError> = getGroupCallParticipants(account: context.account, callId: activeCall.id, accessHash: activeCall.accessHash, offset: "", limit: 10)
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in
|
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
@ -365,20 +516,34 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
}
|
}
|
||||||
|> runOn(.mainQueue())
|
|> runOn(.mainQueue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let initialData: Signal<GroupCallPanelData?, NoError> = .single(GroupCallPanelData(
|
||||||
|
peerId: peerId,
|
||||||
|
info: GroupCallInfo(
|
||||||
|
id: activeCall.id,
|
||||||
|
accessHash: activeCall.accessHash,
|
||||||
|
participantCount: 0,
|
||||||
|
clientParams: nil
|
||||||
|
),
|
||||||
|
topParticipants: [],
|
||||||
|
participantCount: 0,
|
||||||
|
numberOfActiveSpeakers: 0,
|
||||||
|
groupCall: nil
|
||||||
|
))
|
||||||
|
|
||||||
|
return initialData
|
||||||
|
|> then(updatedData)*/
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
availableGroupCall = .single(nil)
|
availableGroupCall = .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(),
|
self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(), availableGroupCall, currentGroupCall).start(next: { [weak self] availableState, currentGroupCall in
|
||||||
currentGroupCall,
|
|
||||||
availableGroupCall
|
|
||||||
).start(next: { [weak self] currentState, availableState in
|
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let panelData = currentState != nil ? nil : availableState
|
let panelData = currentGroupCall != nil ? nil : availableState
|
||||||
|
|
||||||
let wasEmpty = strongSelf.groupCallPanelData == nil
|
let wasEmpty = strongSelf.groupCallPanelData == nil
|
||||||
strongSelf.groupCallPanelData = panelData
|
strongSelf.groupCallPanelData = panelData
|
||||||
|
|||||||
@ -36,6 +36,8 @@ swift_library(
|
|||||||
"//submodules/AudioBlob:AudioBlob",
|
"//submodules/AudioBlob:AudioBlob",
|
||||||
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
||||||
"//submodules/AlertUI:AlertUI",
|
"//submodules/AlertUI:AlertUI",
|
||||||
|
"//submodules/DirectionalPanGesture:DirectionalPanGesture",
|
||||||
|
"//submodules/PeerInfoUI:PeerInfoUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -219,15 +219,12 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
|||||||
|
|
||||||
if videoState.isAvailable {
|
if videoState.isAvailable {
|
||||||
let isCameraActive: Bool
|
let isCameraActive: Bool
|
||||||
let isCameraEnabled: Bool
|
|
||||||
let isCameraInitializing: Bool
|
let isCameraInitializing: Bool
|
||||||
if videoState.hasVideo {
|
if videoState.hasVideo {
|
||||||
isCameraActive = videoState.isCameraActive
|
isCameraActive = videoState.isCameraActive
|
||||||
isCameraEnabled = videoState.canChangeStatus
|
|
||||||
isCameraInitializing = videoState.isInitializingCamera
|
isCameraInitializing = videoState.isInitializingCamera
|
||||||
} else {
|
} else {
|
||||||
isCameraActive = false
|
isCameraActive = false
|
||||||
isCameraEnabled = videoState.canChangeStatus
|
|
||||||
isCameraInitializing = videoState.isInitializingCamera
|
isCameraInitializing = videoState.isInitializingCamera
|
||||||
}
|
}
|
||||||
topButtons.append(.enableCamera(isCameraActive, false, isCameraInitializing))
|
topButtons.append(.enableCamera(isCameraActive, false, isCameraInitializing))
|
||||||
|
|||||||
@ -42,7 +42,7 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let hierarchyTrackingNode: HierarchyTrackingNode
|
private let hierarchyTrackingNode: HierarchyTrackingNode
|
||||||
private var isCurrentlyInHierarchy = false
|
private var isCurrentlyInHierarchy = true
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.foregroundView = UIView()
|
self.foregroundView = UIView()
|
||||||
@ -89,9 +89,11 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
|
|
||||||
CATransaction.begin()
|
CATransaction.begin()
|
||||||
CATransaction.setDisableActions(true)
|
CATransaction.setDisableActions(true)
|
||||||
self.foregroundView.frame = self.bounds
|
if self.maskCurveView.frame != self.bounds {
|
||||||
self.foregroundGradientLayer.frame = self.bounds
|
self.foregroundView.frame = self.bounds
|
||||||
self.maskCurveView.frame = self.bounds
|
self.foregroundGradientLayer.frame = self.bounds
|
||||||
|
self.maskCurveView.frame = self.bounds
|
||||||
|
}
|
||||||
CATransaction.commit()
|
CATransaction.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +326,7 @@ private final class VoiceCurveView: UIView {
|
|||||||
minRandomness: 1,
|
minRandomness: 1,
|
||||||
maxRandomness: 1.3,
|
maxRandomness: 1.3,
|
||||||
minSpeed: 0.9,
|
minSpeed: 0.9,
|
||||||
maxSpeed: 3.5,
|
maxSpeed: 3.2,
|
||||||
minOffset: smallCurveRange.min,
|
minOffset: smallCurveRange.min,
|
||||||
maxOffset: smallCurveRange.max
|
maxOffset: smallCurveRange.max
|
||||||
)
|
)
|
||||||
@ -333,7 +335,7 @@ private final class VoiceCurveView: UIView {
|
|||||||
minRandomness: 1.2,
|
minRandomness: 1.2,
|
||||||
maxRandomness: 1.5,
|
maxRandomness: 1.5,
|
||||||
minSpeed: 1.0,
|
minSpeed: 1.0,
|
||||||
maxSpeed: 4.5,
|
maxSpeed: 4.4,
|
||||||
minOffset: mediumCurveRange.min,
|
minOffset: mediumCurveRange.min,
|
||||||
maxOffset: mediumCurveRange.max
|
maxOffset: mediumCurveRange.max
|
||||||
)
|
)
|
||||||
@ -342,7 +344,7 @@ private final class VoiceCurveView: UIView {
|
|||||||
minRandomness: 1.2,
|
minRandomness: 1.2,
|
||||||
maxRandomness: 1.7,
|
maxRandomness: 1.7,
|
||||||
minSpeed: 1.0,
|
minSpeed: 1.0,
|
||||||
maxSpeed: 6.0,
|
maxSpeed: 5.8,
|
||||||
minOffset: bigCurveRange.min,
|
minOffset: bigCurveRange.min,
|
||||||
maxOffset: bigCurveRange.max
|
maxOffset: bigCurveRange.max
|
||||||
)
|
)
|
||||||
@ -476,7 +478,7 @@ final class CurveView: UIView {
|
|||||||
|
|
||||||
override var frame: CGRect {
|
override var frame: CGRect {
|
||||||
didSet {
|
didSet {
|
||||||
if self.frame != oldValue {
|
if self.frame.size != oldValue.size {
|
||||||
self.fromPoints = nil
|
self.fromPoints = nil
|
||||||
self.toPoints = nil
|
self.toPoints = nil
|
||||||
self.animateToNewShape()
|
self.animateToNewShape()
|
||||||
@ -534,7 +536,7 @@ final class CurveView: UIView {
|
|||||||
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
|
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
|
||||||
speedLevel = max(speedLevel, newSpeedLevel)
|
speedLevel = max(speedLevel, newSpeedLevel)
|
||||||
|
|
||||||
if abs(lastSpeedLevel - newSpeedLevel) > 0.3 {
|
if abs(lastSpeedLevel - newSpeedLevel) > 0.45 {
|
||||||
animateToNewShape()
|
animateToNewShape()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -642,7 +644,8 @@ final class CurveView: UIView {
|
|||||||
|
|
||||||
CATransaction.begin()
|
CATransaction.begin()
|
||||||
CATransaction.setDisableActions(true)
|
CATransaction.setDisableActions(true)
|
||||||
shapeLayer.frame = self.bounds
|
shapeLayer.position = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
|
||||||
|
shapeLayer.bounds = self.bounds
|
||||||
CATransaction.commit()
|
CATransaction.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ public final class GroupCallPanelData {
|
|||||||
public let info: GroupCallInfo
|
public let info: GroupCallInfo
|
||||||
public let topParticipants: [GroupCallParticipantsContext.Participant]
|
public let topParticipants: [GroupCallParticipantsContext.Participant]
|
||||||
public let participantCount: Int
|
public let participantCount: Int
|
||||||
public let numberOfActiveSpeakers: Int
|
public let activeSpeakers: Set<PeerId>
|
||||||
public let groupCall: PresentationGroupCall?
|
public let groupCall: PresentationGroupCall?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@ -35,18 +35,44 @@ public final class GroupCallPanelData {
|
|||||||
info: GroupCallInfo,
|
info: GroupCallInfo,
|
||||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||||
participantCount: Int,
|
participantCount: Int,
|
||||||
numberOfActiveSpeakers: Int,
|
activeSpeakers: Set<PeerId>,
|
||||||
groupCall: PresentationGroupCall?
|
groupCall: PresentationGroupCall?
|
||||||
) {
|
) {
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.info = info
|
self.info = info
|
||||||
self.topParticipants = topParticipants
|
self.topParticipants = topParticipants
|
||||||
self.participantCount = participantCount
|
self.participantCount = participantCount
|
||||||
self.numberOfActiveSpeakers = numberOfActiveSpeakers
|
self.activeSpeakers = activeSpeakers
|
||||||
self.groupCall = groupCall
|
self.groupCall = groupCall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class FakeAudioLevelGenerator {
|
||||||
|
private var previousTarget: Float = 0.0
|
||||||
|
private var nextTarget: Float = 0.0
|
||||||
|
private var nextTargetProgress: Float = 1.0
|
||||||
|
private var nextTargetProgressNorm: Float = 1.0
|
||||||
|
|
||||||
|
func get() -> Float {
|
||||||
|
self.nextTargetProgress *= 0.82
|
||||||
|
if self.nextTargetProgress <= 0.01 {
|
||||||
|
self.previousTarget = self.nextTarget
|
||||||
|
if Int.random(in: 0 ... 4) <= 1 {
|
||||||
|
self.nextTarget = 0.0
|
||||||
|
self.nextTargetProgressNorm = Float.random(in: 0.1 ..< 0.3)
|
||||||
|
} else {
|
||||||
|
self.nextTarget = Float.random(in: 0.0 ..< 20.0)
|
||||||
|
self.nextTargetProgressNorm = Float.random(in: 0.2 ..< 0.7)
|
||||||
|
}
|
||||||
|
self.nextTargetProgress = self.nextTargetProgressNorm
|
||||||
|
return self.nextTarget
|
||||||
|
} else {
|
||||||
|
let value = self.nextTarget * max(0.0, self.nextTargetProgress / self.nextTargetProgressNorm)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
@ -77,6 +103,8 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
private let avatarsContext: AnimatedAvatarSetContext
|
private let avatarsContext: AnimatedAvatarSetContext
|
||||||
private var avatarsContent: AnimatedAvatarSetContext.Content?
|
private var avatarsContent: AnimatedAvatarSetContext.Content?
|
||||||
private let avatarsNode: AnimatedAvatarSetNode
|
private let avatarsNode: AnimatedAvatarSetNode
|
||||||
|
private var audioLevelGenerators: [PeerId: FakeAudioLevelGenerator] = [:]
|
||||||
|
private var audioLevelGeneratorTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
|
|
||||||
@ -167,6 +195,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
deinit {
|
deinit {
|
||||||
self.membersDisposable.dispose()
|
self.membersDisposable.dispose()
|
||||||
self.isMutedDisposable.dispose()
|
self.isMutedDisposable.dispose()
|
||||||
|
self.audioLevelGeneratorTimer?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func didLoad() {
|
public override func didLoad() {
|
||||||
@ -255,6 +284,8 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
let previousData = self.currentData
|
let previousData = self.currentData
|
||||||
self.currentData = data
|
self.currentData = data
|
||||||
|
|
||||||
|
var updateAudioLevels = false
|
||||||
|
|
||||||
if previousData?.groupCall !== data.groupCall {
|
if previousData?.groupCall !== data.groupCall {
|
||||||
let membersText: String
|
let membersText: String
|
||||||
if data.participantCount == 0 {
|
if data.participantCount == 0 {
|
||||||
@ -263,7 +294,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
|
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
|
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }.filter { $0.id != self.context.account.peerId }, animated: false)
|
||||||
|
|
||||||
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
|
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
|
||||||
|
|
||||||
@ -280,22 +311,15 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let membersText: String
|
let membersText: String
|
||||||
let membersTextIsActive: Bool
|
|
||||||
if summaryState.participantCount == 0 {
|
if summaryState.participantCount == 0 {
|
||||||
membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin
|
membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin
|
||||||
} else {
|
} else {
|
||||||
membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount))
|
membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount))
|
||||||
}
|
}
|
||||||
membersTextIsActive = false
|
|
||||||
|
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)
|
||||||
|
|
||||||
if strongSelf.textIsActive != membersTextIsActive {
|
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }.filter { $0.id != strongSelf.context.account.peerId }, animated: false)
|
||||||
strongSelf.textIsActive = membersTextIsActive
|
|
||||||
strongSelf.animateTextChange()
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: membersTextIsActive ? strongSelf.theme.chat.inputPanel.panelControlAccentColor : strongSelf.theme.chat.inputPanel.secondaryTextColor)
|
|
||||||
|
|
||||||
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }, animated: false)
|
|
||||||
|
|
||||||
if let (size, leftInset, rightInset) = strongSelf.validLayout {
|
if let (size, leftInset, rightInset) = strongSelf.validLayout {
|
||||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||||
@ -366,27 +390,59 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
self.audioLevelDisposable.set(nil)
|
self.audioLevelDisposable.set(nil)
|
||||||
|
|
||||||
let membersText: String
|
let membersText: String
|
||||||
let membersTextIsActive: Bool
|
|
||||||
if data.participantCount == 0 {
|
if data.participantCount == 0 {
|
||||||
membersText = self.strings.VoiceChat_Panel_TapToJoin
|
membersText = self.strings.VoiceChat_Panel_TapToJoin
|
||||||
} else {
|
} else {
|
||||||
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
|
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
|
||||||
}
|
}
|
||||||
membersTextIsActive = false
|
|
||||||
|
|
||||||
if self.textIsActive != membersTextIsActive {
|
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
|
||||||
self.textIsActive = membersTextIsActive
|
|
||||||
self.animateTextChange()
|
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }.filter { $0.id != self.context.account.peerId }, animated: false)
|
||||||
}
|
|
||||||
|
|
||||||
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: membersTextIsActive ? self.theme.chat.inputPanel.panelControlAccentColor : self.theme.chat.inputPanel.secondaryTextColor)
|
updateAudioLevels = true
|
||||||
|
|
||||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (size, leftInset, rightInset) = self.validLayout {
|
if let (size, leftInset, rightInset) = self.validLayout {
|
||||||
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if updateAudioLevels {
|
||||||
|
for peerId in data.activeSpeakers {
|
||||||
|
if self.audioLevelGenerators[peerId] == nil {
|
||||||
|
self.audioLevelGenerators[peerId] = FakeAudioLevelGenerator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var removeGenerators: [PeerId] = []
|
||||||
|
for peerId in self.audioLevelGenerators.keys {
|
||||||
|
if !data.activeSpeakers.contains(peerId) {
|
||||||
|
removeGenerators.append(peerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for peerId in removeGenerators {
|
||||||
|
self.audioLevelGenerators.removeValue(forKey: peerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.audioLevelGenerators.isEmpty {
|
||||||
|
self.audioLevelGeneratorTimer?.invalidate()
|
||||||
|
self.audioLevelGeneratorTimer = nil
|
||||||
|
self.avatarsNode.updateAudioLevels(color: self.theme.chat.inputPanel.actionControlFillColor, levels: [:])
|
||||||
|
} else if self.audioLevelGeneratorTimer == nil {
|
||||||
|
let audioLevelGeneratorTimer = SwiftSignalKit.Timer(timeout: 1.0 / 30.0, repeat: true, completion: { [weak self] in
|
||||||
|
self?.sampleAudioGenerators()
|
||||||
|
}, queue: .mainQueue())
|
||||||
|
self.audioLevelGeneratorTimer = audioLevelGeneratorTimer
|
||||||
|
audioLevelGeneratorTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sampleAudioGenerators() {
|
||||||
|
var levels: [PeerId: Float] = [:]
|
||||||
|
for (peerId, generator) in self.audioLevelGenerators {
|
||||||
|
levels[peerId] = generator.get()
|
||||||
|
}
|
||||||
|
self.avatarsNode.updateAudioLevels(color: self.theme.chat.inputPanel.actionControlFillColor, levels: levels)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
|||||||
@ -58,16 +58,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
private struct SummaryParticipantsState: Equatable {
|
private struct SummaryParticipantsState: Equatable {
|
||||||
public var participantCount: Int
|
public var participantCount: Int
|
||||||
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
||||||
public var numberOfActiveSpeakers: Int
|
public var activeSpeakers: Set<PeerId>
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
participantCount: Int,
|
participantCount: Int,
|
||||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||||
numberOfActiveSpeakers: Int
|
activeSpeakers: Set<PeerId>
|
||||||
) {
|
) {
|
||||||
self.participantCount = participantCount
|
self.participantCount = participantCount
|
||||||
self.topParticipants = topParticipants
|
self.topParticipants = topParticipants
|
||||||
self.numberOfActiveSpeakers = numberOfActiveSpeakers
|
self.activeSpeakers = activeSpeakers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,15 +251,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
return self.membersPromise.get()
|
return self.membersPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var invitedPeersValue: Set<PeerId> = Set() {
|
private var invitedPeersValue: [PeerId] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
if self.invitedPeersValue != oldValue {
|
if self.invitedPeersValue != oldValue {
|
||||||
self.inivitedPeersPromise.set(self.invitedPeersValue)
|
self.inivitedPeersPromise.set(self.invitedPeersValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private let inivitedPeersPromise = ValuePromise<Set<PeerId>>(Set())
|
private let inivitedPeersPromise = ValuePromise<[PeerId]>([])
|
||||||
public var invitedPeers: Signal<Set<PeerId>, NoError> {
|
public var invitedPeers: Signal<[PeerId], NoError> {
|
||||||
return self.inivitedPeersPromise.get()
|
return self.inivitedPeersPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,7 +433,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
participantCount: participantsState.participantCount,
|
participantCount: participantsState.participantCount,
|
||||||
callState: callState,
|
callState: callState,
|
||||||
topParticipants: participantsState.topParticipants,
|
topParticipants: participantsState.topParticipants,
|
||||||
numberOfActiveSpeakers: participantsState.numberOfActiveSpeakers
|
activeSpeakers: participantsState.activeSpeakers
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -520,6 +520,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
strongSelf.accountContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.VoiceChat_AnonymousDisabledAlertText, actions: [
|
strongSelf.accountContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.VoiceChat_AnonymousDisabledAlertText, actions: [
|
||||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})
|
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})
|
||||||
]), on: .root, blockInteraction: false, completion: {})
|
]), on: .root, blockInteraction: false, completion: {})
|
||||||
|
} else if case .tooManyParticipants = error {
|
||||||
|
let presentationData = strongSelf.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
strongSelf.accountContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.VoiceChat_ChatFullAlertText, actions: [
|
||||||
|
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})
|
||||||
|
]), on: .root, blockInteraction: false, completion: {})
|
||||||
}
|
}
|
||||||
strongSelf._canBeRemoved.set(.single(true))
|
strongSelf._canBeRemoved.set(.single(true))
|
||||||
}))
|
}))
|
||||||
@ -615,9 +620,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
self.participantsContext = participantsContext
|
self.participantsContext = participantsContext
|
||||||
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||||
participantsContext.state,
|
participantsContext.state,
|
||||||
participantsContext.numberOfActiveSpeakers |> deliverOnMainQueue,
|
participantsContext.activeSpeakers |> deliverOnMainQueue,
|
||||||
self.speakingParticipantsContext.get() |> deliverOnMainQueue
|
self.speakingParticipantsContext.get() |> deliverOnMainQueue
|
||||||
).start(next: { [weak self] state, numberOfActiveSpeakers, speakingParticipants in
|
).start(next: { [weak self] state, activeSpeakers, speakingParticipants in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -685,7 +690,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||||
participantCount: state.totalCount,
|
participantCount: state.totalCount,
|
||||||
topParticipants: topParticipants,
|
topParticipants: topParticipants,
|
||||||
numberOfActiveSpeakers: numberOfActiveSpeakers
|
activeSpeakers: activeSpeakers
|
||||||
)))
|
)))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -911,7 +916,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var updatedInvitedPeers = self.invitedPeersValue
|
var updatedInvitedPeers = self.invitedPeersValue
|
||||||
updatedInvitedPeers.insert(peerId)
|
updatedInvitedPeers.insert(peerId, at: 0)
|
||||||
self.invitedPeersValue = updatedInvitedPeers
|
self.invitedPeersValue = updatedInvitedPeers
|
||||||
|
|
||||||
let _ = inviteToGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start()
|
let _ = inviteToGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start()
|
||||||
|
|||||||
@ -9,12 +9,13 @@ private let titleFont = Font.regular(17.0)
|
|||||||
private let subtitleFont = Font.regular(13.0)
|
private let subtitleFont = Font.regular(13.0)
|
||||||
|
|
||||||
private let white = UIColor(rgb: 0xffffff)
|
private let white = UIColor(rgb: 0xffffff)
|
||||||
private let greyColor = UIColor(rgb: 0x1c1c1e)
|
private let greyColor = UIColor(rgb: 0x2c2c2e)
|
||||||
|
private let secondaryGreyColor = UIColor(rgb: 0x1c1c1e)
|
||||||
private let blue = UIColor(rgb: 0x0078ff)
|
private let blue = UIColor(rgb: 0x0078ff)
|
||||||
private let lightBlue = UIColor(rgb: 0x59c7f8)
|
private let lightBlue = UIColor(rgb: 0x59c7f8)
|
||||||
private let green = UIColor(rgb: 0x33c659)
|
private let green = UIColor(rgb: 0x33c659)
|
||||||
|
|
||||||
private let areaSize = CGSize(width: 370.0, height: 370.0)
|
private let areaSize = CGSize(width: 440.0, height: 440.0)
|
||||||
private let blobSize = CGSize(width: 244.0, height: 244.0)
|
private let blobSize = CGSize(width: 244.0, height: 244.0)
|
||||||
|
|
||||||
final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||||
@ -35,7 +36,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
private let titleLabel: ImmediateTextNode
|
private let titleLabel: ImmediateTextNode
|
||||||
private let subtitleLabel: ImmediateTextNode
|
private let subtitleLabel: ImmediateTextNode
|
||||||
|
|
||||||
private var currentParams: (size: CGSize, buttonSize: CGSize, state: VoiceChatActionButton.State, small: Bool, title: String, subtitle: String)?
|
private var currentParams: (size: CGSize, buttonSize: CGSize, state: VoiceChatActionButton.State, dark: Bool, small: Bool, title: String, subtitle: String)?
|
||||||
|
|
||||||
private var activePromise = ValuePromise<Bool>(false)
|
private var activePromise = ValuePromise<Bool>(false)
|
||||||
private var outerColorPromise = ValuePromise<UIColor?>(nil)
|
private var outerColorPromise = ValuePromise<UIColor?>(nil)
|
||||||
@ -48,10 +49,10 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
didSet {
|
didSet {
|
||||||
if self.pressing {
|
if self.pressing {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||||
transition.updateTransformScale(node: self.containerNode, scale: 0.9)
|
transition.updateTransformScale(node: self.iconNode, scale: 0.9)
|
||||||
} else {
|
} else {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||||
transition.updateTransformScale(node: self.containerNode, scale: 1.0)
|
transition.updateTransformScale(node: self.iconNode, scale: 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,10 +78,10 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if highlighted {
|
if highlighted {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||||
transition.updateTransformScale(node: strongSelf.containerNode, scale: 0.9)
|
transition.updateTransformScale(node: strongSelf.iconNode, scale: 0.9)
|
||||||
} else if !strongSelf.pressing {
|
} else if !strongSelf.pressing {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||||
transition.updateTransformScale(node: strongSelf.containerNode, scale: 1.0)
|
transition.updateTransformScale(node: strongSelf.iconNode, scale: 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,7 +104,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyParams(animated: Bool) {
|
func applyParams(animated: Bool) {
|
||||||
guard let (size, _, _, small, title, subtitle) = self.currentParams else {
|
guard let (size, _, _, _, small, title, subtitle) = self.currentParams else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +137,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||||
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
||||||
|
|
||||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height - totalHeight / 2.0) - 75.0), size: titleSize)
|
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height - totalHeight / 2.0) - 110.0), size: titleSize)
|
||||||
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
||||||
|
|
||||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
@ -153,9 +154,9 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
|
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, buttonSize: CGSize, state: VoiceChatActionButton.State, title: String, subtitle: String, small: Bool, animated: Bool = false) {
|
func update(size: CGSize, buttonSize: CGSize, state: VoiceChatActionButton.State, title: String, subtitle: String, dark: Bool, small: Bool, animated: Bool = false) {
|
||||||
let previousState = self.currentParams?.state
|
let previousState = self.currentParams?.state
|
||||||
self.currentParams = (size, buttonSize, state, small, title, subtitle)
|
self.currentParams = (size, buttonSize, state, dark, small, title, subtitle)
|
||||||
|
|
||||||
var iconMuted = true
|
var iconMuted = true
|
||||||
var iconColor: UIColor = .white
|
var iconColor: UIColor = .white
|
||||||
@ -175,6 +176,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
case .connecting:
|
case .connecting:
|
||||||
backgroundState = .connecting
|
backgroundState = .connecting
|
||||||
}
|
}
|
||||||
|
self.backgroundNode.updateColor(dark: dark)
|
||||||
self.backgroundNode.update(state: backgroundState, animated: true)
|
self.backgroundNode.update(state: backgroundState, animated: true)
|
||||||
self.iconNode.update(state: VoiceChatMicrophoneNode.State(muted: iconMuted, color: iconColor), animated: true)
|
self.iconNode.update(state: VoiceChatMicrophoneNode.State(muted: iconMuted, color: iconColor), animated: true)
|
||||||
|
|
||||||
@ -193,7 +195,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
var hitRect = self.bounds
|
var hitRect = self.bounds
|
||||||
if let (_, buttonSize, _, _, _, _) = self.currentParams {
|
if let (_, buttonSize, _, _, _, _, _) = self.currentParams {
|
||||||
hitRect = self.bounds.insetBy(dx: (self.bounds.width - buttonSize.width) / 2.0, dy: (self.bounds.height - buttonSize.height) / 2.0)
|
hitRect = self.bounds.insetBy(dx: (self.bounds.width - buttonSize.width) / 2.0, dy: (self.bounds.height - buttonSize.height) / 2.0)
|
||||||
}
|
}
|
||||||
let result = super.hitTest(point, with: event)
|
let result = super.hitTest(point, with: event)
|
||||||
@ -334,7 +336,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
override init() {
|
override init() {
|
||||||
self.state = .connecting
|
self.state = .connecting
|
||||||
|
|
||||||
self.maskBlobView = VoiceBlobView(frame: CGRect(origin: CGPoint(x: (areaSize.width - blobSize.width) / 2.0, y: (areaSize.height - blobSize.height) / 2.0), size: blobSize), maxLevel: 2.5, mediumBlobRange: (0.69, 0.87), bigBlobRange: (0.71, 1.0))
|
self.maskBlobView = VoiceBlobView(frame: CGRect(origin: CGPoint(x: (areaSize.width - blobSize.width) / 2.0, y: (areaSize.height - blobSize.height) / 2.0), size: blobSize), maxLevel: 2.0, mediumBlobRange: (0.69, 0.87), bigBlobRange: (0.71, 1.0))
|
||||||
self.maskBlobView.setColor(white)
|
self.maskBlobView.setColor(white)
|
||||||
|
|
||||||
var updateInHierarchy: ((Bool) -> Void)?
|
var updateInHierarchy: ((Bool) -> Void)?
|
||||||
@ -364,7 +366,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
self.maskView.backgroundColor = .clear
|
self.maskView.backgroundColor = .clear
|
||||||
|
|
||||||
self.maskGradientLayer.type = .radial
|
self.maskGradientLayer.type = .radial
|
||||||
self.maskGradientLayer.colors = [UIColor(rgb: 0xffffff, alpha: 0.7).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor]
|
self.maskGradientLayer.colors = [UIColor(rgb: 0xffffff, alpha: 0.4).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor]
|
||||||
self.maskGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
|
self.maskGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
|
||||||
self.maskGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
|
self.maskGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
|
||||||
self.maskGradientLayer.transform = CATransform3DMakeScale(0.3, 0.3, 1.0)
|
self.maskGradientLayer.transform = CATransform3DMakeScale(0.3, 0.3, 1.0)
|
||||||
@ -446,7 +448,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
|
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||||
animation.duration = 1.5
|
animation.duration = 1.0
|
||||||
animation.fromValue = NSNumber(value: Float(0.0))
|
animation.fromValue = NSNumber(value: Float(0.0))
|
||||||
animation.toValue = NSNumber(value: Float.pi * 2.0)
|
animation.toValue = NSNumber(value: Float.pi * 2.0)
|
||||||
animation.repeatCount = Float.infinity
|
animation.repeatCount = Float.infinity
|
||||||
@ -493,16 +495,16 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
if let active = active {
|
if let active = active {
|
||||||
if active {
|
if active {
|
||||||
targetColors = [blue.cgColor, green.cgColor]
|
targetColors = [blue.cgColor, green.cgColor]
|
||||||
targetScale = 0.95
|
targetScale = 0.89
|
||||||
outerColor = UIColor(rgb: 0x005720)
|
outerColor = UIColor(rgb: 0x005720)
|
||||||
} else {
|
} else {
|
||||||
targetColors = [lightBlue.cgColor, blue.cgColor]
|
targetColors = [lightBlue.cgColor, blue.cgColor]
|
||||||
targetScale = 0.8
|
targetScale = 0.85
|
||||||
outerColor = UIColor(rgb: 0x00274d)
|
outerColor = UIColor(rgb: 0x00274d)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
targetColors = [lightBlue.cgColor, blue.cgColor]
|
targetColors = [lightBlue.cgColor, blue.cgColor]
|
||||||
targetScale = 0.35
|
targetScale = 0.3
|
||||||
outerColor = nil
|
outerColor = nil
|
||||||
}
|
}
|
||||||
self.updatedOuterColor?(outerColor)
|
self.updatedOuterColor?(outerColor)
|
||||||
@ -606,9 +608,10 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
self?.transition = nil
|
self?.transition = nil
|
||||||
}
|
}
|
||||||
} else if transition == .disabled {
|
} else if transition == .disabled {
|
||||||
|
self.transition = nil
|
||||||
} else if case let .blob(previousActive) = transition {
|
} else if case let .blob(previousActive) = transition {
|
||||||
updateGlowAndGradientAnimations(active: newActive, previousActive: previousActive)
|
updateGlowAndGradientAnimations(active: newActive, previousActive: previousActive)
|
||||||
|
self.transition = nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.maskBlobView.startAnimating()
|
self.maskBlobView.startAnimating()
|
||||||
@ -618,6 +621,20 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateColor(dark: Bool) {
|
||||||
|
let previousColor: CGColor = self.backgroundCircleLayer.fillColor ?? greyColor.cgColor
|
||||||
|
let targetColor: CGColor
|
||||||
|
if dark {
|
||||||
|
targetColor = secondaryGreyColor.cgColor
|
||||||
|
} else {
|
||||||
|
targetColor = greyColor.cgColor
|
||||||
|
}
|
||||||
|
self.backgroundCircleLayer.fillColor = targetColor
|
||||||
|
self.foregroundCircleLayer.fillColor = targetColor
|
||||||
|
self.backgroundCircleLayer.animate(from: previousColor, to: targetColor, keyPath: "fillColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
|
||||||
|
self.foregroundCircleLayer.animate(from: previousColor, to: targetColor, keyPath: "fillColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
func update(state: State, animated: Bool) {
|
func update(state: State, animated: Bool) {
|
||||||
var animated = animated
|
var animated = animated
|
||||||
var hadState = true
|
var hadState = true
|
||||||
@ -868,7 +885,7 @@ final class BlobView: UIView {
|
|||||||
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
|
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
|
||||||
speedLevel = max(speedLevel, newSpeedLevel)
|
speedLevel = max(speedLevel, newSpeedLevel)
|
||||||
|
|
||||||
if abs(lastSpeedLevel - newSpeedLevel) > 0.3 {
|
if abs(lastSpeedLevel - newSpeedLevel) > 0.45 {
|
||||||
animateToNewShape()
|
animateToNewShape()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,40 @@ import DeleteChatPeerActionSheetItem
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import AlertUI
|
import AlertUI
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
|
import DirectionalPanGesture
|
||||||
|
import PeerInfoUI
|
||||||
|
|
||||||
|
private let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||||
|
private let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
||||||
|
private let fullscreenBackgroundColor = UIColor(rgb: 0x000000)
|
||||||
|
private let dimColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
|
|
||||||
|
private func cornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
|
||||||
|
if !top && !bottom {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return generateImage(CGSize(width: 50.0, height: 50.0), rotatedContext: { (size, context) in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.setFillColor((dark ? fullscreenBackgroundColor : panelBackgroundColor).cgColor)
|
||||||
|
context.fill(bounds)
|
||||||
|
|
||||||
|
context.setBlendMode(.clear)
|
||||||
|
|
||||||
|
var corners: UIRectCorner = []
|
||||||
|
if top {
|
||||||
|
corners.insert(.topLeft)
|
||||||
|
corners.insert(.topRight)
|
||||||
|
}
|
||||||
|
if bottom {
|
||||||
|
corners.insert(.bottomLeft)
|
||||||
|
corners.insert(.bottomRight)
|
||||||
|
}
|
||||||
|
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: 11.0, height: 11.0))
|
||||||
|
context.addPath(path.cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
})?.stretchableImage(withLeftCapWidth: 25, topCapHeight: 25)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private final class VoiceChatControllerTitleView: UIView {
|
private final class VoiceChatControllerTitleView: UIView {
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
@ -57,12 +91,6 @@ private final class VoiceChatControllerTitleView: UIView {
|
|||||||
self.infoNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: UIColor.white.withAlphaComponent(0.5))
|
self.infoNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: UIColor.white.withAlphaComponent(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateIn(duration: Double) {
|
|
||||||
self.titleNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 49.0), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
|
||||||
self.infoNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 49.0), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
|
||||||
self.titleNode.layer.animateScale(from: 0.882, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
@ -164,6 +192,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
enum State {
|
enum State {
|
||||||
case listening
|
case listening
|
||||||
case speaking
|
case speaking
|
||||||
|
case invited
|
||||||
}
|
}
|
||||||
|
|
||||||
var peer: Peer
|
var peer: Peer
|
||||||
@ -288,7 +317,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .invite(_, _, text):
|
case let .invite(_, _, text):
|
||||||
return VoiceChatActionItem(presentationData: ItemListPresentationData(presentationData), title: text, icon: .generic(UIImage(bundleImageName: "Chat/Context Menu/AddUser")!), action: {
|
return VoiceChatActionItem(presentationData: ItemListPresentationData(presentationData), title: text, icon: .generic(UIImage(bundleImageName: "Chat/Context Menu/AddUser")!), action: {
|
||||||
|
interaction.openInvite()
|
||||||
})
|
})
|
||||||
case let .peer(peerEntry):
|
case let .peer(peerEntry):
|
||||||
let peer = peerEntry.peer
|
let peer = peerEntry.peer
|
||||||
@ -308,6 +337,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
case .speaking:
|
case .speaking:
|
||||||
text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
|
text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
|
||||||
icon = .microphone(false, UIColor(rgb: 0x34c759))
|
icon = .microphone(false, UIColor(rgb: 0x34c759))
|
||||||
|
case .invited:
|
||||||
|
text = .text(presentationData.strings.VoiceChat_StatusInvited, .generic)
|
||||||
|
icon = .invite(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
|
let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
|
||||||
@ -340,21 +372,26 @@ public final class VoiceChatController: ViewController {
|
|||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var darkTheme: PresentationTheme
|
private var darkTheme: PresentationTheme
|
||||||
|
|
||||||
private let optionsButton: VoiceChatOptionsButton
|
|
||||||
private let closeButton: HighlightableButtonNode
|
|
||||||
|
|
||||||
private let dimNode: ASDisplayNode
|
private let dimNode: ASDisplayNode
|
||||||
private let contentContainer: ASDisplayNode
|
private let contentContainer: ASDisplayNode
|
||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
private let listNode: ListView
|
private let listNode: ListView
|
||||||
|
private let topPanelNode: ASDisplayNode
|
||||||
|
private let optionsButton: VoiceChatHeaderButton
|
||||||
|
private let closeButton: VoiceChatHeaderButton
|
||||||
|
private let topCornersNode: ASImageNode
|
||||||
|
private let bottomPanelNode: ASDisplayNode
|
||||||
|
private let bottomCornersNode: ASImageNode
|
||||||
private let audioOutputNode: CallControllerButtonItemNode
|
private let audioOutputNode: CallControllerButtonItemNode
|
||||||
private let leaveNode: CallControllerButtonItemNode
|
private let leaveNode: CallControllerButtonItemNode
|
||||||
private let actionButton: VoiceChatActionButton
|
private let actionButton: VoiceChatActionButton
|
||||||
|
private let leftBorderNode: ASDisplayNode
|
||||||
|
private let rightBorderNode: ASDisplayNode
|
||||||
|
|
||||||
private let titleView: VoiceChatControllerTitleView
|
private let titleView: VoiceChatControllerTitleView
|
||||||
|
|
||||||
private var enqueuedTransitions: [ListTransition] = []
|
private var enqueuedTransitions: [ListTransition] = []
|
||||||
private var maxListHeight: CGFloat?
|
private var floatingHeaderOffset: CGFloat?
|
||||||
|
|
||||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
private var didSetContentsReady: Bool = false
|
private var didSetContentsReady: Bool = false
|
||||||
@ -362,6 +399,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
private var currentGroupMembers: [RenderedChannelParticipant]?
|
private var currentGroupMembers: [RenderedChannelParticipant]?
|
||||||
private var currentCallMembers: [GroupCallParticipantsContext.Participant]?
|
private var currentCallMembers: [GroupCallParticipantsContext.Participant]?
|
||||||
|
private var currentInvitedPeers: [Peer]?
|
||||||
private var currentSpeakingPeers: Set<PeerId>?
|
private var currentSpeakingPeers: Set<PeerId>?
|
||||||
private var accountPeer: Peer?
|
private var accountPeer: Peer?
|
||||||
|
|
||||||
@ -395,6 +433,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
private var itemInteraction: Interaction?
|
private var itemInteraction: Interaction?
|
||||||
|
|
||||||
|
private let inviteDisposable = MetaDisposable()
|
||||||
|
|
||||||
init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) {
|
init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.sharedContext = sharedContext
|
self.sharedContext = sharedContext
|
||||||
@ -403,31 +443,59 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.presentationData = sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||||
self.darkTheme = defaultDarkColorPresentationTheme
|
self.darkTheme = defaultDarkColorPresentationTheme
|
||||||
|
|
||||||
self.optionsButton = VoiceChatOptionsButton()
|
|
||||||
self.closeButton = HighlightableButtonNode()
|
|
||||||
|
|
||||||
self.dimNode = ASDisplayNode()
|
self.dimNode = ASDisplayNode()
|
||||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
self.dimNode.backgroundColor = dimColor
|
||||||
|
|
||||||
self.contentContainer = ASDisplayNode()
|
self.contentContainer = ASDisplayNode()
|
||||||
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.backgroundColor = UIColor(rgb: 0x000000)
|
self.backgroundNode.backgroundColor = secondaryPanelBackgroundColor
|
||||||
self.backgroundNode.cornerRadius = 12.0
|
|
||||||
|
|
||||||
self.listNode = ListView()
|
self.listNode = ListView()
|
||||||
self.listNode.backgroundColor = self.darkTheme.list.itemBlocksBackgroundColor
|
|
||||||
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
|
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
|
||||||
self.listNode.clipsToBounds = true
|
self.listNode.clipsToBounds = true
|
||||||
self.listNode.cornerRadius = 12.0
|
self.listNode.stackFromBottom = true
|
||||||
|
|
||||||
|
self.topPanelNode = ASDisplayNode()
|
||||||
|
self.topPanelNode.backgroundColor = panelBackgroundColor
|
||||||
|
self.topPanelNode.clipsToBounds = false
|
||||||
|
self.topPanelNode.layer.cornerRadius = 12.0
|
||||||
|
|
||||||
|
self.optionsButton = VoiceChatHeaderButton()
|
||||||
|
self.optionsButton.setImage(optionsButtonImage(dark: false))
|
||||||
|
self.closeButton = VoiceChatHeaderButton()
|
||||||
|
self.closeButton.setImage(closeButtonImage(dark: false))
|
||||||
|
|
||||||
|
self.titleView = VoiceChatControllerTitleView(theme: self.presentationData.theme)
|
||||||
|
self.titleView.set(title: self.presentationData.strings.VoiceChat_Title, subtitle: self.presentationData.strings.SocksProxySetup_ProxyStatusConnecting)
|
||||||
|
self.titleView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.topCornersNode = ASImageNode()
|
||||||
|
self.topCornersNode.displaysAsynchronously = false
|
||||||
|
self.topCornersNode.displayWithoutProcessing = true
|
||||||
|
self.topCornersNode.image = cornersImage(top: true, bottom: false, dark: false)
|
||||||
|
|
||||||
|
self.bottomPanelNode = ASDisplayNode()
|
||||||
|
self.bottomPanelNode.backgroundColor = panelBackgroundColor
|
||||||
|
self.bottomPanelNode.clipsToBounds = false
|
||||||
|
|
||||||
|
self.bottomCornersNode = ASImageNode()
|
||||||
|
self.bottomCornersNode.displaysAsynchronously = false
|
||||||
|
self.bottomCornersNode.displayWithoutProcessing = true
|
||||||
|
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: false)
|
||||||
|
|
||||||
self.audioOutputNode = CallControllerButtonItemNode()
|
self.audioOutputNode = CallControllerButtonItemNode()
|
||||||
self.leaveNode = CallControllerButtonItemNode()
|
self.leaveNode = CallControllerButtonItemNode()
|
||||||
self.actionButton = VoiceChatActionButton()
|
self.actionButton = VoiceChatActionButton()
|
||||||
|
|
||||||
self.titleView = VoiceChatControllerTitleView(theme: self.presentationData.theme)
|
self.leftBorderNode = ASDisplayNode()
|
||||||
self.titleView.set(title: self.presentationData.strings.VoiceChat_Title, subtitle: self.presentationData.strings.SocksProxySetup_ProxyStatusConnecting)
|
self.leftBorderNode.backgroundColor = panelBackgroundColor
|
||||||
|
self.leftBorderNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.rightBorderNode = ASDisplayNode()
|
||||||
|
self.rightBorderNode.backgroundColor = panelBackgroundColor
|
||||||
|
self.rightBorderNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -442,11 +510,124 @@ public final class VoiceChatController: ViewController {
|
|||||||
self?.call.updateMuteState(peerId: peerId, isMuted: isMuted)
|
self?.call.updateMuteState(peerId: peerId, isMuted: isMuted)
|
||||||
}, openPeer: { [weak self] peerId in
|
}, openPeer: { [weak self] peerId in
|
||||||
if let strongSelf = self, let navigationController = strongSelf.controller?.parentNavigationController {
|
if let strongSelf = self, let navigationController = strongSelf.controller?.parentNavigationController {
|
||||||
strongSelf.controller?.dismiss()
|
let context = strongSelf.context
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), keepStack: .always, purposefulAction: {}, peekData: nil))
|
strongSelf.controller?.dismiss(completion: {
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), keepStack: .always, purposefulAction: {}, peekData: nil))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, openInvite: { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}, openInvite: {
|
|
||||||
|
|
||||||
|
let groupPeerId = strongSelf.call.peerId
|
||||||
|
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Peer? in
|
||||||
|
return transaction.getPeer(groupPeerId)
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { groupPeer in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let groupPeer = groupPeer as? TelegramChannel else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var filters: [ChannelMembersSearchFilter] = []
|
||||||
|
if let currentCallMembers = strongSelf.currentCallMembers {
|
||||||
|
filters.append(.disable(Array(currentCallMembers.map { $0.peer.id })))
|
||||||
|
}
|
||||||
|
if !groupPeer.hasPermission(.inviteMembers) {
|
||||||
|
filters.append(.excludeNonMembers)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dismissController: (() -> Void)?
|
||||||
|
let controller = ChannelMembersSearchController(context: strongSelf.context, peerId: groupPeer.id, forceTheme: strongSelf.darkTheme, mode: .inviteToCall, filters: filters, openPeer: { peer, participant in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
dismissController?()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
if peer.id == strongSelf.context.account.peerId {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let participant = participant {
|
||||||
|
strongSelf.call.invitePeer(participant.peer.id)
|
||||||
|
dismissController?()
|
||||||
|
} else {
|
||||||
|
let selfController = strongSelf.controller
|
||||||
|
let inviteDisposable = strongSelf.inviteDisposable
|
||||||
|
var inviteSignal = strongSelf.context.peerChannelMemberCategoriesContextsManager.addMembers(account: strongSelf.context.account, peerId: groupPeer.id, memberIds: [peer.id])
|
||||||
|
var cancelImpl: (() -> Void)?
|
||||||
|
let progressSignal = Signal<Never, NoError> { [weak selfController] subscriber in
|
||||||
|
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||||
|
cancelImpl?()
|
||||||
|
}))
|
||||||
|
selfController?.present(controller, in: .window(.root))
|
||||||
|
return ActionDisposable { [weak controller] in
|
||||||
|
Queue.mainQueue().async() {
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(Queue.mainQueue())
|
||||||
|
|> delay(0.15, queue: Queue.mainQueue())
|
||||||
|
let progressDisposable = progressSignal.start()
|
||||||
|
|
||||||
|
inviteSignal = inviteSignal
|
||||||
|
|> afterDisposed {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
progressDisposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancelImpl = {
|
||||||
|
inviteDisposable.set(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(next: { _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
dismissController?()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.call.invitePeer(peer.id)
|
||||||
|
dismissController?()
|
||||||
|
}, error: { error in
|
||||||
|
dismissController?()
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
let text: String
|
||||||
|
switch error {
|
||||||
|
case .limitExceeded:
|
||||||
|
text = presentationData.strings.Channel_ErrorAddTooMuch
|
||||||
|
case .tooMuchJoined:
|
||||||
|
text = presentationData.strings.Invite_ChannelsTooMuch
|
||||||
|
case .generic:
|
||||||
|
text = presentationData.strings.Login_UnknownError
|
||||||
|
case .restricted:
|
||||||
|
text = presentationData.strings.Channel_ErrorAddBlocked
|
||||||
|
case .notMutualContact:
|
||||||
|
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||||
|
case .botDoesntSupportGroups:
|
||||||
|
text = presentationData.strings.Channel_BotDoesntSupportGroups
|
||||||
|
case .tooMuchBots:
|
||||||
|
text = presentationData.strings.Channel_TooMuchBots
|
||||||
|
case .bot:
|
||||||
|
text = presentationData.strings.Login_UnknownError
|
||||||
|
}
|
||||||
|
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dismissController = { [weak controller] in
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
strongSelf.controller?.push(controller)
|
||||||
|
})
|
||||||
}, peerContextAction: { [weak self] entry, sourceNode, gesture in
|
}, peerContextAction: { [weak self] entry, sourceNode, gesture in
|
||||||
guard let strongSelf = self, let controller = strongSelf.controller, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else {
|
guard let strongSelf = self, let controller = strongSelf.controller, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else {
|
||||||
return
|
return
|
||||||
@ -521,7 +702,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(items), reactionItems: [], gesture: gesture)
|
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false, blurBackground: true)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||||
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
||||||
}, setPeerIdWithRevealedOptions: { peerId, _ in
|
}, setPeerIdWithRevealedOptions: { peerId, _ in
|
||||||
updateState { state in
|
updateState { state in
|
||||||
@ -531,41 +712,53 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.backgroundNode.addSubnode(self.listNode)
|
self.topPanelNode.view.addSubview(self.titleView)
|
||||||
self.backgroundNode.addSubnode(self.audioOutputNode)
|
self.topPanelNode.addSubnode(self.optionsButton)
|
||||||
self.backgroundNode.addSubnode(self.leaveNode)
|
self.topPanelNode.addSubnode(self.closeButton)
|
||||||
self.backgroundNode.addSubnode(self.actionButton)
|
self.topPanelNode.addSubnode(self.topCornersNode)
|
||||||
self.backgroundNode.view.addSubview(self.titleView)
|
|
||||||
self.backgroundNode.addSubnode(self.optionsButton)
|
self.bottomPanelNode.addSubnode(self.bottomCornersNode)
|
||||||
self.backgroundNode.addSubnode(self.closeButton)
|
self.bottomPanelNode.addSubnode(self.audioOutputNode)
|
||||||
|
self.bottomPanelNode.addSubnode(self.leaveNode)
|
||||||
|
self.bottomPanelNode.addSubnode(self.actionButton)
|
||||||
|
|
||||||
self.addSubnode(self.dimNode)
|
self.addSubnode(self.dimNode)
|
||||||
self.addSubnode(self.contentContainer)
|
self.addSubnode(self.contentContainer)
|
||||||
self.contentContainer.addSubnode(self.backgroundNode)
|
self.contentContainer.addSubnode(self.backgroundNode)
|
||||||
|
|
||||||
self.memberStatesDisposable = (self.call.members
|
self.contentContainer.addSubnode(self.listNode)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] callMembers in
|
self.contentContainer.addSubnode(self.topPanelNode)
|
||||||
|
self.contentContainer.addSubnode(self.leftBorderNode)
|
||||||
|
self.contentContainer.addSubnode(self.rightBorderNode)
|
||||||
|
self.contentContainer.addSubnode(self.bottomPanelNode)
|
||||||
|
|
||||||
|
|
||||||
|
let context = self.context
|
||||||
|
let invitedPeers: Signal<[Peer], NoError> = self.call.invitedPeers
|
||||||
|
|> mapToSignal { ids -> Signal<[Peer], NoError> in
|
||||||
|
return context.account.postbox.transaction { transaction -> [Peer] in
|
||||||
|
return ids.compactMap(transaction.getPeer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.memberStatesDisposable = combineLatest(queue: .mainQueue(),
|
||||||
|
self.call.members,
|
||||||
|
invitedPeers
|
||||||
|
).start(next: { [weak self] callMembers, invitedPeers in
|
||||||
guard let strongSelf = self, let callMembers = callMembers else {
|
guard let strongSelf = self, let callMembers = callMembers else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let groupMembers = strongSelf.currentGroupMembers {
|
if let groupMembers = strongSelf.currentGroupMembers {
|
||||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: groupMembers, callMembers: callMembers.participants, speakingPeers: callMembers.speakingParticipants)
|
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: groupMembers, callMembers: callMembers.participants, invitedPeers: invitedPeers, speakingPeers: callMembers.speakingParticipants)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.currentCallMembers = callMembers.participants
|
strongSelf.currentCallMembers = callMembers.participants
|
||||||
|
strongSelf.currentInvitedPeers = invitedPeers
|
||||||
}
|
}
|
||||||
|
|
||||||
let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers.totalCount)))
|
let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers.totalCount)))
|
||||||
strongSelf.titleView.set(title: strongSelf.presentationData.strings.VoiceChat_Title, subtitle: subtitle)
|
strongSelf.titleView.set(title: strongSelf.presentationData.strings.VoiceChat_Title, subtitle: subtitle)
|
||||||
})
|
})
|
||||||
|
|
||||||
self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if case let .known(value) = offset, value < 40.0 {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.peerViewDisposable = (combineLatest(self.context.account.viewTracker.peerView(self.call.peerId), self.context.account.postbox.loadedPeerWithId(self.context.account.peerId))
|
self.peerViewDisposable = (combineLatest(self.context.account.viewTracker.peerView(self.call.peerId), self.context.account.postbox.loadedPeerWithId(self.context.account.peerId))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] view, accountPeer in
|
|> deliverOnMainQueue).start(next: { [weak self] view, accountPeer in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -574,7 +767,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
if !strongSelf.didSetDataReady {
|
if !strongSelf.didSetDataReady {
|
||||||
strongSelf.accountPeer = accountPeer
|
strongSelf.accountPeer = accountPeer
|
||||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: [], callMembers: strongSelf.currentCallMembers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
|
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: [], callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
|
||||||
|
|
||||||
if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel {
|
if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel {
|
||||||
let addressName = channel.addressName ?? ""
|
let addressName = channel.addressName ?? ""
|
||||||
@ -612,7 +805,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if wasMuted != (state.muteState != nil), let groupMembers = strongSelf.currentGroupMembers {
|
if wasMuted != (state.muteState != nil), let groupMembers = strongSelf.currentGroupMembers {
|
||||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: groupMembers, callMembers: strongSelf.currentCallMembers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
|
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: groupMembers, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||||
@ -762,14 +955,32 @@ public final class VoiceChatController: ViewController {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: strongOptionsButton.extractedContainerNode, keepInPlace: true)), items: .single(items), reactionItems: [], gesture: gesture)
|
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: strongOptionsButton.extractedContainerNode, keepInPlace: true, blurBackground: false)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||||
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.closeButton.setImage(closeButtonImage(), for: [.normal])
|
self.optionsButton.addTarget(self, action: #selector(self.optionsPressed), forControlEvents: .touchUpInside)
|
||||||
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
|
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
self.optionsButton.addTarget(self, action: #selector(self.optionsPressed), forControlEvents: .touchUpInside)
|
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.listNode.endedInteractiveDragging = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch strongSelf.listNode.visibleContentOffset() {
|
||||||
|
case let .known(value):
|
||||||
|
if value <= -10.0 {
|
||||||
|
// strongSelf.controller?.dismiss()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -781,6 +992,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.memberStatesDisposable?.dispose()
|
self.memberStatesDisposable?.dispose()
|
||||||
self.audioLevelsDisposable?.dispose()
|
self.audioLevelsDisposable?.dispose()
|
||||||
self.myAudioLevelDisposable?.dispose()
|
self.myAudioLevelDisposable?.dispose()
|
||||||
|
self.inviteDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
@ -793,14 +1005,22 @@ public final class VoiceChatController: ViewController {
|
|||||||
longTapRecognizer.delegate = self
|
longTapRecognizer.delegate = self
|
||||||
self.actionButton.view.addGestureRecognizer(longTapRecognizer)
|
self.actionButton.view.addGestureRecognizer(longTapRecognizer)
|
||||||
|
|
||||||
let panRecognizer = CallPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
let panRecognizer = DirectionalPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||||
panRecognizer.shouldBegin = { [weak self] _ in
|
panRecognizer.delegate = self
|
||||||
guard let _ = self else {
|
panRecognizer.delaysTouchesBegan = false
|
||||||
return false
|
panRecognizer.cancelsTouchesInView = true
|
||||||
}
|
// panRecognizer.shouldBegin = { [weak self] point in
|
||||||
return true
|
// guard let strongSelf = self else {
|
||||||
}
|
// return false
|
||||||
self.backgroundNode.view.addGestureRecognizer(panRecognizer)
|
// }
|
||||||
|
// if strongSelf.topPanelNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.topPanelNode.view)) {
|
||||||
|
// if strongSelf.topPanelNode.frame.maxY <= strongSelf.listNode.frame.minY {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
self.view.addGestureRecognizer(panRecognizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func optionsPressed() {
|
@objc private func optionsPressed() {
|
||||||
@ -829,15 +1049,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var actionButtonPressGestureStartTime: Double = 0.0
|
private var actionButtonPressGestureStartTime: Double = 0.0
|
||||||
|
|
||||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
||||||
if let callState = self.callState, case .connected = callState.networkState, let muteState = callState.muteState, !muteState.canUnmute {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
@objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||||
guard let callState = self.callState else {
|
guard let callState = self.callState else {
|
||||||
return
|
return
|
||||||
@ -860,7 +1071,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
if let (layout, navigationHeight) = self.validLayout {
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
}
|
}
|
||||||
self.updateMembers(muteState: self.effectiveMuteState, groupMembers: self.currentGroupMembers ?? [], callMembers: self.currentCallMembers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
self.updateMembers(muteState: self.effectiveMuteState, groupMembers: self.currentGroupMembers ?? [], callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
||||||
case .ended, .cancelled:
|
case .ended, .cancelled:
|
||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
|
|
||||||
@ -875,7 +1086,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
if let (layout, navigationHeight) = self.validLayout {
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
}
|
}
|
||||||
self.updateMembers(muteState: self.effectiveMuteState, groupMembers: self.currentGroupMembers ?? [], callMembers: self.currentCallMembers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
self.updateMembers(muteState: self.effectiveMuteState, groupMembers: self.currentGroupMembers ?? [], callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -955,6 +1166,101 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
guard let (validLayout, _) = self.validLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.floatingHeaderOffset = offset
|
||||||
|
|
||||||
|
let layoutTopInset: CGFloat = max(validLayout.statusBarHeight ?? 0.0, validLayout.safeInsets.top)
|
||||||
|
|
||||||
|
let topPanelHeight: CGFloat = 63.0
|
||||||
|
let listTopInset = layoutTopInset + topPanelHeight
|
||||||
|
|
||||||
|
let rawPanelOffset = offset + listTopInset - topPanelHeight
|
||||||
|
let panelOffset = max(layoutTopInset, rawPanelOffset)
|
||||||
|
let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: validLayout.size.width, height: topPanelHeight))
|
||||||
|
|
||||||
|
let previousFrame = self.topPanelNode.frame
|
||||||
|
if !topPanelFrame.equalTo(previousFrame) {
|
||||||
|
self.topPanelNode.frame = topPanelFrame
|
||||||
|
|
||||||
|
let positionDelta = CGPoint(x: topPanelFrame.minX - previousFrame.minX, y: topPanelFrame.minY - previousFrame.minY)
|
||||||
|
transition.animateOffsetAdditive(node: self.topPanelNode, offset: positionDelta.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: validLayout.size.width, height: validLayout.size.height))
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 16.0
|
||||||
|
let leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: validLayout.size.height))
|
||||||
|
let rightBorderFrame = CGRect(origin: CGPoint(x: validLayout.size.width - sideInset, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: validLayout.size.height))
|
||||||
|
|
||||||
|
let previousBackgroundFrame = self.backgroundNode.frame
|
||||||
|
let previousLeftBorderFrame = self.leftBorderNode.frame
|
||||||
|
let previousRightBorderFrame = self.rightBorderNode.frame
|
||||||
|
|
||||||
|
self.updateColors(fullscreen: panelOffset == layoutTopInset)
|
||||||
|
|
||||||
|
if !backgroundFrame.equalTo(previousBackgroundFrame) {
|
||||||
|
self.backgroundNode.frame = backgroundFrame
|
||||||
|
self.leftBorderNode.frame = leftBorderFrame
|
||||||
|
self.rightBorderNode.frame = rightBorderFrame
|
||||||
|
|
||||||
|
let backgroundPositionDelta = CGPoint(x: backgroundFrame.minX - previousBackgroundFrame.minX, y: backgroundFrame.minY - previousBackgroundFrame.minY)
|
||||||
|
transition.animateOffsetAdditive(node: self.backgroundNode, offset: backgroundPositionDelta.y)
|
||||||
|
|
||||||
|
let leftBorderPositionDelta = CGPoint(x: leftBorderFrame.minX - previousLeftBorderFrame.minX, y: leftBorderFrame.minY - previousLeftBorderFrame.minY)
|
||||||
|
transition.animateOffsetAdditive(node: self.leftBorderNode, offset: leftBorderPositionDelta.y)
|
||||||
|
|
||||||
|
let rightBorderPositionDelta = CGPoint(x: rightBorderFrame.minX - previousRightBorderFrame.minX, y: rightBorderFrame.minY - previousRightBorderFrame.minY)
|
||||||
|
transition.animateOffsetAdditive(node: self.rightBorderNode, offset: rightBorderPositionDelta.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isFullscreen = false
|
||||||
|
func updateColors(fullscreen: Bool) {
|
||||||
|
guard self.isFullscreen != fullscreen else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.isFullscreen = fullscreen
|
||||||
|
|
||||||
|
self.controller?.statusBar.statusBarStyle = fullscreen ? .White : .Ignore
|
||||||
|
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear)
|
||||||
|
|
||||||
|
transition.updateBackgroundColor(node: self.dimNode, color: fullscreen ? fullscreenBackgroundColor : dimColor)
|
||||||
|
transition.updateBackgroundColor(node: self.backgroundNode, color: fullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor)
|
||||||
|
transition.updateBackgroundColor(node: self.topPanelNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
|
||||||
|
transition.updateBackgroundColor(node: self.bottomPanelNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
|
||||||
|
transition.updateBackgroundColor(node: self.leftBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
|
||||||
|
transition.updateBackgroundColor(node: self.rightBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
|
||||||
|
transition.updateBackgroundColor(node: self.rightBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
|
||||||
|
|
||||||
|
if let snapshotView = self.topCornersNode.view.snapshotContentTree() {
|
||||||
|
snapshotView.frame = self.topCornersNode.frame
|
||||||
|
self.topPanelNode.view.addSubview(snapshotView)
|
||||||
|
|
||||||
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
|
snapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
self.topCornersNode.image = cornersImage(top: true, bottom: false, dark: fullscreen)
|
||||||
|
|
||||||
|
if let snapshotView = self.bottomCornersNode.view.snapshotContentTree() {
|
||||||
|
snapshotView.frame = self.bottomCornersNode.frame
|
||||||
|
self.bottomPanelNode.view.addSubview(snapshotView)
|
||||||
|
|
||||||
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
|
snapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: fullscreen)
|
||||||
|
|
||||||
|
self.optionsButton.setImage(optionsButtonImage(dark: fullscreen), animated: transition.isAnimated)
|
||||||
|
self.closeButton.setImage(closeButtonImage(dark: fullscreen), animated: transition.isAnimated)
|
||||||
|
}
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
let isFirstTime = self.validLayout == nil
|
let isFirstTime = self.validLayout == nil
|
||||||
self.validLayout = (layout, navigationHeight)
|
self.validLayout = (layout, navigationHeight)
|
||||||
@ -965,31 +1271,41 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
|
||||||
let contentHeight: CGFloat = layout.size.height - 240.0
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.contentContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: self.contentContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - contentHeight), size: CGSize(width: layout.size.width, height: contentHeight + 1000.0)))
|
|
||||||
|
|
||||||
let bottomAreaHeight: CGFloat = 290.0
|
let bottomAreaHeight: CGFloat = 268.0
|
||||||
let listOrigin = CGPoint(x: 16.0, y: 64.0)
|
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||||
|
|
||||||
var listHeight: CGFloat = 44.0 + 56.0
|
let sideInset: CGFloat = 16.0
|
||||||
if let maxListHeight = self.maxListHeight {
|
var insets = UIEdgeInsets()
|
||||||
listHeight = min(max(1.0, contentHeight - bottomAreaHeight - listOrigin.y - layout.intrinsicInsets.bottom + 25.0), maxListHeight + 44.0)
|
insets.left = layout.safeInsets.left + sideInset
|
||||||
}
|
insets.right = layout.safeInsets.right + sideInset
|
||||||
|
|
||||||
let listFrame = CGRect(origin: listOrigin, size: CGSize(width: layout.size.width - 16.0 * 2.0, height: listHeight))
|
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
||||||
transition.updateFrame(node: self.listNode, frame: listFrame)
|
let listTopInset = layoutTopInset + 63.0
|
||||||
|
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
|
||||||
|
|
||||||
|
insets.top = max(0.0, listSize.height - 44.0 - floor(56.0 * 3.5))
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset), size: listSize))
|
||||||
|
|
||||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listFrame.size, insets: UIEdgeInsets(top: -1.0, left: -6.0, bottom: -1.0, right: -6.0), scrollIndicatorInsets: UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0), duration: duration, curve: curve)
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
|
||||||
|
|
||||||
|
// let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listFrame.size, insets: UIEdgeInsets(top: -1.0, left: -6.0, bottom: -1.0, right: -6.0), scrollIndicatorInsets: UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0), duration: duration, curve: curve)
|
||||||
|
|
||||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.topCornersNode, frame: CGRect(origin: CGPoint(x: sideInset, y: 63.0), size: CGSize(width: layout.size.width - sideInset * 2.0, height: 50.0)))
|
||||||
|
|
||||||
|
let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: layout.size.width, height: bottomPanelHeight))
|
||||||
|
transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame)
|
||||||
|
transition.updateFrame(node: self.bottomCornersNode, frame: CGRect(origin: CGPoint(x: sideInset, y: -50.0), size: CGSize(width: layout.size.width - sideInset * 2.0, height: 50.0)))
|
||||||
|
|
||||||
let sideButtonSize = CGSize(width: 60.0, height: 60.0)
|
let sideButtonSize = CGSize(width: 60.0, height: 60.0)
|
||||||
let centralButtonSize = CGSize(width: 370.0, height: 370.0)
|
let centralButtonSize = CGSize(width: 440.0, height: 440.0)
|
||||||
|
|
||||||
let actionButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - centralButtonSize.width) / 2.0), y: contentHeight - bottomAreaHeight - layout.intrinsicInsets.bottom + floorToScreenPixels((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
|
let actionButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - centralButtonSize.width) / 2.0), y: floorToScreenPixels((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
|
||||||
|
|
||||||
let actionButtonState: VoiceChatActionButton.State
|
let actionButtonState: VoiceChatActionButton.State
|
||||||
let actionButtonTitle: String
|
let actionButtonTitle: String
|
||||||
@ -1036,7 +1352,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.actionButton.isUserInteractionEnabled = actionButtonEnabled
|
self.actionButton.isUserInteractionEnabled = actionButtonEnabled
|
||||||
self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 144.0, height: 144.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, small: layout.size.width < 330.0, animated: true)
|
self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 144.0, height: 144.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, dark: false, small: layout.size.width < 330.0, animated: true)
|
||||||
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
||||||
|
|
||||||
var audioMode: CallControllerButtonsSpeakerMode = .none
|
var audioMode: CallControllerButtonsSpeakerMode = .none
|
||||||
@ -1094,8 +1410,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
let sideButtonOffset = min(36.0, floor((((layout.size.width - 144.0) / 2.0) - sideButtonSize.width) / 2.0))
|
let sideButtonOffset = min(36.0, floor((((layout.size.width - 144.0) / 2.0) - sideButtonSize.width) / 2.0))
|
||||||
let sideButtonOrigin = max(sideButtonMinimalInset, floor((layout.size.width - 144.0) / 2.0) - sideButtonOffset - sideButtonSize.width)
|
let sideButtonOrigin = max(sideButtonMinimalInset, floor((layout.size.width - 144.0) / 2.0) - sideButtonOffset - sideButtonSize.width)
|
||||||
|
|
||||||
transition.updateFrame(node: self.audioOutputNode, frame: CGRect(origin: CGPoint(x: sideButtonOrigin, y: contentHeight - bottomAreaHeight - layout.intrinsicInsets.bottom + floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
|
transition.updateFrame(node: self.audioOutputNode, frame: CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
|
||||||
transition.updateFrame(node: self.leaveNode, frame: CGRect(origin: CGPoint(x: layout.size.width - sideButtonOrigin - sideButtonSize.width, y: contentHeight - bottomAreaHeight - layout.intrinsicInsets.bottom + floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
|
transition.updateFrame(node: self.leaveNode, frame: CGRect(origin: CGPoint(x: layout.size.width - sideButtonOrigin - sideButtonSize.width, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
|
||||||
|
|
||||||
if isFirstTime {
|
if isFirstTime {
|
||||||
while !self.enqueuedTransitions.isEmpty {
|
while !self.enqueuedTransitions.isEmpty {
|
||||||
@ -1105,45 +1421,35 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
guard let (layout, _) = self.validLayout else {
|
self.layer.animateBoundsOriginYAdditive(from: -self.bounds.size.height, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
return
|
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
self.dimNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.bounds.size.height), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true)
|
||||||
|
|
||||||
self.isHidden = false
|
|
||||||
|
|
||||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
|
||||||
|
|
||||||
let offset: CGFloat = layout.size.height - 240.0
|
|
||||||
self.contentContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true, completion: { _ in
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: (() -> Void)?) {
|
func animateOut(completion: (() -> Void)?) {
|
||||||
guard let (layout, _) = self.validLayout else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var dimCompleted = false
|
var dimCompleted = false
|
||||||
var offsetCompleted = false
|
var offsetCompleted = false
|
||||||
|
|
||||||
let internalCompletion: () -> Void = { [weak self] in
|
let internalCompletion: () -> Void = { [weak self] in
|
||||||
if dimCompleted && offsetCompleted {
|
if dimCompleted && offsetCompleted {
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
strongSelf.layer.removeAllAnimations()
|
||||||
strongSelf.dimNode.layer.removeAllAnimations()
|
strongSelf.dimNode.layer.removeAllAnimations()
|
||||||
strongSelf.contentContainer.layer.removeAllAnimations()
|
|
||||||
|
var bounds = strongSelf.bounds
|
||||||
|
bounds.origin.y = 0.0
|
||||||
|
strongSelf.contentContainer.bounds = bounds
|
||||||
}
|
}
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
self.layer.animateBoundsOriginYAdditive(from: self.bounds.origin.y, to: -self.bounds.size.height, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||||
dimCompleted = true
|
offsetCompleted = true
|
||||||
internalCompletion()
|
internalCompletion()
|
||||||
})
|
})
|
||||||
|
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
let offset: CGFloat = layout.size.height - 240.0
|
self.dimNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -self.bounds.size.height), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in
|
||||||
self.contentContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in
|
dimCompleted = true
|
||||||
offsetCompleted = true
|
|
||||||
internalCompletion()
|
internalCompletion()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1180,14 +1486,13 @@ public final class VoiceChatController: ViewController {
|
|||||||
strongSelf.controller?.contentsReady.set(true)
|
strongSelf.controller?.contentsReady.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !transition.deletions.isEmpty || !transition.insertions.isEmpty {
|
if false, !transition.deletions.isEmpty || !transition.insertions.isEmpty {
|
||||||
var itemHeight: CGFloat = 56.0
|
var itemHeight: CGFloat = 56.0
|
||||||
strongSelf.listNode.forEachVisibleItemNode { node in
|
strongSelf.listNode.forEachVisibleItemNode { node in
|
||||||
if node.frame.height > 0 {
|
if node.frame.height > 0 {
|
||||||
itemHeight = node.frame.height
|
itemHeight = node.frame.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strongSelf.maxListHeight = CGFloat(transition.count - 1) * itemHeight
|
|
||||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
}
|
}
|
||||||
@ -1195,7 +1500,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, groupMembers: [RenderedChannelParticipant], callMembers: [GroupCallParticipantsContext.Participant], speakingPeers: Set<PeerId>) {
|
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, groupMembers: [RenderedChannelParticipant], callMembers: [GroupCallParticipantsContext.Participant], invitedPeers: [Peer], speakingPeers: Set<PeerId>) {
|
||||||
var sortedCallMembers = callMembers
|
var sortedCallMembers = callMembers
|
||||||
sortedCallMembers.sort()
|
sortedCallMembers.sort()
|
||||||
|
|
||||||
@ -1213,6 +1518,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.currentGroupMembers = groupMembers
|
self.currentGroupMembers = groupMembers
|
||||||
self.currentCallMembers = callMembers
|
self.currentCallMembers = callMembers
|
||||||
self.currentSpeakingPeers = speakingPeers
|
self.currentSpeakingPeers = speakingPeers
|
||||||
|
self.currentInvitedPeers = invitedPeers
|
||||||
|
|
||||||
let previousEntries = self.currentEntries
|
let previousEntries = self.currentEntries
|
||||||
var entries: [ListEntry] = []
|
var entries: [ListEntry] = []
|
||||||
@ -1221,7 +1527,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
var processedPeerIds = Set<PeerId>()
|
var processedPeerIds = Set<PeerId>()
|
||||||
|
|
||||||
entries.append(.invite(self.presentationData.theme, self.presentationData.strings, "Invite Member"))
|
entries.append(.invite(self.presentationData.theme, self.presentationData.strings, self.presentationData.strings.VoiceChat_InviteMember))
|
||||||
|
|
||||||
for member in callMembers {
|
for member in callMembers {
|
||||||
if processedPeerIds.contains(member.peer.id) {
|
if processedPeerIds.contains(member.peer.id) {
|
||||||
@ -1265,6 +1571,23 @@ public final class VoiceChatController: ViewController {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for peer in invitedPeers {
|
||||||
|
if processedPeerIds.contains(peer.id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
processedPeerIds.insert(peer.id)
|
||||||
|
|
||||||
|
entries.append(.peer(PeerEntry(
|
||||||
|
peer: peer,
|
||||||
|
presence: nil,
|
||||||
|
activityTimestamp: Int32.max - 1 - index,
|
||||||
|
state: .invited,
|
||||||
|
muteState: nil,
|
||||||
|
canManageCall: false
|
||||||
|
)))
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
self.currentEntries = entries
|
self.currentEntries = entries
|
||||||
|
|
||||||
let presentationData = self.presentationData.withUpdated(theme: self.darkTheme)
|
let presentationData = self.presentationData.withUpdated(theme: self.darkTheme)
|
||||||
@ -1272,39 +1595,82 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.enqueueTransition(transition)
|
self.enqueueTransition(transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func panGesture(_ recognizer: CallPanGestureRecognizer) {
|
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
// if let callState = self.callState, case .connected = callState.networkState, let muteState = callState.muteState, !muteState.canUnmute {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
if let recognizer = gestureRecognizer as? UIPanGestureRecognizer {
|
||||||
|
let location = recognizer.location(in: self.view)
|
||||||
|
if let view = super.hitTest(location, with: nil) {
|
||||||
|
if let gestureRecognizers = view.gestureRecognizers, view != self.view {
|
||||||
|
for gestureRecognizer in gestureRecognizers {
|
||||||
|
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer, gestureRecognizer.isEnabled {
|
||||||
|
print(view)
|
||||||
|
if panGestureRecognizer.state != .began {
|
||||||
|
panGestureRecognizer.isEnabled = false
|
||||||
|
panGestureRecognizer.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .began:
|
case .began:
|
||||||
self.contentContainer.clipsToBounds = true
|
break
|
||||||
case .changed:
|
case .changed:
|
||||||
let offset = recognizer.translation(in: self.view).y
|
let translation = recognizer.translation(in: self.contentContainer.view)
|
||||||
var bounds = self.contentContainer.bounds
|
var bounds = self.contentContainer.bounds
|
||||||
bounds.origin.y = -offset
|
bounds.origin.y = -translation.y
|
||||||
|
bounds.origin.y = min(0.0, bounds.origin.y)
|
||||||
self.contentContainer.bounds = bounds
|
self.contentContainer.bounds = bounds
|
||||||
case .cancelled, .ended:
|
case .ended:
|
||||||
let velocity = recognizer.velocity(in: self.view).y
|
let translation = recognizer.translation(in: self.contentContainer.view)
|
||||||
if velocity < 200.0 {
|
var bounds = self.contentContainer.bounds
|
||||||
var bounds = self.contentContainer.bounds
|
bounds.origin.y = -translation.y
|
||||||
let previous = bounds
|
|
||||||
bounds.origin = CGPoint()
|
let velocity = recognizer.velocity(in: self.contentContainer.view)
|
||||||
self.contentContainer.bounds = bounds
|
|
||||||
self.contentContainer.layer.animateBounds(from: previous, to: bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
if (bounds.minY < -60.0 || velocity.y > 300.0) {
|
||||||
|
self.controller?.dismiss()
|
||||||
} else {
|
} else {
|
||||||
var bounds = self.contentContainer.bounds
|
let previousBounds = self.bounds
|
||||||
let previous = bounds
|
var bounds = self.bounds
|
||||||
bounds.origin = CGPoint(x: 0.0, y: velocity > 0.0 ? -bounds.height: bounds.height)
|
bounds.origin.y = 0.0
|
||||||
self.contentContainer.bounds = bounds
|
self.contentContainer.bounds = bounds
|
||||||
self.contentContainer.layer.animateBounds(from: previous, to: bounds, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { [weak self] _ in
|
self.contentContainer.layer.animateBounds(from: previousBounds, to: self.contentContainer.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||||
self?.controller?.dismissInteractively()
|
|
||||||
var initialBounds = bounds
|
|
||||||
initialBounds.origin = CGPoint()
|
|
||||||
self?.contentContainer.bounds = initialBounds
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
case .cancelled:
|
||||||
|
let previousBounds = self.contentContainer.bounds
|
||||||
|
var bounds = self.contentContainer.bounds
|
||||||
|
bounds.origin.y = 0.0
|
||||||
|
self.contentContainer.bounds = bounds
|
||||||
|
self.contentContainer.layer.animateBounds(from: previousBounds, to: self.contentContainer.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
let result = super.hitTest(point, with: event)
|
||||||
|
|
||||||
|
print("actually hitting")
|
||||||
|
if result === self.topPanelNode.view || result === self.bottomPanelNode.view {
|
||||||
|
return self.view
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.bounds.contains(point) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if point.y < self.topPanelNode.frame.minY {
|
||||||
|
return self.dimNode.view
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let sharedContext: SharedAccountContext
|
private let sharedContext: SharedAccountContext
|
||||||
@ -1319,6 +1685,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
return self._ready
|
return self._ready
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var onViewDidAppear: (() -> Void)?
|
||||||
|
public var onViewDidDisappear: (() -> Void)?
|
||||||
|
|
||||||
private var didAppearOnce: Bool = false
|
private var didAppearOnce: Bool = false
|
||||||
private var isDismissed: Bool = false
|
private var isDismissed: Bool = false
|
||||||
|
|
||||||
@ -1367,7 +1736,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func viewDidAppear(_ animated: Bool) {
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
@ -1380,6 +1749,10 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.idleTimerExtensionDisposable.set(self.sharedContext.applicationBindings.pushIdleTimerExtension())
|
self.idleTimerExtensionDisposable.set(self.sharedContext.applicationBindings.pushIdleTimerExtension())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.onViewDidAppear?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func viewDidDisappear(_ animated: Bool) {
|
override public func viewDidDisappear(_ animated: Bool) {
|
||||||
@ -1394,7 +1767,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.didAppearOnce = false
|
self.didAppearOnce = false
|
||||||
|
|
||||||
completion?()
|
completion?()
|
||||||
self.presentingViewController?.dismiss(animated: false)
|
self.dismiss(animated: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1405,8 +1778,12 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.controllerNode.animateOut(completion: { [weak self] in
|
self.controllerNode.animateOut(completion: { [weak self] in
|
||||||
completion?()
|
completion?()
|
||||||
self?.presentingViewController?.dismiss(animated: false)
|
self?.dismiss(animated: false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.onViewDidDisappear?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1420,14 +1797,16 @@ public final class VoiceChatController: ViewController {
|
|||||||
private final class VoiceChatContextExtractedContentSource: ContextExtractedContentSource {
|
private final class VoiceChatContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
var keepInPlace: Bool
|
var keepInPlace: Bool
|
||||||
let ignoreContentTouches: Bool = true
|
let ignoreContentTouches: Bool = true
|
||||||
|
let blurBackground: Bool
|
||||||
|
|
||||||
private let controller: ViewController
|
private let controller: ViewController
|
||||||
private let sourceNode: ContextExtractedContentContainingNode
|
private let sourceNode: ContextExtractedContentContainingNode
|
||||||
|
|
||||||
init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool) {
|
init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool, blurBackground: Bool) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.sourceNode = sourceNode
|
self.sourceNode = sourceNode
|
||||||
self.keepInPlace = keepInPlace
|
self.keepInPlace = keepInPlace
|
||||||
|
self.blurBackground = blurBackground
|
||||||
}
|
}
|
||||||
|
|
||||||
func takeView() -> ContextControllerTakeViewInfo? {
|
func takeView() -> ContextControllerTakeViewInfo? {
|
||||||
|
|||||||
@ -3,10 +3,13 @@ import UIKit
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
|
|
||||||
func optionsButtonImage() -> UIImage? {
|
func optionsButtonImage(dark: Bool) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
context.setFillColor(UIColor(rgb: dark ? 0x1c1c1e : 0x2c2c2e).cgColor)
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setFillColor(UIColor.white.cgColor)
|
context.setFillColor(UIColor.white.cgColor)
|
||||||
context.fillEllipse(in: CGRect(x: 6.0, y: 12.0, width: 4.0, height: 4.0))
|
context.fillEllipse(in: CGRect(x: 6.0, y: 12.0, width: 4.0, height: 4.0))
|
||||||
context.fillEllipse(in: CGRect(x: 12.0, y: 12.0, width: 4.0, height: 4.0))
|
context.fillEllipse(in: CGRect(x: 12.0, y: 12.0, width: 4.0, height: 4.0))
|
||||||
@ -14,11 +17,11 @@ func optionsButtonImage() -> UIImage? {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeButtonImage() -> UIImage? {
|
func closeButtonImage(dark: Bool) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setFillColor(UIColor(rgb: 0x1c1c1e).cgColor)
|
context.setFillColor(UIColor(rgb: dark ? 0x1c1c1e : 0x2c2c2e).cgColor)
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setLineWidth(2.0)
|
context.setLineWidth(2.0)
|
||||||
@ -35,7 +38,7 @@ func closeButtonImage() -> UIImage? {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
final class VoiceChatOptionsButton: HighlightableButtonNode {
|
final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||||
let extractedContainerNode: ContextExtractedContentContainingNode
|
let extractedContainerNode: ContextExtractedContentContainingNode
|
||||||
let containerNode: ContextControllerSourceNode
|
let containerNode: ContextControllerSourceNode
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
@ -57,6 +60,12 @@ final class VoiceChatOptionsButton: HighlightableButtonNode {
|
|||||||
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
|
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
|
||||||
self.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
|
|
||||||
|
self.containerNode.shouldBegin = { [weak self] location in
|
||||||
|
guard let strongSelf = self, let _ = strongSelf.contextAction else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
self.containerNode.activated = { [weak self] gesture, _ in
|
self.containerNode.activated = { [weak self] gesture, _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -64,17 +73,7 @@ final class VoiceChatOptionsButton: HighlightableButtonNode {
|
|||||||
strongSelf.contextAction?(strongSelf.containerNode, gesture)
|
strongSelf.contextAction?(strongSelf.containerNode, gesture)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.iconNode.image = generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
self.iconNode.image = optionsButtonImage(dark: false)
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
context.setFillColor(UIColor(rgb: 0x1c1c1e).cgColor)
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
context.setFillColor(UIColor.white.cgColor)
|
|
||||||
context.fillEllipse(in: CGRect(x: 6.0, y: 12.0, width: 4.0, height: 4.0))
|
|
||||||
context.fillEllipse(in: CGRect(x: 12.0, y: 12.0, width: 4.0, height: 4.0))
|
|
||||||
context.fillEllipse(in: CGRect(x: 18.0, y: 12.0, width: 4.0, height: 4.0))
|
|
||||||
})
|
|
||||||
|
|
||||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
|
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
|
||||||
self.extractedContainerNode.frame = self.containerNode.bounds
|
self.extractedContainerNode.frame = self.containerNode.bounds
|
||||||
@ -82,6 +81,18 @@ final class VoiceChatOptionsButton: HighlightableButtonNode {
|
|||||||
self.iconNode.frame = self.containerNode.bounds
|
self.iconNode.frame = self.containerNode.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setImage(_ image: UIImage?, animated: Bool = false) {
|
||||||
|
if animated, let snapshotView = self.iconNode.view.snapshotContentTree() {
|
||||||
|
snapshotView.frame = self.iconNode.frame
|
||||||
|
self.view.addSubview(snapshotView)
|
||||||
|
|
||||||
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
|
snapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
self.iconNode.image = image
|
||||||
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
self.view.isOpaque = false
|
self.view.isOpaque = false
|
||||||
|
|||||||
@ -139,7 +139,6 @@ public final class VoiceChatParticipantItem: ListViewItem {
|
|||||||
private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0))
|
private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0))
|
||||||
|
|
||||||
public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||||
private let backgroundNode: ASDisplayNode
|
|
||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
@ -172,9 +171,6 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
|
||||||
self.backgroundNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.topStripeNode = ASDisplayNode()
|
self.topStripeNode = ASDisplayNode()
|
||||||
self.topStripeNode.isLayerBacked = true
|
self.topStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
@ -470,7 +466,6 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
if let _ = updatedTheme {
|
if let _ = updatedTheme {
|
||||||
strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||||
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||||
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
|
||||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,20 +512,16 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
let _ = statusApply()
|
let _ = statusApply()
|
||||||
|
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
|
||||||
}
|
|
||||||
if strongSelf.topStripeNode.supernode == nil {
|
if strongSelf.topStripeNode.supernode == nil {
|
||||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 0)
|
||||||
}
|
}
|
||||||
if strongSelf.bottomStripeNode.supernode == nil {
|
if strongSelf.bottomStripeNode.supernode == nil {
|
||||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.topStripeNode.isHidden = first
|
strongSelf.topStripeNode.isHidden = first
|
||||||
strongSelf.bottomStripeNode.isHidden = last
|
strongSelf.bottomStripeNode.isHidden = last
|
||||||
|
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
|
||||||
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
|
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
|
||||||
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height + -separatorHeight), size: CGSize(width: layoutSize.width - leftInset, height: separatorHeight)))
|
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height + -separatorHeight), size: CGSize(width: layoutSize.width - leftInset, height: separatorHeight)))
|
||||||
|
|
||||||
@ -665,7 +656,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.iconNode?.frame = CGRect(origin: CGPoint(), size: animationSize)
|
strongSelf.iconNode?.frame = CGRect(origin: CGPoint(), size: animationSize)
|
||||||
strongSelf.animationNode?.frame = CGRect(origin: CGPoint(), size: animationSize)
|
strongSelf.animationNode?.frame = CGRect(origin: CGPoint(), size: animationSize)
|
||||||
|
|
||||||
strongSelf.actionButtonNode.frame = CGRect(x: params.width - animationSize.width - 6.0, y: floor((layout.contentSize.height - animationSize.height) / 2.0) + 1.0, width: animationSize.width, height: animationSize.height)
|
strongSelf.actionButtonNode.frame = CGRect(x: params.width - animationSize.width - 6.0 - params.rightInset, y: floor((layout.contentSize.height - animationSize.height) / 2.0) + 1.0, width: animationSize.width, height: animationSize.height)
|
||||||
|
|
||||||
if let presence = item.presence as? TelegramUserPresence {
|
if let presence = item.presence as? TelegramUserPresence {
|
||||||
strongSelf.peerPresenceManager?.reset(presence: presence)
|
strongSelf.peerPresenceManager?.reset(presence: presence)
|
||||||
@ -681,14 +672,8 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isHighlighted = false
|
var isHighlighted = false
|
||||||
|
|
||||||
var reallyHighlighted: Bool {
|
|
||||||
var reallyHighlighted = self.isHighlighted
|
|
||||||
return reallyHighlighted
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateIsHighlighted(transition: ContainedViewLayoutTransition) {
|
func updateIsHighlighted(transition: ContainedViewLayoutTransition) {
|
||||||
if self.reallyHighlighted {
|
if self.isHighlighted {
|
||||||
self.highlightedBackgroundNode.alpha = 1.0
|
self.highlightedBackgroundNode.alpha = 1.0
|
||||||
if self.highlightedBackgroundNode.supernode == nil {
|
if self.highlightedBackgroundNode.supernode == nil {
|
||||||
var anchorNode: ASDisplayNode?
|
var anchorNode: ASDisplayNode?
|
||||||
@ -696,8 +681,6 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
anchorNode = self.bottomStripeNode
|
anchorNode = self.bottomStripeNode
|
||||||
} else if self.topStripeNode.supernode != nil {
|
} else if self.topStripeNode.supernode != nil {
|
||||||
anchorNode = self.topStripeNode
|
anchorNode = self.topStripeNode
|
||||||
} else if self.backgroundNode.supernode != nil {
|
|
||||||
anchorNode = self.backgroundNode
|
|
||||||
}
|
}
|
||||||
if let anchorNode = anchorNode {
|
if let anchorNode = anchorNode {
|
||||||
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
|
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
|
||||||
@ -760,7 +743,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
super.updateRevealOffset(offset: offset, transition: transition)
|
super.updateRevealOffset(offset: offset, transition: transition)
|
||||||
|
|
||||||
if let item = self.layoutParams?.0, let params = self.layoutParams?.1 {
|
if let item = self.layoutParams?.0, let params = self.layoutParams?.1 {
|
||||||
var leftInset: CGFloat = 65.0 + params.leftInset
|
let leftInset: CGFloat = 65.0 + params.leftInset
|
||||||
|
|
||||||
var avatarFrame = self.avatarNode.frame
|
var avatarFrame = self.avatarNode.frame
|
||||||
avatarFrame.origin.x = offset + leftInset - 50.0
|
avatarFrame.origin.x = offset + leftInset - 50.0
|
||||||
|
|||||||
@ -256,6 +256,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
|
|||||||
public enum JoinGroupCallError {
|
public enum JoinGroupCallError {
|
||||||
case generic
|
case generic
|
||||||
case anonymousNotAllowed
|
case anonymousNotAllowed
|
||||||
|
case tooManyParticipants
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct JoinGroupCallResult {
|
public struct JoinGroupCallResult {
|
||||||
@ -272,6 +273,8 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
|
|||||||
|> mapError { error -> JoinGroupCallError in
|
|> mapError { error -> JoinGroupCallError in
|
||||||
if error.errorDescription == "GROUP_CALL_ANONYMOUS_FORBIDDEN" {
|
if error.errorDescription == "GROUP_CALL_ANONYMOUS_FORBIDDEN" {
|
||||||
return .anonymousNotAllowed
|
return .anonymousNotAllowed
|
||||||
|
} else if error.errorDescription == "GROUPCALL_PARTICIPANTS_TOO_MUCH" {
|
||||||
|
return .tooManyParticipants
|
||||||
}
|
}
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
@ -633,16 +636,16 @@ public final class GroupCallParticipantsContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var numberOfActiveSpeakersValue: Int = 0 {
|
private var activeSpeakersValue: Set<PeerId> = Set() {
|
||||||
didSet {
|
didSet {
|
||||||
if self.numberOfActiveSpeakersValue != oldValue {
|
if self.activeSpeakersValue != oldValue {
|
||||||
self.numberOfActiveSpeakersPromise.set(self.numberOfActiveSpeakersValue)
|
self.activeSpeakersPromise.set(self.activeSpeakersValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private let numberOfActiveSpeakersPromise = ValuePromise<Int>(0)
|
private let activeSpeakersPromise = ValuePromise<Set<PeerId>>(Set())
|
||||||
public var numberOfActiveSpeakers: Signal<Int, NoError> {
|
public var activeSpeakers: Signal<Set<PeerId>, NoError> {
|
||||||
return self.numberOfActiveSpeakersPromise.get()
|
return self.activeSpeakersPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var updateQueue: [Update.StateUpdate] = []
|
private var updateQueue: [Update.StateUpdate] = []
|
||||||
@ -684,7 +687,9 @@ public final class GroupCallParticipantsContext {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.numberOfActiveSpeakersValue = activities.count
|
strongSelf.activeSpeakersValue = Set(activities.map { item -> PeerId in
|
||||||
|
item.0
|
||||||
|
})
|
||||||
|
|
||||||
if !strongSelf.hasReceivedSpeackingParticipantsReport {
|
if !strongSelf.hasReceivedSpeackingParticipantsReport {
|
||||||
var updatedParticipants = strongSelf.stateValue.state.participants
|
var updatedParticipants = strongSelf.stateValue.state.participants
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -10,14 +10,11 @@ import TelegramPresentationData
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import LiveLocationManager
|
import LiveLocationManager
|
||||||
import TemporaryCachedPeerDataManager
|
import TemporaryCachedPeerDataManager
|
||||||
#if ENABLE_WALLET
|
|
||||||
import WalletCore
|
|
||||||
import WalletUI
|
|
||||||
#endif
|
|
||||||
import PhoneNumberFormat
|
import PhoneNumberFormat
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import TelegramVoip
|
import TelegramVoip
|
||||||
import TelegramCallsUI
|
import TelegramCallsUI
|
||||||
|
import TelegramBaseController
|
||||||
|
|
||||||
private final class DeviceSpecificContactImportContext {
|
private final class DeviceSpecificContactImportContext {
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
@ -111,10 +108,6 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
}
|
}
|
||||||
public let account: Account
|
public let account: Account
|
||||||
|
|
||||||
#if ENABLE_WALLET
|
|
||||||
public let tonContext: StoredTonContext?
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public let fetchManager: FetchManager
|
public let fetchManager: FetchManager
|
||||||
private let prefetchManager: PrefetchManager?
|
private let prefetchManager: PrefetchManager?
|
||||||
|
|
||||||
@ -159,38 +152,12 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
|
|
||||||
private var experimentalUISettingsDisposable: Disposable?
|
private var experimentalUISettingsDisposable: Disposable?
|
||||||
|
|
||||||
#if ENABLE_WALLET
|
public let cachedGroupCallContexts: AccountGroupCallContextCache
|
||||||
public var hasWallets: Signal<Bool, NoError> {
|
|
||||||
return WalletStorageInterfaceImpl(postbox: self.account.postbox).getWalletRecords()
|
|
||||||
|> map { records in
|
|
||||||
return !records.isEmpty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var hasWalletAccess: Signal<Bool, NoError> {
|
|
||||||
return self.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
|
||||||
|> map { view -> Bool in
|
|
||||||
guard let appConfiguration = view.values[PreferencesKeys.appConfiguration] as? AppConfiguration else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
let walletConfiguration = WalletConfiguration.with(appConfiguration: appConfiguration)
|
|
||||||
if walletConfiguration.config != nil && walletConfiguration.blockchainName != nil {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|> distinctUntilChanged
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public init(sharedContext: SharedAccountContextImpl, account: Account, /*tonContext: StoredTonContext?, */limitsConfiguration: LimitsConfiguration, contentSettings: ContentSettings, appConfiguration: AppConfiguration, temp: Bool = false)
|
public init(sharedContext: SharedAccountContextImpl, account: Account, /*tonContext: StoredTonContext?, */limitsConfiguration: LimitsConfiguration, contentSettings: ContentSettings, appConfiguration: AppConfiguration, temp: Bool = false)
|
||||||
{
|
{
|
||||||
self.sharedContextImpl = sharedContext
|
self.sharedContextImpl = sharedContext
|
||||||
self.account = account
|
self.account = account
|
||||||
#if ENABLE_WALLET
|
|
||||||
self.tonContext = tonContext
|
|
||||||
#endif
|
|
||||||
|
|
||||||
self.downloadedMediaStoreManager = DownloadedMediaStoreManagerImpl(postbox: account.postbox, accountManager: sharedContext.accountManager)
|
self.downloadedMediaStoreManager = DownloadedMediaStoreManagerImpl(postbox: account.postbox, accountManager: sharedContext.accountManager)
|
||||||
|
|
||||||
@ -216,6 +183,8 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
self.peersNearbyManager = nil
|
self.peersNearbyManager = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.cachedGroupCallContexts = AccountGroupCallContextCacheImpl()
|
||||||
|
|
||||||
let updatedLimitsConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration])
|
let updatedLimitsConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration])
|
||||||
|> map { preferences -> LimitsConfiguration in
|
|> map { preferences -> LimitsConfiguration in
|
||||||
return preferences.values[PreferencesKeys.limitsConfiguration] as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
|
return preferences.values[PreferencesKeys.limitsConfiguration] as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
|
||||||
|
|||||||
@ -402,11 +402,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
switch chatLocation {
|
switch chatLocation {
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
locationBroadcastPanelSource = .peer(peerId)
|
locationBroadcastPanelSource = .peer(peerId)
|
||||||
groupCallPanelSource = .peer(peerId)
|
switch subject {
|
||||||
|
case .message, .none:
|
||||||
|
groupCallPanelSource = .peer(peerId)
|
||||||
|
default:
|
||||||
|
groupCallPanelSource = .none
|
||||||
|
}
|
||||||
self.chatLocationInfoData = .peer(Promise())
|
self.chatLocationInfoData = .peer(Promise())
|
||||||
case let .replyThread(replyThreadMessage):
|
case let .replyThread(replyThreadMessage):
|
||||||
locationBroadcastPanelSource = .none
|
locationBroadcastPanelSource = .none
|
||||||
groupCallPanelSource = .all
|
groupCallPanelSource = .none
|
||||||
let promise = Promise<Message?>()
|
let promise = Promise<Message?>()
|
||||||
let key = PostboxViewKey.messages([replyThreadMessage.messageId])
|
let key = PostboxViewKey.messages([replyThreadMessage.messageId])
|
||||||
promise.set(context.account.postbox.combinedView(keys: [key])
|
promise.set(context.account.postbox.combinedView(keys: [key])
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import Postbox
|
|||||||
final class ChatMessageContextExtractedContentSource: ContextExtractedContentSource {
|
final class ChatMessageContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
let keepInPlace: Bool = false
|
let keepInPlace: Bool = false
|
||||||
let ignoreContentTouches: Bool = false
|
let ignoreContentTouches: Bool = false
|
||||||
|
let blurBackground: Bool = true
|
||||||
|
|
||||||
private weak var chatNode: ChatControllerNode?
|
private weak var chatNode: ChatControllerNode?
|
||||||
private let message: Message
|
private let message: Message
|
||||||
|
|||||||
@ -5011,7 +5011,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .members = currentPaneKey {
|
} else if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .members = currentPaneKey {
|
||||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, contentNode: ChannelMembersSearchContainerNode(context: self.context, peerId: self.peerId, mode: .searchMembers, filters: [], searchContext: self.groupMembersSearchContext, openPeer: { [weak self] peer, participant in
|
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, contentNode: ChannelMembersSearchContainerNode(context: self.context, forceTheme: nil, peerId: self.peerId, mode: .searchMembers, filters: [], searchContext: self.groupMembersSearchContext, openPeer: { [weak self] peer, participant in
|
||||||
self?.openPeer(peerId: peer.id, navigation: .info)
|
self?.openPeer(peerId: peer.id, navigation: .info)
|
||||||
}, updateActivity: { _ in
|
}, updateActivity: { _ in
|
||||||
}, pushController: { [weak self] c in
|
}, pushController: { [weak self] c in
|
||||||
@ -6017,6 +6017,7 @@ public final class PeerInfoScreen: ViewController {
|
|||||||
private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource {
|
private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
let keepInPlace: Bool = true
|
let keepInPlace: Bool = true
|
||||||
let ignoreContentTouches: Bool = true
|
let ignoreContentTouches: Bool = true
|
||||||
|
let blurBackground: Bool = true
|
||||||
|
|
||||||
private let controller: ViewController
|
private let controller: ViewController
|
||||||
private let sourceNode: ContextExtractedContentContainingNode
|
private let sourceNode: ContextExtractedContentContainingNode
|
||||||
@ -6261,6 +6262,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
|
|||||||
private final class MessageContextExtractedContentSource: ContextExtractedContentSource {
|
private final class MessageContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
let keepInPlace: Bool = false
|
let keepInPlace: Bool = false
|
||||||
let ignoreContentTouches: Bool = true
|
let ignoreContentTouches: Bool = true
|
||||||
|
let blurBackground: Bool = true
|
||||||
|
|
||||||
private let sourceNode: ContextExtractedContentContainingNode
|
private let sourceNode: ContextExtractedContentContainingNode
|
||||||
|
|
||||||
|
|||||||
@ -103,6 +103,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
private let callState = Promise<PresentationCallState?>(nil)
|
private let callState = Promise<PresentationCallState?>(nil)
|
||||||
|
|
||||||
private var groupCallController: VoiceChatController?
|
private var groupCallController: VoiceChatController?
|
||||||
|
private let hasGroupCallOnScreen = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
|
|
||||||
private var immediateHasOngoingCallValue = Atomic<Bool>(value: false)
|
private var immediateHasOngoingCallValue = Atomic<Bool>(value: false)
|
||||||
public var immediateHasOngoingCall: Bool {
|
public var immediateHasOngoingCall: Bool {
|
||||||
@ -637,12 +638,24 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
strongSelf.groupCallController = nil
|
strongSelf.groupCallController = nil
|
||||||
strongSelf.hasOngoingCall.set(false)
|
strongSelf.hasOngoingCall.set(false)
|
||||||
|
|
||||||
if let call = call {
|
if let call = call, let navigationController = mainWindow.viewController as? NavigationController {
|
||||||
mainWindow.hostView.containerView.endEditing(true)
|
mainWindow.hostView.containerView.endEditing(true)
|
||||||
|
strongSelf.hasGroupCallOnScreen.set(true)
|
||||||
let groupCallController = VoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call)
|
let groupCallController = VoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call)
|
||||||
groupCallController.parentNavigationController = mainWindow.viewController as? NavigationController
|
groupCallController.onViewDidAppear = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.hasGroupCallOnScreen.set(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupCallController.onViewDidDisappear = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.hasGroupCallOnScreen.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupCallController.navigationPresentation = .flatModal
|
||||||
|
groupCallController.parentNavigationController = navigationController
|
||||||
strongSelf.groupCallController = groupCallController
|
strongSelf.groupCallController = groupCallController
|
||||||
strongSelf.mainWindow?.present(groupCallController, on: .calls)
|
navigationController.pushViewController(groupCallController)
|
||||||
strongSelf.hasOngoingCall.set(true)
|
strongSelf.hasOngoingCall.set(true)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.hasOngoingCall.set(false)
|
strongSelf.hasOngoingCall.set(false)
|
||||||
@ -662,21 +675,20 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
|
|
||||||
self.callStateDisposable = combineLatest(queue: .mainQueue(),
|
self.callStateDisposable = combineLatest(queue: .mainQueue(),
|
||||||
callSignal,
|
callSignal,
|
||||||
groupCallSignal
|
groupCallSignal,
|
||||||
).start(next: { [weak self] call, groupCall in
|
self.hasGroupCallOnScreen.get()
|
||||||
|
).start(next: { [weak self] call, groupCall, hasGroupCallOnScreen in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let statusBarContent: CallStatusBarNodeImpl.Content?
|
let statusBarContent: CallStatusBarNodeImpl.Content?
|
||||||
|
|
||||||
if let call = call {
|
if let call = call {
|
||||||
statusBarContent = .call(strongSelf, call.account, call)
|
statusBarContent = .call(strongSelf, call.account, call)
|
||||||
} else if let groupCall = groupCall {
|
} else if let groupCall = groupCall, !hasGroupCallOnScreen {
|
||||||
statusBarContent = .groupCall(strongSelf, groupCall.account, groupCall)
|
statusBarContent = .groupCall(strongSelf, groupCall.account, groupCall)
|
||||||
} else {
|
} else {
|
||||||
statusBarContent = nil
|
statusBarContent = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolvedCallStatusBarNode: CallStatusBarNodeImpl?
|
var resolvedCallStatusBarNode: CallStatusBarNodeImpl?
|
||||||
|
|
||||||
if let statusBarContent = statusBarContent {
|
if let statusBarContent = statusBarContent {
|
||||||
if let current = strongSelf.currentCallStatusBarNode {
|
if let current = strongSelf.currentCallStatusBarNode {
|
||||||
resolvedCallStatusBarNode = current
|
resolvedCallStatusBarNode = current
|
||||||
@ -712,7 +724,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
if groupCallController.isNodeLoaded {
|
if groupCallController.isNodeLoaded {
|
||||||
mainWindow.hostView.containerView.endEditing(true)
|
mainWindow.hostView.containerView.endEditing(true)
|
||||||
if groupCallController.view.superview == nil {
|
if groupCallController.view.superview == nil {
|
||||||
mainWindow.present(groupCallController, on: .calls)
|
(mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -987,7 +999,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
} else if let groupCallController = self.groupCallController {
|
} else if let groupCallController = self.groupCallController {
|
||||||
if groupCallController.isNodeLoaded && groupCallController.view.superview == nil {
|
if groupCallController.isNodeLoaded && groupCallController.view.superview == nil {
|
||||||
mainWindow.hostView.containerView.endEditing(true)
|
mainWindow.hostView.containerView.endEditing(true)
|
||||||
mainWindow.present(groupCallController, on: .calls)
|
(mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user