mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
Various fixes
This commit is contained in:
parent
fdbd3ceb7a
commit
ad2678645d
15
.gitattributes
vendored
Normal file
15
.gitattributes
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Linguist overrides for vendored code
|
||||||
|
submodules/AsyncDisplayKit/* linguist-vendored
|
||||||
|
submodules/ffmpeg/* linguist-vendored
|
||||||
|
submodules/HockeySDK-iOS/* linguist-vendored
|
||||||
|
submodules/libphonenumber/* linguist-vendored
|
||||||
|
submodules/libtgvoip/* linguist-vendored
|
||||||
|
submodules/lottie-ios/* linguist-vendored
|
||||||
|
submodules/Opus/* linguist-vendored
|
||||||
|
submodules/OpusBinding/* linguist-vendored
|
||||||
|
submodules/rlottie/rlottie/* linguist-vendored
|
||||||
|
submodules/sqlcipher/* linguist-vendored
|
||||||
|
submodules/Stripe/* linguist-vendored
|
||||||
|
submodules/ton/tonlib-src/* linguist-vendored
|
||||||
|
submodules/webp/include/* linguist-vendored
|
||||||
|
third-party/* linguist-vendored
|
Binary file not shown.
Binary file not shown.
@ -5838,6 +5838,7 @@ Sorry for the inconvenience.";
|
|||||||
"InviteLink.PeopleJoined_many" = "%@ people joined";
|
"InviteLink.PeopleJoined_many" = "%@ people joined";
|
||||||
"InviteLink.PeopleJoined_any" = "%@ people joined";
|
"InviteLink.PeopleJoined_any" = "%@ people joined";
|
||||||
"InviteLink.CreatePrivateLinkHelp" = "Anyone who has Telegram installed will be able to join your group by following this link.";
|
"InviteLink.CreatePrivateLinkHelp" = "Anyone who has Telegram installed will be able to join your group by following this link.";
|
||||||
|
"InviteLink.CreatePrivateLinkHelpChannel" = "Anyone who has Telegram installed will be able to join your channel by following this link.";
|
||||||
"InviteLink.Manage" = "Manage Invite Links";
|
"InviteLink.Manage" = "Manage Invite Links";
|
||||||
|
|
||||||
"InviteLink.PeopleJoinedShortNoneExpired" = "no one joined";
|
"InviteLink.PeopleJoinedShortNoneExpired" = "no one joined";
|
||||||
@ -5899,6 +5900,7 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"InviteLink.QRCode.Title" = "Invite by QR Code";
|
"InviteLink.QRCode.Title" = "Invite by QR Code";
|
||||||
"InviteLink.QRCode.Info" = "Everyone on Telegram can scan this code to join your group.";
|
"InviteLink.QRCode.Info" = "Everyone on Telegram can scan this code to join your group.";
|
||||||
|
"InviteLink.QRCode.InfoChannel" = "Everyone on Telegram can scan this code to join your channel.";
|
||||||
"InviteLink.QRCode.Share" = "Share QR Code";
|
"InviteLink.QRCode.Share" = "Share QR Code";
|
||||||
|
|
||||||
"InviteLink.InviteLink" = "Invite Link";
|
"InviteLink.InviteLink" = "Invite Link";
|
||||||
@ -6021,6 +6023,9 @@ Sorry for the inconvenience.";
|
|||||||
"Channel.AdminLog.DeletedInviteLink" = "%1$@ deleted invite link %2$@";
|
"Channel.AdminLog.DeletedInviteLink" = "%1$@ deleted invite link %2$@";
|
||||||
"Channel.AdminLog.RevokedInviteLink" = "%1$@ revoked invite link %2$@";
|
"Channel.AdminLog.RevokedInviteLink" = "%1$@ revoked invite link %2$@";
|
||||||
"Channel.AdminLog.EditedInviteLink" = "%1$@ edited invite link %2$@";
|
"Channel.AdminLog.EditedInviteLink" = "%1$@ edited invite link %2$@";
|
||||||
|
"Channel.AdminLog.CreatedInviteLink" = "%1$@ created invite link %2$@";
|
||||||
|
|
||||||
|
"Channel.AdminLog.JoinedViaInviteLink" = "%1$@ joined via invite link %2$@";
|
||||||
|
|
||||||
"GroupInfo.Permissions.BroadcastTitle" = "Broadcast Channel";
|
"GroupInfo.Permissions.BroadcastTitle" = "Broadcast Channel";
|
||||||
"GroupInfo.Permissions.BroadcastConvert" = "Convert Group to Channel";
|
"GroupInfo.Permissions.BroadcastConvert" = "Convert Group to Channel";
|
||||||
@ -6056,7 +6061,6 @@ Sorry for the inconvenience.";
|
|||||||
"Report.Report" = "Report";
|
"Report.Report" = "Report";
|
||||||
"Report.Succeed" = "Telegram moderators will study your report. Thank you!";
|
"Report.Succeed" = "Telegram moderators will study your report. Thank you!";
|
||||||
|
|
||||||
|
|
||||||
"Conversation.AutoremoveRemainingTime" = "auto-delete in %@";
|
"Conversation.AutoremoveRemainingTime" = "auto-delete in %@";
|
||||||
"Conversation.AutoremoveRemainingDays_1" = "auto-delete in %@ day";
|
"Conversation.AutoremoveRemainingDays_1" = "auto-delete in %@ day";
|
||||||
"Conversation.AutoremoveRemainingDays_any" = "auto-delete in %@ days";
|
"Conversation.AutoremoveRemainingDays_any" = "auto-delete in %@ days";
|
||||||
@ -6066,3 +6070,7 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"Conversation.AutoremoveChanged" = "Auto-Delete timer set to %@";
|
"Conversation.AutoremoveChanged" = "Auto-Delete timer set to %@";
|
||||||
|
|
||||||
|
"PeerInfo.ReportProfilePhoto" = "Report Profile Photo";
|
||||||
|
"PeerInfo.ReportProfileVideo" = "Report Profile Video";
|
||||||
|
|
||||||
|
"Channel.AdminLog.CanInviteUsersViaLink" = "Invite Users via Link";
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="17156" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="umr-Wa-jBL">
|
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="17701" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="umr-Wa-jBL">
|
||||||
<device id="watch38"/>
|
<device id="watch38"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="17034"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="17500"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--TGNeoConversationController-->
|
<!--TGNeoConversationController-->
|
||||||
@ -1569,11 +1569,445 @@ contacts found.</string>
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="729" y="757.25"/>
|
<point key="canvasLocation" x="729" y="757.25"/>
|
||||||
</scene>
|
</scene>
|
||||||
|
<!--Static M-->
|
||||||
|
<scene sceneID="uW0-5W-J6c">
|
||||||
|
<objects>
|
||||||
|
<notificationController backgroundImage="BubbleNotification" spacing="0.0" id="JcB-1W-jcv" userLabel="Static M">
|
||||||
|
<items>
|
||||||
|
<group width="1" alignment="left" radius="0.0" id="11B-ry-7nl">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" text="Text" numberOfLines="0" id="RMV-rW-0qs">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" style="UICTFontTextStyleBody"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="margins" left="8" right="8" top="10" bottom="11"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<notificationCategory key="notificationCategory" identifier="m" id="MXx-dC-nsP">
|
||||||
|
<color key="titleColor" red="0.10051588710000001" green="0.10051287709999999" blue="0.1005146056" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<color key="sashColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</notificationCategory>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="4"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="notificationAlertLabel" destination="RMV-rW-0qs" id="buR-qh-N93"/>
|
||||||
|
<segue destination="RQh-4n-Jyy" kind="relationship" relationship="dynamicNotificationInterface" id="wDy-DD-bRl"/>
|
||||||
|
<segue destination="RQh-4n-Jyy" kind="relationship" relationship="dynamicInteractiveNotificationInterface" id="MKi-5u-rhv"/>
|
||||||
|
</connections>
|
||||||
|
</notificationController>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="263" y="875"/>
|
||||||
|
</scene>
|
||||||
|
<!--Dynamic M-->
|
||||||
|
<scene sceneID="aFy-up-fB6">
|
||||||
|
<objects>
|
||||||
|
<controller backgroundImage="BubbleNotification" id="RQh-4n-Jyy" userLabel="Dynamic M" customClass="TGNotificationController">
|
||||||
|
<items>
|
||||||
|
<group width="1" alignment="left" layout="vertical" radius="0.0" spacing="0.0" id="Dd4-YD-hsp">
|
||||||
|
<items>
|
||||||
|
<group width="1" alignment="left" layout="vertical" spacing="0.0" id="ZlG-DJ-Ctv">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" hidden="YES" text="Chat Title" id="frP-KX-c0b">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
<label alignment="left" hidden="YES" text="Name" id="JUM-Bm-hxM">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" weight="medium" pointSize="16"/>
|
||||||
|
</label>
|
||||||
|
<group width="1" height="29" alignment="left" hidden="YES" layout="vertical" spacing="0.0" id="r84-Ll-prj">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" verticalAlignment="center" text="Forwarded from" id="Br6-65-UTH" userLabel="ForwardTitle">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
<label alignment="left" verticalAlignment="center" text="Name" id="DI0-a4-1u0" userLabel="ForwardFrom">
|
||||||
|
<color key="textColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" height="29" alignment="left" hidden="YES" spacing="4" id="Th4-sR-kDF" userLabel="ReplyHeader">
|
||||||
|
<items>
|
||||||
|
<group width="2" height="26" alignment="left" verticalAlignment="center" radius="0.0" spacing="0.0" id="n46-ZY-9jJ" userLabel="ReplyLine">
|
||||||
|
<color key="backgroundColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<group width="26" height="26" alignment="left" verticalAlignment="center" radius="2" id="FmK-ex-jTs" userLabel="ReplyImage">
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.089999999999999997" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" layout="vertical" spacing="0.0" id="tQQ-et-qfm" userLabel="ReplyMessage">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" text="Name" id="ifF-tf-ens" userLabel="ReplyAuthor">
|
||||||
|
<color key="textColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
<label alignment="left" text="Text" id="tWb-zc-3NN" userLabel="ReplyText">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<label alignment="left" text="Text" numberOfLines="0" id="t9S-qg-lnm">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" style="UICTFontTextStyleBody"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="8" right="8" top="0.0" bottom="5"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" hidden="YES" layout="vertical" id="CMM-a2-p1K" userLabel="WrapperGroup">
|
||||||
|
<items>
|
||||||
|
<group width="1" alignment="left" layout="vertical" radius="10" spacing="0.0" id="r4K-n7-uKy" userLabel="LocationGroup">
|
||||||
|
<items>
|
||||||
|
<map height="92" alignment="left" id="PY6-r9-1nn"/>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" spacing="5" id="XJr-R0-HOA" userLabel="FileGroup">
|
||||||
|
<items>
|
||||||
|
<imageView width="26" height="26" alignment="left" verticalAlignment="center" hidden="YES" image="Location" contentMode="center" id="vZ3-mt-ICu" userLabel="VenueIcon">
|
||||||
|
<color key="tintColor" red="0.35566622019999999" green="0.68838506939999999" blue="0.91561108830000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</imageView>
|
||||||
|
<group width="26" height="26" alignment="left" verticalAlignment="center" hidden="YES" radius="13" spacing="0.0" id="IJt-Cp-BKm" userLabel="AudioGroup">
|
||||||
|
<items>
|
||||||
|
<imageView width="26" height="26" alignment="left" image="MediaAudioPlay" contentMode="center" id="PDu-Bz-gy5"/>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.35566622019999999" green="0.68838506939999999" blue="0.91561108830000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<group width="26" height="26" alignment="left" verticalAlignment="center" radius="0.0" id="Gd3-ap-jO4" userLabel="FileIconGroup">
|
||||||
|
<items>
|
||||||
|
<imageView alignment="center" verticalAlignment="center" image="File.png" contentMode="center" id="dZu-99-pMR">
|
||||||
|
<color key="tintColor" red="0.14697439970000001" green="0.5607914329" blue="0.88162887099999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</imageView>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</group>
|
||||||
|
<group alignment="left" verticalAlignment="center" layout="vertical" spacing="0.0" id="DHa-mY-ceB" userLabel="FileMetaGroup">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" text="File Name" id="tul-U8-7fj">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
<label alignment="left" text="Size" id="m2I-fn-zCe">
|
||||||
|
<color key="textColor" red="0.41865724329999998" green="0.41825520989999998" blue="0.4306421876" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="6.5" right="6.5" top="2" bottom="1"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" spacing="0.0" id="DLM-Wi-QFQ" userLabel="StickerWrapper">
|
||||||
|
<items>
|
||||||
|
<group width="0.5" height="64" alignment="left" contentMode="scaleAspectFit" id="7TZ-8f-EgD" userLabel="StickerGroup">
|
||||||
|
<variation key="device=watch42mm" height="72"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="6.5" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" radius="12" id="sxE-kX-fH5" userLabel="MediaGroup">
|
||||||
|
<items>
|
||||||
|
<group alignment="right" verticalAlignment="bottom" radius="10" id="GEf-6R-cw1" userLabel="DurationGroup">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" text="2:34" id="wRk-Fm-UIl" userLabel="Duration">
|
||||||
|
<color key="textColor" red="0.2461894453" green="0.24618205430000001" blue="0.2461862564" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.89292949440000002" green="0.91148859260000004" blue="0.93112039570000005" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="6" right="6" top="2" bottom="2"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.089999999999999997" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="4" right="4" top="4" bottom="4"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" id="W49-eu-bIX" userLabel="CaptionGroup">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" text="Caption" numberOfLines="0" id="3dY-xL-bio">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" pointSize="16"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="8" right="8" top="4" bottom="4"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="1.5" right="1.5" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="10" bottom="5"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="4"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="audioGroup" destination="IJt-Cp-BKm" id="UDG-gj-AJm"/>
|
||||||
|
<outlet property="captionGroup" destination="W49-eu-bIX" id="fLo-8u-qJ7"/>
|
||||||
|
<outlet property="captionLabel" destination="3dY-xL-bio" id="HPd-qv-6SR"/>
|
||||||
|
<outlet property="chatTitleLabel" destination="frP-KX-c0b" id="gQB-RH-eAC"/>
|
||||||
|
<outlet property="durationGroup" destination="GEf-6R-cw1" id="b6I-rw-1ma"/>
|
||||||
|
<outlet property="durationLabel" destination="wRk-Fm-UIl" id="OY4-mh-oGN"/>
|
||||||
|
<outlet property="fileGroup" destination="XJr-R0-HOA" id="jAA-6n-GcD"/>
|
||||||
|
<outlet property="fileIconGroup" destination="Gd3-ap-jO4" id="Gk0-pX-7xT"/>
|
||||||
|
<outlet property="forwardFromLabel" destination="DI0-a4-1u0" id="97d-FM-hvd"/>
|
||||||
|
<outlet property="forwardHeaderGroup" destination="r84-Ll-prj" id="eOS-0I-fH2"/>
|
||||||
|
<outlet property="forwardTitleLabel" destination="Br6-65-UTH" id="ZXg-hL-4Nt"/>
|
||||||
|
<outlet property="map" destination="PY6-r9-1nn" id="QtL-Ma-9i3"/>
|
||||||
|
<outlet property="mapGroup" destination="r4K-n7-uKy" id="QaM-0N-n60"/>
|
||||||
|
<outlet property="mediaGroup" destination="sxE-kX-fH5" id="oMl-K9-upS"/>
|
||||||
|
<outlet property="messageTextLabel" destination="t9S-qg-lnm" id="NTR-1N-c27"/>
|
||||||
|
<outlet property="nameLabel" destination="JUM-Bm-hxM" id="Eiz-Pp-a1C"/>
|
||||||
|
<outlet property="replyAuthorNameLabel" destination="ifF-tf-ens" id="NSF-eV-jEP"/>
|
||||||
|
<outlet property="replyHeaderGroup" destination="Th4-sR-kDF" id="SjQ-KI-BYD"/>
|
||||||
|
<outlet property="replyHeaderImageGroup" destination="FmK-ex-jTs" id="j7i-Sc-BUV"/>
|
||||||
|
<outlet property="replyMessageTextLabel" destination="tWb-zc-3NN" id="aSf-v0-kQf"/>
|
||||||
|
<outlet property="stickerGroup" destination="7TZ-8f-EgD" id="f6z-Rx-GYV"/>
|
||||||
|
<outlet property="stickerWrapperGroup" destination="DLM-Wi-QFQ" id="wXu-ff-inw"/>
|
||||||
|
<outlet property="subtitleLabel" destination="m2I-fn-zCe" id="WMT-u4-Tgp"/>
|
||||||
|
<outlet property="titleLabel" destination="tul-U8-7fj" id="VxJ-io-0DL"/>
|
||||||
|
<outlet property="venueIcon" destination="vZ3-mt-ICu" id="WvO-Db-hRw"/>
|
||||||
|
<outlet property="wrapperGroup" destination="CMM-a2-p1K" id="SSt-aA-79a"/>
|
||||||
|
</connections>
|
||||||
|
</controller>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="503" y="874.75"/>
|
||||||
|
</scene>
|
||||||
|
<!--Static R-->
|
||||||
|
<scene sceneID="Aix-4r-fFK">
|
||||||
|
<objects>
|
||||||
|
<notificationController backgroundImage="BubbleNotification" spacing="0.0" id="crw-Qo-dti" userLabel="Static R">
|
||||||
|
<items>
|
||||||
|
<group width="1" alignment="left" layout="vertical" radius="0.0" id="RTo-Ed-NsD">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" text="Text" numberOfLines="0" id="6f0-WK-Add">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" style="UICTFontTextStyleBody"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="margins" left="8" right="8" top="10" bottom="11"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<notificationCategory key="notificationCategory" id="pgn-NP-bt0">
|
||||||
|
<color key="titleColor" red="0.10051588710000001" green="0.10051287709999999" blue="0.1005146056" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<color key="sashColor" red="1" green="0.99997437" blue="0.99999129769999995" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</notificationCategory>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="4"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="notificationAlertLabel" destination="6f0-WK-Add" id="ioT-Xv-KJp"/>
|
||||||
|
<segue destination="79a-X2-tmF" kind="relationship" relationship="dynamicNotificationInterface" id="dmG-n7-HwD"/>
|
||||||
|
<segue destination="79a-X2-tmF" kind="relationship" relationship="dynamicInteractiveNotificationInterface" id="UyA-Ra-UVy"/>
|
||||||
|
</connections>
|
||||||
|
</notificationController>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="263" y="1281"/>
|
||||||
|
</scene>
|
||||||
|
<!--Dynamic R-->
|
||||||
|
<scene sceneID="K4P-nV-Yqh">
|
||||||
|
<objects>
|
||||||
|
<controller backgroundImage="BubbleNotification" spacing="0.0" id="79a-X2-tmF" userLabel="Dynamic R" customClass="TGNotificationController">
|
||||||
|
<items>
|
||||||
|
<group width="1" alignment="left" layout="vertical" radius="0.0" spacing="0.0" id="Go3-q1-pnJ">
|
||||||
|
<items>
|
||||||
|
<group width="1" alignment="left" layout="vertical" spacing="0.0" id="JAA-Ky-cyx">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" hidden="YES" text="Chat Title" id="HtY-WB-aFI">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
<label alignment="left" hidden="YES" text="Name" id="Hqd-Pr-2zp">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" weight="medium" pointSize="16"/>
|
||||||
|
</label>
|
||||||
|
<group width="1" height="29" alignment="left" hidden="YES" layout="vertical" spacing="0.0" id="Kzq-HH-8Ev">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" verticalAlignment="center" text="Forwarded from" id="NTL-lz-9dd" userLabel="ForwardTitle">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
<label alignment="left" verticalAlignment="center" text="Name" id="B2R-Qa-MeP" userLabel="ForwardFrom">
|
||||||
|
<color key="textColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" height="29" alignment="left" hidden="YES" spacing="4" id="KRV-O6-45y" userLabel="ReplyHeader">
|
||||||
|
<items>
|
||||||
|
<group width="2" height="26" alignment="left" verticalAlignment="center" radius="0.0" spacing="0.0" id="eW6-JQ-FAY" userLabel="ReplyLine">
|
||||||
|
<color key="backgroundColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<group width="26" height="26" alignment="left" verticalAlignment="center" radius="2" id="swd-Nx-MDo" userLabel="ReplyImage">
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.089999999999999997" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" layout="vertical" spacing="0.0" id="h1Y-se-IEW" userLabel="ReplyMessage">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" text="Name" id="g6r-Nm-SOQ" userLabel="ReplyAuthor">
|
||||||
|
<color key="textColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
<label alignment="left" text="Text" id="dAz-x8-jbh" userLabel="ReplyText">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<label alignment="left" text="Text" numberOfLines="0" id="wxg-r7-aSG">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" style="UICTFontTextStyleBody"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="8" right="8" top="0.0" bottom="5"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" hidden="YES" layout="vertical" id="0ch-zY-dJX" userLabel="WrapperGroup">
|
||||||
|
<items>
|
||||||
|
<group width="1" alignment="left" layout="vertical" radius="10" spacing="0.0" id="vel-8f-OFm" userLabel="LocationGroup">
|
||||||
|
<items>
|
||||||
|
<map height="92" alignment="left" id="k4c-6T-xVa"/>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" spacing="5" id="pRF-iD-Gt4" userLabel="FileGroup">
|
||||||
|
<items>
|
||||||
|
<imageView width="26" height="26" alignment="left" verticalAlignment="center" hidden="YES" image="Location" contentMode="center" id="uxs-Nx-we9" userLabel="VenueIcon">
|
||||||
|
<color key="tintColor" red="0.35566622019999999" green="0.68838506939999999" blue="0.91561108830000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</imageView>
|
||||||
|
<group width="26" height="26" alignment="left" verticalAlignment="center" hidden="YES" radius="13" spacing="0.0" id="Y04-zP-Wh2" userLabel="AudioGroup">
|
||||||
|
<items>
|
||||||
|
<imageView width="26" height="26" alignment="left" image="MediaAudioPlay" contentMode="center" id="pcd-Ly-8eO"/>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.35566622019999999" green="0.68838506939999999" blue="0.91561108830000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<group width="26" height="26" alignment="left" verticalAlignment="center" radius="0.0" id="vs3-R3-hff" userLabel="FileIconGroup">
|
||||||
|
<items>
|
||||||
|
<imageView alignment="center" verticalAlignment="center" image="File.png" contentMode="center" id="XqZ-BE-Abt">
|
||||||
|
<color key="tintColor" red="0.14697439970000001" green="0.5607914329" blue="0.88162887099999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</imageView>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</group>
|
||||||
|
<group alignment="left" verticalAlignment="center" layout="vertical" spacing="0.0" id="7Mg-3H-okj" userLabel="FileMetaGroup">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" text="File Name" id="xUk-Hc-qsr">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
<label alignment="left" text="Size" id="d5k-bL-6BP">
|
||||||
|
<color key="textColor" red="0.41865724329999998" green="0.41825520989999998" blue="0.4306421876" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="6.5" right="6.5" top="2" bottom="1"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" spacing="0.0" id="zqd-Tm-ZRg" userLabel="StickerWrapper">
|
||||||
|
<items>
|
||||||
|
<group width="84" height="84" alignment="left" contentMode="scaleAspectFit" id="CH0-jD-uni" userLabel="StickerGroup">
|
||||||
|
<variation key="device=watch38mm" height="72" width="72"/>
|
||||||
|
<variation key="device=watch40mm" height="88" width="88"/>
|
||||||
|
<variation key="device=watch44mm" height="100" width="100"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="6.5" right="0.0" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" radius="12" id="hTA-bS-Jf1" userLabel="MediaGroup">
|
||||||
|
<items>
|
||||||
|
<group alignment="right" verticalAlignment="bottom" radius="10" id="eEx-yh-cyr" userLabel="DurationGroup">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" text="2:34" id="RiW-Br-zCj" userLabel="Duration">
|
||||||
|
<color key="textColor" red="0.2461894453" green="0.24618205430000001" blue="0.2461862564" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" pointSize="12"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.89292949440000002" green="0.91148859260000004" blue="0.93112039570000005" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="6" right="6" top="2" bottom="2"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.089999999999999997" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="4" right="4" top="4" bottom="4"/>
|
||||||
|
</group>
|
||||||
|
<group width="1" alignment="left" id="0B5-0H-Py3" userLabel="CaptionGroup">
|
||||||
|
<items>
|
||||||
|
<label alignment="left" text="Caption" numberOfLines="0" id="isV-kh-Mdr">
|
||||||
|
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<fontDescription key="font" type="system" pointSize="16"/>
|
||||||
|
</label>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="8" right="8" top="4" bottom="4"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<edgeInsets key="margins" left="1.5" right="1.5" top="0.0" bottom="0.0"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="10" bottom="5"/>
|
||||||
|
</group>
|
||||||
|
</items>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="4"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="audioGroup" destination="Y04-zP-Wh2" id="qTp-zF-Epj"/>
|
||||||
|
<outlet property="captionGroup" destination="0B5-0H-Py3" id="jwq-FC-JCS"/>
|
||||||
|
<outlet property="captionLabel" destination="isV-kh-Mdr" id="egK-Dy-gRD"/>
|
||||||
|
<outlet property="chatTitleLabel" destination="HtY-WB-aFI" id="sSF-mw-4JJ"/>
|
||||||
|
<outlet property="durationGroup" destination="eEx-yh-cyr" id="8Qk-ml-oO8"/>
|
||||||
|
<outlet property="durationLabel" destination="RiW-Br-zCj" id="19H-az-0O6"/>
|
||||||
|
<outlet property="fileGroup" destination="pRF-iD-Gt4" id="I5Q-Fx-UjF"/>
|
||||||
|
<outlet property="fileIconGroup" destination="vs3-R3-hff" id="MGI-5h-Lv8"/>
|
||||||
|
<outlet property="forwardFromLabel" destination="B2R-Qa-MeP" id="kPS-wB-hNR"/>
|
||||||
|
<outlet property="forwardHeaderGroup" destination="Kzq-HH-8Ev" id="bhU-ZI-H0w"/>
|
||||||
|
<outlet property="forwardTitleLabel" destination="NTL-lz-9dd" id="CCG-3i-IZ9"/>
|
||||||
|
<outlet property="map" destination="k4c-6T-xVa" id="2aI-Tb-Oqf"/>
|
||||||
|
<outlet property="mapGroup" destination="vel-8f-OFm" id="mDw-hR-bxF"/>
|
||||||
|
<outlet property="mediaGroup" destination="hTA-bS-Jf1" id="nQw-W2-W4B"/>
|
||||||
|
<outlet property="messageTextLabel" destination="wxg-r7-aSG" id="yS9-nx-01R"/>
|
||||||
|
<outlet property="nameLabel" destination="Hqd-Pr-2zp" id="ija-IM-5QH"/>
|
||||||
|
<outlet property="replyAuthorNameLabel" destination="g6r-Nm-SOQ" id="dK1-3l-hWN"/>
|
||||||
|
<outlet property="replyHeaderGroup" destination="KRV-O6-45y" id="RZE-Vt-aui"/>
|
||||||
|
<outlet property="replyHeaderImageGroup" destination="swd-Nx-MDo" id="aYu-g3-upQ"/>
|
||||||
|
<outlet property="replyMessageTextLabel" destination="dAz-x8-jbh" id="6Cn-zA-Kcs"/>
|
||||||
|
<outlet property="stickerGroup" destination="CH0-jD-uni" id="CcD-rg-vXX"/>
|
||||||
|
<outlet property="stickerWrapperGroup" destination="zqd-Tm-ZRg" id="kps-Rx-vsc"/>
|
||||||
|
<outlet property="subtitleLabel" destination="d5k-bL-6BP" id="7va-0d-xfX"/>
|
||||||
|
<outlet property="titleLabel" destination="xUk-Hc-qsr" id="ck7-ks-w9e"/>
|
||||||
|
<outlet property="venueIcon" destination="uxs-Nx-we9" id="ab7-gA-XrO"/>
|
||||||
|
<outlet property="wrapperGroup" destination="0ch-zY-dJX" id="YqT-J5-azh"/>
|
||||||
|
</connections>
|
||||||
|
</controller>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="503" y="1283"/>
|
||||||
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
|
<inferredMetricsTieBreakers>
|
||||||
|
<segue reference="UyA-Ra-UVy"/>
|
||||||
|
<segue reference="wDy-DD-bRl"/>
|
||||||
|
</inferredMetricsTieBreakers>
|
||||||
<color key="tintColor" red="0.15550534427165985" green="0.57037848234176636" blue="0.8720671534538269" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="tintColor" red="0.15550534427165985" green="0.57037848234176636" blue="0.8720671534538269" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="BotCommandIcon" width="128" height="128"/>
|
<image name="BotCommandIcon" width="128" height="128"/>
|
||||||
|
<image name="BubbleNotification" width="128" height="128"/>
|
||||||
<image name="File.png" width="128" height="128"/>
|
<image name="File.png" width="128" height="128"/>
|
||||||
|
<image name="Location" width="128" height="128"/>
|
||||||
<image name="LocationIcon" width="128" height="128"/>
|
<image name="LocationIcon" width="128" height="128"/>
|
||||||
<image name="LoginIcon" width="128" height="128"/>
|
<image name="LoginIcon" width="128" height="128"/>
|
||||||
<image name="MediaAudioPlay" width="128" height="128"/>
|
<image name="MediaAudioPlay" width="128" height="128"/>
|
||||||
|
@ -10,15 +10,15 @@ public func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModif
|
|||||||
let replacementColors: [UIColor]
|
let replacementColors: [UIColor]
|
||||||
switch fitzModifier {
|
switch fitzModifier {
|
||||||
case .type12:
|
case .type12:
|
||||||
replacementColors = [0xca907a, 0xedc5a5, 0xf7e3c3, 0xfbefd6].map { UIColor(rgb: $0) }
|
replacementColors = [0xcb7b55, 0xf6b689, 0xffcda7, 0xffdfc5].map { UIColor(rgb: $0) }
|
||||||
case .type3:
|
case .type3:
|
||||||
replacementColors = [0xaa7c60, 0xc8a987, 0xddc89f, 0xe6d6b2].map { UIColor(rgb: $0) }
|
replacementColors = [0xa45a38, 0xdf986b, 0xedb183, 0xf4c3a0].map { UIColor(rgb: $0) }
|
||||||
case .type4:
|
case .type4:
|
||||||
replacementColors = [0x8c6148, 0xad8562, 0xc49e76, 0xd4b188].map { UIColor(rgb: $0) }
|
replacementColors = [0x703a17, 0xab673d, 0xc37f4e, 0xd89667].map { UIColor(rgb: $0) }
|
||||||
case .type5:
|
case .type5:
|
||||||
replacementColors = [0x6e3c2c, 0x925a34, 0xa16e46, 0xac7a52].map { UIColor(rgb: $0) }
|
replacementColors = [0x4a2409, 0x7d3e0e, 0x965529, 0xa96337].map { UIColor(rgb: $0) }
|
||||||
case .type6:
|
case .type6:
|
||||||
replacementColors = [0x291c12, 0x472a22, 0x573b30, 0x68493c].map { UIColor(rgb: $0) }
|
replacementColors = [0x200f0a, 0x412924, 0x593d37, 0x63453f].map { UIColor(rgb: $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func colorToString(_ color: UIColor) -> String {
|
func colorToString(_ color: UIColor) -> String {
|
||||||
|
@ -835,7 +835,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
|
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
|
|
||||||
let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start()
|
let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start()
|
||||||
|
|
||||||
strongSelf.updateState { state in
|
strongSelf.updateState { state in
|
||||||
@ -859,7 +858,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
|
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
|
|
||||||
let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start()
|
let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start()
|
||||||
|
|
||||||
strongSelf.updateState { state in
|
strongSelf.updateState { state in
|
||||||
|
@ -307,23 +307,6 @@ public final class DatePickerNode: ASDisplayNode {
|
|||||||
|
|
||||||
public var valueUpdated: ((Date) -> Void)?
|
public var valueUpdated: ((Date) -> Void)?
|
||||||
|
|
||||||
public var maximumDate: Date {
|
|
||||||
get {
|
|
||||||
return self.state.maxDate
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
guard newValue != self.maximumDate else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let updatedState = State(minDate: self.state.minDate, maxDate: newValue, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
|
|
||||||
self.updateState(updatedState, animated: false)
|
|
||||||
|
|
||||||
if let size = self.validLayout {
|
|
||||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public var minimumDate: Date {
|
public var minimumDate: Date {
|
||||||
get {
|
get {
|
||||||
return self.state.minDate
|
return self.state.minDate
|
||||||
@ -336,11 +319,34 @@ public final class DatePickerNode: ASDisplayNode {
|
|||||||
let updatedState = State(minDate: newValue, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
|
let updatedState = State(minDate: newValue, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
|
||||||
self.updateState(updatedState, animated: false)
|
self.updateState(updatedState, animated: false)
|
||||||
|
|
||||||
|
self.pickerNode.minimumDate = newValue
|
||||||
|
|
||||||
if let size = self.validLayout {
|
if let size = self.validLayout {
|
||||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var maximumDate: Date {
|
||||||
|
get {
|
||||||
|
return self.state.maxDate
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
guard newValue != self.maximumDate else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let updatedState = State(minDate: self.state.minDate, maxDate: newValue, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
|
||||||
|
self.updateState(updatedState, animated: false)
|
||||||
|
|
||||||
|
self.pickerNode.maximumDate = newValue
|
||||||
|
|
||||||
|
if let size = self.validLayout {
|
||||||
|
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var date: Date {
|
public var date: Date {
|
||||||
get {
|
get {
|
||||||
return self.state.date
|
return self.state.date
|
||||||
@ -385,6 +391,8 @@ public final class DatePickerNode: ASDisplayNode {
|
|||||||
self.pickerNode = MonthPickerNode(theme: theme, strings: strings, date: self.state.date, yearRange: yearRange(for: self.state), valueChanged: { date in
|
self.pickerNode = MonthPickerNode(theme: theme, strings: strings, date: self.state.date, yearRange: yearRange(for: self.state), valueChanged: { date in
|
||||||
monthChangedImpl?(date)
|
monthChangedImpl?(date)
|
||||||
})
|
})
|
||||||
|
self.pickerNode.minimumDate = self.state.minDate
|
||||||
|
self.pickerNode.maximumDate = self.state.maxDate
|
||||||
|
|
||||||
self.monthButtonNode = HighlightTrackingButtonNode()
|
self.monthButtonNode = HighlightTrackingButtonNode()
|
||||||
self.monthTextNode = ImmediateTextNode()
|
self.monthTextNode = ImmediateTextNode()
|
||||||
@ -768,8 +776,8 @@ private final class MonthPickerNode: ASDisplayNode, UIPickerViewDelegate, UIPick
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var minDate: Date?
|
var minimumDate: Date?
|
||||||
var maxDate: Date?
|
var maximumDate: Date?
|
||||||
|
|
||||||
private let valueChanged: (Date) -> Void
|
private let valueChanged: (Date) -> Void
|
||||||
private let pickerView: UIPickerView
|
private let pickerView: UIPickerView
|
||||||
@ -847,7 +855,26 @@ private final class MonthPickerNode: ASDisplayNode, UIPickerViewDelegate, UIPick
|
|||||||
let numberOfDays = calendar.range(of: .day, in: .month, for: tempDate)!.count
|
let numberOfDays = calendar.range(of: .day, in: .month, for: tempDate)!.count
|
||||||
components.day = min(day, numberOfDays)
|
components.day = min(day, numberOfDays)
|
||||||
|
|
||||||
let date = calendar.date(from: components)!
|
var date = calendar.date(from: components)!
|
||||||
|
|
||||||
|
if let minimumDate = self.minimumDate, let maximumDate = self.maximumDate {
|
||||||
|
var changed = false
|
||||||
|
if date < minimumDate {
|
||||||
|
date = minimumDate
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if date > maximumDate {
|
||||||
|
date = maximumDate
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
let month = calendar.component(.month, from: date)
|
||||||
|
let year = calendar.component(.year, from: date)
|
||||||
|
self.pickerView.selectRow(month - 1, inComponent: 0, animated: true)
|
||||||
|
self.pickerView.selectRow(year - yearRange.startIndex, inComponent: 1, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.date = date
|
self.date = date
|
||||||
|
|
||||||
self.valueChanged(date)
|
self.valueChanged(date)
|
||||||
@ -924,7 +951,7 @@ private final class TimePickerNode: ASDisplayNode {
|
|||||||
let minutes = Int32(calendar.component(.hour, from: self.date))
|
let minutes = Int32(calendar.component(.hour, from: self.date))
|
||||||
let string = stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: self.dateTimeFormat).replacingOccurrences(of: " AM", with: "").replacingOccurrences(of: " PM", with: "")
|
let string = stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: self.dateTimeFormat).replacingOccurrences(of: " AM", with: "").replacingOccurrences(of: " PM", with: "")
|
||||||
|
|
||||||
self.textNode.attributedText = NSAttributedString(string: string, font: Font.with(size: 21.0, design: .monospace, weight: .regular, traits: []), textColor: self.theme.textColor)
|
self.textNode.attributedText = NSAttributedString(string: string, font: Font.with(size: 21.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]), textColor: self.theme.textColor)
|
||||||
let textSize = self.textNode.updateLayout(size)
|
let textSize = self.textNode.updateLayout(size)
|
||||||
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.backgroundNode.frame.width - textSize.width) / 2.0), y: floorToScreenPixels((self.backgroundNode.frame.height - textSize.height) / 2.0)), size: textSize)
|
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.backgroundNode.frame.width - textSize.width) / 2.0), y: floorToScreenPixels((self.backgroundNode.frame.height - textSize.height) / 2.0)), size: textSize)
|
||||||
|
|
||||||
|
@ -48,14 +48,12 @@ private struct InviteLinkInviteTransaction {
|
|||||||
private enum InviteLinkInviteEntryId: Hashable {
|
private enum InviteLinkInviteEntryId: Hashable {
|
||||||
case header
|
case header
|
||||||
case mainLink
|
case mainLink
|
||||||
case links(Int32)
|
|
||||||
case manage
|
case manage
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||||
case header(PresentationTheme, String, String)
|
case header(PresentationTheme, String, String)
|
||||||
case mainLink(PresentationTheme, ExportedInvitation)
|
case mainLink(PresentationTheme, ExportedInvitation)
|
||||||
case links(Int32, PresentationTheme, [ExportedInvitation])
|
|
||||||
case manage(PresentationTheme, String, Bool)
|
case manage(PresentationTheme, String, Bool)
|
||||||
|
|
||||||
var stableId: InviteLinkInviteEntryId {
|
var stableId: InviteLinkInviteEntryId {
|
||||||
@ -64,8 +62,6 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
|||||||
return .header
|
return .header
|
||||||
case .mainLink:
|
case .mainLink:
|
||||||
return .mainLink
|
return .mainLink
|
||||||
case let .links(index, _, _):
|
|
||||||
return .links(index)
|
|
||||||
case .manage:
|
case .manage:
|
||||||
return .manage
|
return .manage
|
||||||
}
|
}
|
||||||
@ -85,12 +81,6 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .links(lhsIndex, lhsTheme, lhsInvitations):
|
|
||||||
if case let .links(rhsIndex, rhsTheme, rhsInvitations) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsInvitations == rhsInvitations {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case let .manage(lhsTheme, lhsText, lhsStandalone):
|
case let .manage(lhsTheme, lhsText, lhsStandalone):
|
||||||
if case let .manage(rhsTheme, rhsText, rhsStandalone) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStandalone == rhsStandalone {
|
if case let .manage(rhsTheme, rhsText, rhsStandalone) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStandalone == rhsStandalone {
|
||||||
return true
|
return true
|
||||||
@ -106,28 +96,19 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
|||||||
switch rhs {
|
switch rhs {
|
||||||
case .header:
|
case .header:
|
||||||
return false
|
return false
|
||||||
case .mainLink, .links, .manage:
|
case .mainLink, .manage:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case .mainLink:
|
case .mainLink:
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .header, .mainLink:
|
case .header, .mainLink:
|
||||||
return false
|
return false
|
||||||
case .links, .manage:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case let .links(lhsIndex, _, _):
|
|
||||||
switch rhs {
|
|
||||||
case .header, .mainLink:
|
|
||||||
return false
|
|
||||||
case let .links(rhsIndex, _, _):
|
|
||||||
return lhsIndex < rhsIndex
|
|
||||||
case .manage:
|
case .manage:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case .manage:
|
case .manage:
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .header, .mainLink, .links:
|
case .header, .mainLink:
|
||||||
return false
|
return false
|
||||||
case .manage:
|
case .manage:
|
||||||
return true
|
return true
|
||||||
@ -148,12 +129,6 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
|||||||
interaction.mainLinkContextAction(invite, node, nil)
|
interaction.mainLinkContextAction(invite, node, nil)
|
||||||
}, viewAction: {
|
}, viewAction: {
|
||||||
})
|
})
|
||||||
case let .links(_, _, invites):
|
|
||||||
return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, count: 0, share: true, sectionId: 1, style: .plain, tapAction: { invite in
|
|
||||||
interaction.copyLink(invite)
|
|
||||||
}, contextAction: { invite, _ in
|
|
||||||
interaction.shareLink(invite)
|
|
||||||
})
|
|
||||||
case let .manage(theme, text, standalone):
|
case let .manage(theme, text, standalone):
|
||||||
return InviteLinkInviteManageItem(theme: theme, text: text, standalone: standalone, action: {
|
return InviteLinkInviteManageItem(theme: theme, text: text, standalone: standalone, action: {
|
||||||
interaction.manageLinks()
|
interaction.manageLinks()
|
||||||
@ -372,8 +347,17 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
if let invite = invite {
|
if let invite = invite {
|
||||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||||
self?.controller?.present(controller, in: .window(.root))
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
|
let isGroup: Bool
|
||||||
|
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||||
|
isGroup = false
|
||||||
|
} else {
|
||||||
|
isGroup = true
|
||||||
|
}
|
||||||
|
let controller = InviteLinkQRCodeController(context: context, invite: invite, isGroup: isGroup)
|
||||||
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
@ -392,10 +376,6 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
||||||
dismissAction()
|
dismissAction()
|
||||||
|
|
||||||
// revokePeerExportedInvitation(account: <#T##Account#>, peerId: <#T##PeerId#>, link: <#T##String#>)
|
|
||||||
// self?.revokeDisposable.set((revokePersistentPeerExportedInvitation(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
|
|
||||||
//
|
|
||||||
// }))
|
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||||
@ -430,7 +410,13 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var entries: [InviteLinkInviteEntry] = []
|
var entries: [InviteLinkInviteEntry] = []
|
||||||
|
|
||||||
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_InviteLink, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
|
let helpText: String
|
||||||
|
if let peer = peerViewMainPeer(view) as? TelegramChannel, case .broadcast = peer.info {
|
||||||
|
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelpChannel
|
||||||
|
} else {
|
||||||
|
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
|
||||||
|
}
|
||||||
|
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_InviteLink, helpText))
|
||||||
|
|
||||||
let mainInvite: ExportedInvitation?
|
let mainInvite: ExportedInvitation?
|
||||||
if let cachedData = view.cachedData as? CachedGroupData, let invite = cachedData.exportedInvitation {
|
if let cachedData = view.cachedData as? CachedGroupData, let invite = cachedData.exportedInvitation {
|
||||||
@ -444,19 +430,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
entries.append(.mainLink(presentationData.theme, mainInvite))
|
entries.append(.mainLink(presentationData.theme, mainInvite))
|
||||||
}
|
}
|
||||||
|
|
||||||
let additionalInvites = invites.invitations.filter { $0.link != mainInvite?.link }
|
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, true))
|
||||||
var index: Int32 = 0
|
|
||||||
for i in stride(from: 0, to: additionalInvites.endIndex, by: 2) {
|
|
||||||
var invitesPair: [ExportedInvitation] = []
|
|
||||||
invitesPair.append(additionalInvites[i])
|
|
||||||
if i + 1 < additionalInvites.count {
|
|
||||||
invitesPair.append(additionalInvites[i + 1])
|
|
||||||
}
|
|
||||||
entries.append(.links(index, presentationData.theme, invitesPair))
|
|
||||||
index += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, additionalInvites.isEmpty))
|
|
||||||
|
|
||||||
let previousEntries = previousEntries.swap(entries)
|
let previousEntries = previousEntries.swap(entries)
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
case revokedLinksHeader(PresentationTheme, String)
|
case revokedLinksHeader(PresentationTheme, String)
|
||||||
case revokedLinksDeleteAll(PresentationTheme, String)
|
case revokedLinksDeleteAll(PresentationTheme, String)
|
||||||
case revokedLinks(Int32, PresentationTheme, ExportedInvitation?)
|
case revokedLink(Int32, PresentationTheme, ExportedInvitation?)
|
||||||
|
|
||||||
case adminsHeader(PresentationTheme, String)
|
case adminsHeader(PresentationTheme, String)
|
||||||
case admin(Int32, PresentationTheme, ExportedInvitationCreator)
|
case admin(Int32, PresentationTheme, ExportedInvitationCreator)
|
||||||
@ -83,7 +83,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
return InviteLinksListSection.mainLink.rawValue
|
return InviteLinksListSection.mainLink.rawValue
|
||||||
case .linksHeader, .linksCreate, .link, .linksInfo:
|
case .linksHeader, .linksCreate, .link, .linksInfo:
|
||||||
return InviteLinksListSection.links.rawValue
|
return InviteLinksListSection.links.rawValue
|
||||||
case .revokedLinksHeader, .revokedLinksDeleteAll, .revokedLinks:
|
case .revokedLinksHeader, .revokedLinksDeleteAll, .revokedLink:
|
||||||
return InviteLinksListSection.revokedLinks.rawValue
|
return InviteLinksListSection.revokedLinks.rawValue
|
||||||
case .adminsHeader, .admin:
|
case .adminsHeader, .admin:
|
||||||
return InviteLinksListSection.admins.rawValue
|
return InviteLinksListSection.admins.rawValue
|
||||||
@ -112,7 +112,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
return 10001
|
return 10001
|
||||||
case .revokedLinksDeleteAll:
|
case .revokedLinksDeleteAll:
|
||||||
return 10002
|
return 10002
|
||||||
case let .revokedLinks(index, _, _):
|
case let .revokedLink(index, _, _):
|
||||||
return 10003 + index
|
return 10003 + index
|
||||||
case .adminsHeader:
|
case .adminsHeader:
|
||||||
return 20001
|
return 20001
|
||||||
@ -183,8 +183,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .revokedLinks(lhsIndex, lhsTheme, lhsLink):
|
case let .revokedLink(lhsIndex, lhsTheme, lhsLink):
|
||||||
if case let .revokedLinks(rhsIndex, rhsTheme, rhsLink) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink {
|
if case let .revokedLink(rhsIndex, rhsTheme, rhsLink) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -253,7 +253,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, color: .destructive, editing: false, action: {
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, color: .destructive, editing: false, action: {
|
||||||
arguments.deleteAllRevokedLinks()
|
arguments.deleteAllRevokedLinks()
|
||||||
})
|
})
|
||||||
case let .revokedLinks(_, _, invite):
|
case let .revokedLink(_, _, invite):
|
||||||
return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
|
return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
|
||||||
arguments.openLink(invite)
|
arguments.openLink(invite)
|
||||||
} contextAction: { invite, node, gesture in
|
} contextAction: { invite, node, gesture in
|
||||||
@ -273,7 +273,13 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
|||||||
var entries: [InviteLinksListEntry] = []
|
var entries: [InviteLinksListEntry] = []
|
||||||
|
|
||||||
if admin == nil {
|
if admin == nil {
|
||||||
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp))
|
let helpText: String
|
||||||
|
if let peer = peerViewMainPeer(view) as? TelegramChannel, case .broadcast = peer.info {
|
||||||
|
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelpChannel
|
||||||
|
} else {
|
||||||
|
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
|
||||||
|
}
|
||||||
|
entries.append(.header(presentationData.theme, helpText))
|
||||||
}
|
}
|
||||||
|
|
||||||
let mainInvite: ExportedInvitation?
|
let mainInvite: ExportedInvitation?
|
||||||
@ -308,21 +314,32 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
|||||||
entries.append(.mainLinkOtherInfo(presentationData.theme, string.0))
|
entries.append(.mainLinkOtherInfo(presentationData.theme, string.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.linksHeader(presentationData.theme, presentationData.strings.InviteLink_AdditionalLinks.uppercased()))
|
|
||||||
if admin == nil {
|
|
||||||
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
|
|
||||||
}
|
|
||||||
var additionalInvites: [ExportedInvitation]?
|
var additionalInvites: [ExportedInvitation]?
|
||||||
if let invites = invites {
|
if let invites = invites {
|
||||||
additionalInvites = invites.filter { $0.link != mainInvite?.link }
|
additionalInvites = invites.filter { $0.link != mainInvite?.link }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hasLinks = false
|
||||||
|
if let additionalInvites = additionalInvites, !additionalInvites.isEmpty {
|
||||||
|
hasLinks = true
|
||||||
|
} else if let admin = admin, admin.count > 1 {
|
||||||
|
hasLinks = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasLinks {
|
||||||
|
entries.append(.linksHeader(presentationData.theme, presentationData.strings.InviteLink_AdditionalLinks.uppercased()))
|
||||||
|
}
|
||||||
|
if admin == nil {
|
||||||
|
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
|
||||||
|
}
|
||||||
|
|
||||||
if let additionalInvites = additionalInvites {
|
if let additionalInvites = additionalInvites {
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
for invite in additionalInvites {
|
for invite in additionalInvites {
|
||||||
entries.append(.link(index, presentationData.theme, invite, invite.expireDate != nil ? tick : nil))
|
entries.append(.link(index, presentationData.theme, invite, invite.expireDate != nil ? tick : nil))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
} else if let admin = admin, admin.count > 0 {
|
} else if let admin = admin, admin.count > 1 {
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
for _ in 0 ..< admin.count - 1 {
|
for _ in 0 ..< admin.count - 1 {
|
||||||
entries.append(.link(index, presentationData.theme, nil, nil))
|
entries.append(.link(index, presentationData.theme, nil, nil))
|
||||||
@ -340,7 +357,13 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
|||||||
}
|
}
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
for invite in revokedInvites {
|
for invite in revokedInvites {
|
||||||
entries.append(.revokedLinks(index, presentationData.theme, invite))
|
entries.append(.revokedLink(index, presentationData.theme, invite))
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
} else if let admin = admin, admin.revokedCount > 0 {
|
||||||
|
var index: Int32 = 0
|
||||||
|
for _ in 0 ..< admin.revokedCount {
|
||||||
|
entries.append(.revokedLink(index, presentationData.theme, nil))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,7 +410,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
|
|||||||
var getControllerImpl: (() -> ViewController?)?
|
var getControllerImpl: (() -> ViewController?)?
|
||||||
|
|
||||||
let adminId = admin?.peer.peer?.id
|
let adminId = admin?.peer.peer?.id
|
||||||
let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: false, forceUpdate: false)
|
let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: false, forceUpdate: true)
|
||||||
let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: true, forceUpdate: true)
|
let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: true, forceUpdate: true)
|
||||||
|
|
||||||
let creators: Signal<[ExportedInvitationCreator], NoError>
|
let creators: Signal<[ExportedInvitationCreator], NoError>
|
||||||
@ -435,8 +458,17 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
|
|||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||||
presentControllerImpl?(controller, nil)
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
let isGroup: Bool
|
||||||
|
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||||
|
isGroup = false
|
||||||
|
} else {
|
||||||
|
isGroup = true
|
||||||
|
}
|
||||||
|
let controller = InviteLinkQRCodeController(context: context, invite: invite, isGroup: isGroup)
|
||||||
|
presentControllerImpl?(controller, nil)
|
||||||
|
})
|
||||||
})))
|
})))
|
||||||
|
|
||||||
if invite.adminId.toInt64() != 0 {
|
if invite.adminId.toInt64() != 0 {
|
||||||
@ -545,8 +577,17 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
|
|||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||||
presentControllerImpl?(controller, nil)
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
let isGroup: Bool
|
||||||
|
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||||
|
isGroup = false
|
||||||
|
} else {
|
||||||
|
isGroup = true
|
||||||
|
}
|
||||||
|
let controller = InviteLinkQRCodeController(context: context, invite: invite, isGroup: isGroup)
|
||||||
|
presentControllerImpl?(controller, nil)
|
||||||
|
})
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextEdit, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextEdit, icon: { theme in
|
||||||
|
@ -37,14 +37,16 @@ public final class InviteLinkQRCodeController: ViewController {
|
|||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let invite: ExportedInvitation
|
private let invite: ExportedInvitation
|
||||||
|
private let isGroup: Bool
|
||||||
|
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
private let idleTimerExtensionDisposable = MetaDisposable()
|
private let idleTimerExtensionDisposable = MetaDisposable()
|
||||||
|
|
||||||
public init(context: AccountContext, invite: ExportedInvitation) {
|
public init(context: AccountContext, invite: ExportedInvitation, isGroup: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.invite = invite
|
self.invite = invite
|
||||||
|
self.isGroup = isGroup
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
@ -74,7 +76,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = Node(context: self.context, invite: self.invite)
|
self.displayNode = Node(context: self.context, invite: self.invite, isGroup: self.isGroup)
|
||||||
self.controllerNode.dismiss = { [weak self] in
|
self.controllerNode.dismiss = { [weak self] in
|
||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
}
|
}
|
||||||
@ -133,7 +135,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
|||||||
var dismiss: (() -> Void)?
|
var dismiss: (() -> Void)?
|
||||||
var cancel: (() -> Void)?
|
var cancel: (() -> Void)?
|
||||||
|
|
||||||
init(context: AccountContext, invite: ExportedInvitation) {
|
init(context: AccountContext, invite: ExportedInvitation, isGroup: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.invite = invite
|
self.invite = invite
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -211,7 +213,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
|||||||
|
|
||||||
let textFont = Font.regular(13.0)
|
let textFont = Font.regular(13.0)
|
||||||
|
|
||||||
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_QRCode_Info, font: textFont, textColor: secondaryTextColor)
|
self.textNode.attributedText = NSAttributedString(string: isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel, font: textFont, textColor: secondaryTextColor)
|
||||||
self.buttonNode.title = self.presentationData.strings.InviteLink_QRCode_Share
|
self.buttonNode.title = self.presentationData.strings.InviteLink_QRCode_Share
|
||||||
|
|
||||||
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||||
|
@ -343,7 +343,6 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
private var disposable: Disposable?
|
private var disposable: Disposable?
|
||||||
private let actionDisposable = MetaDisposable()
|
|
||||||
|
|
||||||
private let dimNode: ASDisplayNode
|
private let dimNode: ASDisplayNode
|
||||||
private let contentNode: ASDisplayNode
|
private let contentNode: ASDisplayNode
|
||||||
@ -473,9 +472,10 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
ActionSheetTextItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Text),
|
ActionSheetTextItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Text),
|
||||||
ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Action, color: .destructive, action: {
|
ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Action, color: .destructive, action: {
|
||||||
dismissAction()
|
dismissAction()
|
||||||
|
self?.controller?.dismiss()
|
||||||
self?.actionDisposable.set((deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
|
|
||||||
}))
|
let _ = (deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
|
||||||
|
})
|
||||||
|
|
||||||
self?.controller?.revokedInvitationsContext?.remove(invite)
|
self?.controller?.revokedInvitationsContext?.remove(invite)
|
||||||
})
|
})
|
||||||
@ -490,7 +490,42 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
}, action: { [weak self] _, f in
|
}, action: { [weak self] _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
|
let isGroup: Bool
|
||||||
|
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||||
|
isGroup = false
|
||||||
|
} else {
|
||||||
|
isGroup = true
|
||||||
|
}
|
||||||
|
let controller = InviteLinkQRCodeController(context: context, invite: invite, isGroup: isGroup)
|
||||||
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||||
|
}, action: { [weak self] _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
|
let controller = ActionSheetController(presentationData: presentationData)
|
||||||
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
}
|
||||||
|
controller.setItemGroups([
|
||||||
|
ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text),
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
||||||
|
dismissAction()
|
||||||
|
self?.controller?.dismiss()
|
||||||
|
|
||||||
|
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
|
||||||
|
})
|
||||||
|
|
||||||
|
self?.controller?.revokedInvitationsContext?.remove(invite)
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||||
|
])
|
||||||
self?.controller?.present(controller, in: .window(.root))
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
@ -739,6 +774,7 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
subtitleText = self.presentationData.strings.InviteLink_Revoked
|
subtitleText = self.presentationData.strings.InviteLink_Revoked
|
||||||
} else if let usageLimit = self.invite.usageLimit, let count = self.invite.count, count >= usageLimit {
|
} else if let usageLimit = self.invite.usageLimit, let count = self.invite.count, count >= usageLimit {
|
||||||
subtitleText = self.presentationData.strings.InviteLink_UsageLimitReached
|
subtitleText = self.presentationData.strings.InviteLink_UsageLimitReached
|
||||||
|
subtitleColor = self.presentationData.theme.list.itemDestructiveColor
|
||||||
} else if let expireDate = self.invite.expireDate {
|
} else if let expireDate = self.invite.expireDate {
|
||||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
if currentTime >= expireDate {
|
if currentTime >= expireDate {
|
||||||
|
@ -1,670 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import SwiftSignalKit
|
|
||||||
import SyncCore
|
|
||||||
import TelegramPresentationData
|
|
||||||
import ItemListUI
|
|
||||||
import SolidRoundedButtonNode
|
|
||||||
import RadialStatusNode
|
|
||||||
|
|
||||||
private let itemSpacing: CGFloat = 10.0
|
|
||||||
private let titleFont = Font.semibold(17.0)
|
|
||||||
private let subtitleFont = Font.regular(12.0)
|
|
||||||
|
|
||||||
private enum ItemBackgroundColor: Equatable {
|
|
||||||
case blue
|
|
||||||
case green
|
|
||||||
case yellow
|
|
||||||
case red
|
|
||||||
case gray
|
|
||||||
|
|
||||||
var colors: (top: UIColor, bottom: UIColor, text: UIColor) {
|
|
||||||
switch self {
|
|
||||||
case .blue:
|
|
||||||
return (UIColor(rgb: 0x00b5f7), UIColor(rgb: 0x00b2f6), UIColor(rgb: 0xa7f4ff))
|
|
||||||
case .green:
|
|
||||||
return (UIColor(rgb: 0x4aca62), UIColor(rgb: 0x43c85c), UIColor(rgb: 0xc5ffe6))
|
|
||||||
case .yellow:
|
|
||||||
return (UIColor(rgb: 0xf8a953), UIColor(rgb: 0xf7a64e), UIColor(rgb: 0xfeffd7))
|
|
||||||
case .red:
|
|
||||||
return (UIColor(rgb: 0xf2656a), UIColor(rgb: 0xf25f65), UIColor(rgb: 0xffd3de))
|
|
||||||
case .gray:
|
|
||||||
return (UIColor(rgb: 0xa8b2bb), UIColor(rgb: 0xa2abb4), UIColor(rgb: 0xe3e6e8))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private let moreIcon = generateImage(CGSize(width: 26.0, height: 26.0), contextGenerator: { size, context in
|
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
context.setFillColor(UIColor.white.cgColor)
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
context.setBlendMode(.clear)
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 4.0, y: 11.0), size: CGSize(width: 4.0, height: 4.0)))
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 11.0, y: 11.0), size: CGSize(width: 4.0, height: 4.0)))
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 18.0, y: 11.0), size: CGSize(width: 4.0, height: 4.0)))
|
|
||||||
})
|
|
||||||
|
|
||||||
private let shareIcon = generateImage(CGSize(width: 26.0, height: 26.0), contextGenerator: { size, context in
|
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
context.setFillColor(UIColor.white.cgColor)
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
if let maskImage = UIImage(bundleImageName: "Chat/Links/Share") {
|
|
||||||
context.clip(to: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - maskImage.size.width) / 2.0), y: floorToScreenPixels((size.height - maskImage.size.height) / 2.0)), size: maskImage.size), mask: maskImage.cgImage!)
|
|
||||||
context.setBlendMode(.clear)
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
private class ItemNode: ASDisplayNode {
|
|
||||||
private let selectionNode: HighlightTrackingButtonNode
|
|
||||||
private let wrapperNode: ASDisplayNode
|
|
||||||
private let backgroundNode: ASDisplayNode
|
|
||||||
private let backgroundGradientLayer: CAGradientLayer
|
|
||||||
|
|
||||||
private let iconNode: ASImageNode
|
|
||||||
private var timerNode: TimerNode?
|
|
||||||
|
|
||||||
private let extractedContainerNode: ContextExtractedContentContainingNode
|
|
||||||
private let containerNode: ContextControllerSourceNode
|
|
||||||
private let buttonNode: HighlightTrackingButtonNode
|
|
||||||
private let buttonIconNode: ASImageNode
|
|
||||||
|
|
||||||
private let titleNode: ImmediateTextNode
|
|
||||||
private let subtitleNode: ImmediateTextNode
|
|
||||||
|
|
||||||
private var updateTimer: SwiftSignalKit.Timer?
|
|
||||||
|
|
||||||
private var params: (size: CGSize, wide: Bool, invite: ExportedInvitation?, color: ItemBackgroundColor, presentationData: ItemListPresentationData)?
|
|
||||||
|
|
||||||
var action: (() -> Void)?
|
|
||||||
var contextAction: ((ASDisplayNode) -> Void)?
|
|
||||||
|
|
||||||
private let hapticFeedback = HapticFeedback()
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
self.selectionNode = HighlightTrackingButtonNode()
|
|
||||||
self.wrapperNode = ASDisplayNode()
|
|
||||||
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
|
||||||
self.backgroundNode.clipsToBounds = true
|
|
||||||
self.backgroundNode.cornerRadius = 15.0
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
self.backgroundNode.layer.cornerCurve = .continuous
|
|
||||||
}
|
|
||||||
self.backgroundNode.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.backgroundGradientLayer = CAGradientLayer()
|
|
||||||
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
|
|
||||||
self.backgroundGradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
|
|
||||||
self.backgroundNode.layer.addSublayer(self.backgroundGradientLayer)
|
|
||||||
|
|
||||||
self.iconNode = ASImageNode()
|
|
||||||
self.iconNode.displaysAsynchronously = false
|
|
||||||
self.iconNode.displayWithoutProcessing = true
|
|
||||||
self.iconNode.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.buttonNode = HighlightTrackingButtonNode()
|
|
||||||
self.extractedContainerNode = ContextExtractedContentContainingNode()
|
|
||||||
self.containerNode = ContextControllerSourceNode()
|
|
||||||
self.containerNode.isGestureEnabled = false
|
|
||||||
self.buttonIconNode = ASImageNode()
|
|
||||||
self.buttonIconNode.displaysAsynchronously = false
|
|
||||||
self.buttonIconNode.displayWithoutProcessing = true
|
|
||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
|
||||||
self.titleNode.maximumNumberOfLines = 2
|
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.subtitleNode = ImmediateTextNode()
|
|
||||||
self.subtitleNode.maximumNumberOfLines = 1
|
|
||||||
self.subtitleNode.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.addSubnode(self.wrapperNode)
|
|
||||||
self.wrapperNode.addSubnode(self.backgroundNode)
|
|
||||||
self.wrapperNode.addSubnode(self.iconNode)
|
|
||||||
|
|
||||||
self.containerNode.addSubnode(self.extractedContainerNode)
|
|
||||||
self.extractedContainerNode.contentNode.addSubnode(self.buttonIconNode)
|
|
||||||
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
|
|
||||||
self.buttonNode.addSubnode(self.containerNode)
|
|
||||||
|
|
||||||
self.wrapperNode.addSubnode(self.selectionNode)
|
|
||||||
self.wrapperNode.addSubnode(self.buttonNode)
|
|
||||||
|
|
||||||
self.wrapperNode.addSubnode(self.titleNode)
|
|
||||||
self.wrapperNode.addSubnode(self.subtitleNode)
|
|
||||||
|
|
||||||
self.selectionNode.addTarget(self, action: #selector(self.tapped), forControlEvents: .touchUpInside)
|
|
||||||
self.selectionNode.highligthedChanged = { [weak self] highlighted in
|
|
||||||
if let strongSelf = self {
|
|
||||||
if highlighted {
|
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.18, curve: .linear)
|
|
||||||
transition.updateSublayerTransformScale(node: strongSelf, scale: 0.95)
|
|
||||||
} else {
|
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .linear)
|
|
||||||
transition.updateSublayerTransformScale(node: strongSelf, scale: 1.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
|
||||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
|
||||||
if let strongSelf = self {
|
|
||||||
if highlighted {
|
|
||||||
strongSelf.buttonIconNode.layer.removeAnimation(forKey: "opacity")
|
|
||||||
strongSelf.buttonIconNode.alpha = 0.4
|
|
||||||
} else {
|
|
||||||
strongSelf.buttonIconNode.alpha = 1.0
|
|
||||||
strongSelf.buttonIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.updateTimer?.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func tapped() {
|
|
||||||
self.hapticFeedback.impact(.light)
|
|
||||||
self.action?()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func buttonPressed() {
|
|
||||||
self.contextAction?(self.extractedContainerNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(size: CGSize, wide: Bool, share: Bool, invite: ExportedInvitation?, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
|
|
||||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
|
||||||
|
|
||||||
let availability = invite.flatMap { invitationAvailability($0) } ?? 0.0
|
|
||||||
let transitionFraction: CGFloat
|
|
||||||
let color: ItemBackgroundColor
|
|
||||||
let nextColor: ItemBackgroundColor
|
|
||||||
if let invite = invite {
|
|
||||||
if invite.isRevoked {
|
|
||||||
color = .gray
|
|
||||||
nextColor = .gray
|
|
||||||
transitionFraction = 0.0
|
|
||||||
} else if invite.expireDate == nil && invite.usageLimit == nil {
|
|
||||||
color = .blue
|
|
||||||
nextColor = .blue
|
|
||||||
transitionFraction = 0.0
|
|
||||||
} else if availability >= 0.5 {
|
|
||||||
color = .green
|
|
||||||
nextColor = .yellow
|
|
||||||
transitionFraction = (availability - 0.5) / 0.5
|
|
||||||
} else if availability > 0.0 {
|
|
||||||
color = .yellow
|
|
||||||
nextColor = .red
|
|
||||||
transitionFraction = availability / 0.5
|
|
||||||
} else {
|
|
||||||
color = .red
|
|
||||||
nextColor = .red
|
|
||||||
transitionFraction = 0.0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
color = .gray
|
|
||||||
nextColor = .gray
|
|
||||||
transitionFraction = 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
let previousParams = self.params
|
|
||||||
self.params = (size, wide, invite, color, presentationData)
|
|
||||||
|
|
||||||
let previousExpireDate = previousParams?.invite?.expireDate
|
|
||||||
if previousExpireDate != invite?.expireDate {
|
|
||||||
self.updateTimer?.invalidate()
|
|
||||||
self.updateTimer = nil
|
|
||||||
|
|
||||||
if let expireDate = invite?.expireDate, availability > 0.0 {
|
|
||||||
let timeout = min(2.0, max(0.001, Double(expireDate - currentTime)))
|
|
||||||
let updateTimer = SwiftSignalKit.Timer(timeout: timeout, repeat: true, completion: { [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
if let (size, wide, invite, _, presentationData) = strongSelf.params {
|
|
||||||
let _ = strongSelf.update(size: size, wide: wide, share: share, invite: invite, presentationData: presentationData, transition: .animated(duration: 0.3, curve: .linear))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, queue: Queue.mainQueue())
|
|
||||||
self.updateTimer = updateTimer
|
|
||||||
updateTimer.start()
|
|
||||||
}
|
|
||||||
} else if availability.isZero {
|
|
||||||
self.updateTimer?.invalidate()
|
|
||||||
self.updateTimer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let topColor = color.colors.top
|
|
||||||
let bottomColor = color.colors.bottom
|
|
||||||
let nextTopColor = nextColor.colors.top
|
|
||||||
let nextBottomColor = nextColor.colors.bottom
|
|
||||||
let colors: NSArray
|
|
||||||
if let invite = invite {
|
|
||||||
colors = [nextTopColor.mixedWith(topColor, alpha: transitionFraction).cgColor, nextBottomColor.mixedWith(bottomColor, alpha: transitionFraction).cgColor]
|
|
||||||
} else {
|
|
||||||
colors = [UIColor(rgb: 0xf2f2f7).cgColor, UIColor(rgb: 0xf2f2f7).cgColor]
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (_, _, previousInvite, previousColor, _) = previousParams, previousInvite == invite {
|
|
||||||
if previousColor != color && color == .red {
|
|
||||||
if let snapshotView = self.wrapperNode.view.snapshotContentTree() {
|
|
||||||
snapshotView.frame = self.wrapperNode.bounds
|
|
||||||
self.wrapperNode.view.addSubview(snapshotView)
|
|
||||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
|
||||||
snapshotView?.removeFromSuperview()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
self.backgroundGradientLayer.colors = colors as? [Any]
|
|
||||||
} else if (color == .green && nextColor == .yellow) || (color == .yellow && nextColor == .red) {
|
|
||||||
let previousColors = self.backgroundGradientLayer.colors
|
|
||||||
if transition.isAnimated {
|
|
||||||
self.backgroundGradientLayer.animate(from: previousColors as AnyObject, to: self.backgroundGradientLayer.colors as AnyObject, keyPath: "colors", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 2.5)
|
|
||||||
}
|
|
||||||
self.backgroundGradientLayer.colors = colors as? [Any]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.backgroundGradientLayer.colors = colors as? [Any]
|
|
||||||
}
|
|
||||||
|
|
||||||
let secondaryTextColor = nextColor.colors.text.mixedWith(color.colors.text, alpha: transitionFraction)
|
|
||||||
|
|
||||||
let itemWidth = wide ? size.width : floor((size.width - itemSpacing) / 2.0)
|
|
||||||
var inviteLink = invite?.link.replacingOccurrences(of: "https://", with: "") ?? ""
|
|
||||||
if !wide {
|
|
||||||
inviteLink = inviteLink.replacingOccurrences(of: "joinchat/", with: "joinchat/\n")
|
|
||||||
inviteLink = inviteLink.replacingOccurrences(of: "join/", with: "join/\n")
|
|
||||||
}
|
|
||||||
let title: NSMutableAttributedString = NSMutableAttributedString(string: inviteLink, font: titleFont, textColor: UIColor.white)
|
|
||||||
if inviteLink.hasPrefix("t.me/joinchat/") {
|
|
||||||
title.addAttribute(NSAttributedString.Key.foregroundColor, value: secondaryTextColor, range: NSMakeRange(0, "t.me/joinchat/".count))
|
|
||||||
} else if inviteLink.hasPrefix("t.me/join/") {
|
|
||||||
title.addAttribute(NSAttributedString.Key.foregroundColor, value: secondaryTextColor, range: NSMakeRange(0, "t.me/join/".count))
|
|
||||||
}
|
|
||||||
self.titleNode.attributedText = title
|
|
||||||
|
|
||||||
self.buttonIconNode.image = share ? shareIcon : moreIcon
|
|
||||||
|
|
||||||
var subtitleText: String = ""
|
|
||||||
if let invite = invite {
|
|
||||||
if let count = invite.count {
|
|
||||||
subtitleText = presentationData.strings.InviteLink_PeopleJoinedShort(count)
|
|
||||||
} else {
|
|
||||||
subtitleText = [.red, .gray].contains(color) ? presentationData.strings.InviteLink_PeopleJoinedShortNoneExpired : presentationData.strings.InviteLink_PeopleJoinedShortNone
|
|
||||||
}
|
|
||||||
if invite.isRevoked {
|
|
||||||
if !subtitleText.isEmpty {
|
|
||||||
subtitleText += " • "
|
|
||||||
}
|
|
||||||
subtitleText += presentationData.strings.InviteLink_Revoked
|
|
||||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
|
|
||||||
self.timerNode?.removeFromSupernode()
|
|
||||||
self.timerNode = nil
|
|
||||||
} else if let expireDate = invite.expireDate, currentTime >= expireDate {
|
|
||||||
if !subtitleText.isEmpty {
|
|
||||||
subtitleText += " • "
|
|
||||||
}
|
|
||||||
if share {
|
|
||||||
subtitleText = presentationData.strings.InviteLink_Expired
|
|
||||||
} else {
|
|
||||||
subtitleText += presentationData.strings.InviteLink_Expired
|
|
||||||
}
|
|
||||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
|
|
||||||
self.timerNode?.removeFromSupernode()
|
|
||||||
self.timerNode = nil
|
|
||||||
} else if let usageLimit = invite.usageLimit, let count = invite.count, count >= usageLimit {
|
|
||||||
if !subtitleText.isEmpty {
|
|
||||||
subtitleText += " • "
|
|
||||||
}
|
|
||||||
if share {
|
|
||||||
subtitleText = presentationData.strings.InviteLink_UsageLimitReached
|
|
||||||
} else {
|
|
||||||
subtitleText += presentationData.strings.InviteLink_UsageLimitReached
|
|
||||||
}
|
|
||||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Expired"), color: .white)
|
|
||||||
self.timerNode?.removeFromSupernode()
|
|
||||||
self.timerNode = nil
|
|
||||||
} else if let expireDate = invite.expireDate {
|
|
||||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Flame"), color: .white)
|
|
||||||
let timerNode: TimerNode
|
|
||||||
if let current = self.timerNode {
|
|
||||||
timerNode = current
|
|
||||||
} else {
|
|
||||||
timerNode = TimerNode()
|
|
||||||
timerNode.isUserInteractionEnabled = false
|
|
||||||
self.timerNode = timerNode
|
|
||||||
self.addSubnode(timerNode)
|
|
||||||
}
|
|
||||||
timerNode.update(color: UIColor.white, creationTimestamp: invite.startDate ?? invite.date, deadlineTimestamp: expireDate)
|
|
||||||
if share {
|
|
||||||
subtitleText = presentationData.strings.InviteLink_TapToCopy
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Link"), color: .white)
|
|
||||||
self.timerNode?.removeFromSupernode()
|
|
||||||
self.timerNode = nil
|
|
||||||
if share {
|
|
||||||
subtitleText = presentationData.strings.InviteLink_TapToCopy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.iconNode.isHidden = false
|
|
||||||
self.buttonIconNode.isHidden = false
|
|
||||||
} else {
|
|
||||||
self.iconNode.isHidden = true
|
|
||||||
self.buttonIconNode.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
self.iconNode.frame = CGRect(x: 10.0, y: 10.0, width: 30.0, height: 30.0)
|
|
||||||
self.timerNode?.frame = CGRect(x: 8.0, y: 8.0, width: 34.0, height: 34.0)
|
|
||||||
|
|
||||||
self.subtitleNode.attributedText = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: secondaryTextColor)
|
|
||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: itemWidth - 24.0, height: 100.0))
|
|
||||||
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: itemWidth - 24.0, height: 100.0))
|
|
||||||
|
|
||||||
self.titleNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 52.0), size: titleSize)
|
|
||||||
self.subtitleNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 52.0 + titleSize.height + 3.0), size: subtitleSize)
|
|
||||||
|
|
||||||
let itemSize = CGSize(width: itemWidth, height: wide ? 102.0 : 122.0)
|
|
||||||
|
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(), size: itemSize)
|
|
||||||
transition.updateFrame(node: self.wrapperNode, frame: backgroundFrame)
|
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
|
||||||
transition.updateFrame(node: self.selectionNode, frame: backgroundFrame)
|
|
||||||
transition.updateFrame(layer: self.backgroundGradientLayer, frame: backgroundFrame)
|
|
||||||
|
|
||||||
let buttonSize = CGSize(width: 26.0, height: 26.0)
|
|
||||||
let buttonFrame = CGRect(origin: CGPoint(x: itemSize.width - buttonSize.width - 12.0, y: 12.0), size: buttonSize)
|
|
||||||
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
|
||||||
|
|
||||||
self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: buttonSize)
|
|
||||||
self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(), size: buttonSize)
|
|
||||||
self.buttonIconNode.frame = CGRect(origin: CGPoint(), size: buttonSize)
|
|
||||||
|
|
||||||
return itemSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class InviteLinksGridNode: ASDisplayNode {
|
|
||||||
private var items: [ExportedInvitation]?
|
|
||||||
private var itemNodes: [String: ItemNode] = [:]
|
|
||||||
|
|
||||||
var action: ((ExportedInvitation) -> Void)?
|
|
||||||
var contextAction: ((ASDisplayNode, ExportedInvitation) -> Void)?
|
|
||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
||||||
let result = super.hitTest(point, with: event)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(size: CGSize, safeInset: CGFloat, items: [ExportedInvitation]?, count: Int, share: Bool, presentationData: ItemListPresentationData, transition: ContainedViewLayoutTransition) -> CGSize {
|
|
||||||
self.items = items
|
|
||||||
|
|
||||||
var contentSize: CGSize = size
|
|
||||||
var contentHeight: CGFloat = 0.0
|
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0 + safeInset
|
|
||||||
|
|
||||||
var validIds = Set<String>()
|
|
||||||
|
|
||||||
let count = items?.count ?? count
|
|
||||||
|
|
||||||
for i in 0 ..< count {
|
|
||||||
let invite: ExportedInvitation?
|
|
||||||
let id: String
|
|
||||||
if let items = items, i < items.count {
|
|
||||||
invite = items[i]
|
|
||||||
id = invite!.link
|
|
||||||
} else {
|
|
||||||
invite = nil
|
|
||||||
id = "placeholder_\(i)"
|
|
||||||
}
|
|
||||||
|
|
||||||
validIds.insert(id)
|
|
||||||
|
|
||||||
var itemNode: ItemNode?
|
|
||||||
var wasAdded = false
|
|
||||||
|
|
||||||
if let current = self.itemNodes[id] {
|
|
||||||
itemNode = current
|
|
||||||
} else {
|
|
||||||
wasAdded = true
|
|
||||||
let addedItemNode = ItemNode()
|
|
||||||
itemNode = addedItemNode
|
|
||||||
self.itemNodes[id] = addedItemNode
|
|
||||||
self.addSubnode(addedItemNode)
|
|
||||||
}
|
|
||||||
if let itemNode = itemNode {
|
|
||||||
let col = CGFloat(i % 2)
|
|
||||||
let row = floor(CGFloat(i) / 2.0)
|
|
||||||
let wide = (i == count - 1 && (count % 2) != 0)
|
|
||||||
let itemSize = itemNode.update(size: CGSize(width: size.width - sideInset * 2.0, height: size.height), wide: wide, share: share, invite: invite, presentationData: presentationData, transition: transition)
|
|
||||||
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: 4.0 + row * (122.0 + itemSpacing)), size: itemSize)
|
|
||||||
if !wide && col > 0 {
|
|
||||||
itemFrame.origin.x += itemSpacing + itemSize.width
|
|
||||||
}
|
|
||||||
|
|
||||||
contentHeight = max(contentHeight, itemFrame.maxY + itemSpacing)
|
|
||||||
|
|
||||||
if wasAdded {
|
|
||||||
itemNode.frame = itemFrame
|
|
||||||
} else {
|
|
||||||
transition.updateFrame(node: itemNode, frame: itemFrame)
|
|
||||||
}
|
|
||||||
itemNode.action = { [weak self] in
|
|
||||||
if let invite = invite {
|
|
||||||
self?.action?(invite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
itemNode.contextAction = { [weak self] node in
|
|
||||||
if let invite = invite {
|
|
||||||
self?.contextAction?(node, invite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var removeIds: [String] = []
|
|
||||||
for (id, _) in self.itemNodes {
|
|
||||||
if !validIds.contains(id) {
|
|
||||||
removeIds.append(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for id in removeIds {
|
|
||||||
if let itemNode = self.itemNodes.removeValue(forKey: id) {
|
|
||||||
itemNode.removeFromSupernode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contentSize.height = contentHeight
|
|
||||||
return contentSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct ContentParticle {
|
|
||||||
var position: CGPoint
|
|
||||||
var direction: CGPoint
|
|
||||||
var velocity: CGFloat
|
|
||||||
var alpha: CGFloat
|
|
||||||
var lifetime: Double
|
|
||||||
var beginTime: Double
|
|
||||||
|
|
||||||
init(position: CGPoint, direction: CGPoint, velocity: CGFloat, alpha: CGFloat, lifetime: Double, beginTime: Double) {
|
|
||||||
self.position = position
|
|
||||||
self.direction = direction
|
|
||||||
self.velocity = velocity
|
|
||||||
self.alpha = alpha
|
|
||||||
self.lifetime = lifetime
|
|
||||||
self.beginTime = beginTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class TimerNode: ASDisplayNode {
|
|
||||||
private struct Params: Equatable {
|
|
||||||
var color: UIColor
|
|
||||||
var creationTimestamp: Int32
|
|
||||||
var deadlineTimestamp: Int32
|
|
||||||
}
|
|
||||||
|
|
||||||
private let hierarchyTrackingNode: HierarchyTrackingNode
|
|
||||||
private var inHierarchyValue: Bool = false
|
|
||||||
|
|
||||||
private var animator: ConstantDisplayLinkAnimator?
|
|
||||||
private let contentNode: ASDisplayNode
|
|
||||||
private var particles: [ContentParticle] = []
|
|
||||||
|
|
||||||
private var currentParams: Params?
|
|
||||||
|
|
||||||
var reachedTimeout: (() -> Void)?
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
var updateInHierarchy: ((Bool) -> Void)?
|
|
||||||
self.hierarchyTrackingNode = HierarchyTrackingNode({ value in
|
|
||||||
updateInHierarchy?(value)
|
|
||||||
})
|
|
||||||
|
|
||||||
self.contentNode = ASDisplayNode()
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.addSubnode(self.contentNode)
|
|
||||||
|
|
||||||
updateInHierarchy = { [weak self] value in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.inHierarchyValue = value
|
|
||||||
strongSelf.animator?.isPaused = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.animator?.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(color: UIColor, creationTimestamp: Int32, deadlineTimestamp: Int32) {
|
|
||||||
let params = Params(
|
|
||||||
color: color,
|
|
||||||
creationTimestamp: creationTimestamp,
|
|
||||||
deadlineTimestamp: deadlineTimestamp
|
|
||||||
)
|
|
||||||
self.currentParams = params
|
|
||||||
|
|
||||||
self.updateValues()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateValues() {
|
|
||||||
guard let params = self.currentParams else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let color = params.color
|
|
||||||
|
|
||||||
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
|
||||||
var fraction = CGFloat(params.deadlineTimestamp - currentTimestamp) / CGFloat(params.deadlineTimestamp - params.creationTimestamp)
|
|
||||||
fraction = max(0.0001, 1.0 - max(0.0, min(1.0, fraction)))
|
|
||||||
|
|
||||||
let image: UIImage?
|
|
||||||
|
|
||||||
let diameter: CGFloat = 26.0
|
|
||||||
let inset: CGFloat = 8.0
|
|
||||||
let lineWidth: CGFloat = 2.0
|
|
||||||
|
|
||||||
let timestamp = CACurrentMediaTime()
|
|
||||||
|
|
||||||
let center = CGPoint(x: (diameter + inset) / 2.0, y: (diameter + inset) / 2.0)
|
|
||||||
let radius: CGFloat = (diameter - lineWidth / 2.0) / 2.0
|
|
||||||
|
|
||||||
let startAngle: CGFloat = -CGFloat.pi / 2.0
|
|
||||||
let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * fraction
|
|
||||||
|
|
||||||
let sparks = fraction > 0.1 && fraction != 1.0
|
|
||||||
if sparks {
|
|
||||||
let v = CGPoint(x: sin(endAngle), y: -cos(endAngle))
|
|
||||||
let c = CGPoint(x: -v.y * radius + center.x, y: v.x * radius + center.y)
|
|
||||||
|
|
||||||
let dt: CGFloat = 1.0 / 60.0
|
|
||||||
var removeIndices: [Int] = []
|
|
||||||
for i in 0 ..< self.particles.count {
|
|
||||||
let currentTime = timestamp - self.particles[i].beginTime
|
|
||||||
if currentTime > self.particles[i].lifetime {
|
|
||||||
removeIndices.append(i)
|
|
||||||
} else {
|
|
||||||
let input: CGFloat = CGFloat(currentTime / self.particles[i].lifetime)
|
|
||||||
let decelerated: CGFloat = (1.0 - (1.0 - input) * (1.0 - input))
|
|
||||||
self.particles[i].alpha = 1.0 - decelerated
|
|
||||||
|
|
||||||
var p = self.particles[i].position
|
|
||||||
let d = self.particles[i].direction
|
|
||||||
let v = self.particles[i].velocity
|
|
||||||
p = CGPoint(x: p.x + d.x * v * dt, y: p.y + d.y * v * dt)
|
|
||||||
self.particles[i].position = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in removeIndices.reversed() {
|
|
||||||
self.particles.remove(at: i)
|
|
||||||
}
|
|
||||||
|
|
||||||
let newParticleCount = 1
|
|
||||||
for _ in 0 ..< newParticleCount {
|
|
||||||
let degrees: CGFloat = CGFloat(arc4random_uniform(140)) - 40.0
|
|
||||||
let angle: CGFloat = degrees * CGFloat.pi / 180.0
|
|
||||||
|
|
||||||
let direction = CGPoint(x: v.x * cos(angle) - v.y * sin(angle), y: v.x * sin(angle) + v.y * cos(angle))
|
|
||||||
let velocity = (20.0 + (CGFloat(arc4random()) / CGFloat(UINT32_MAX)) * 4.0) * 0.3
|
|
||||||
|
|
||||||
let lifetime = Double(0.4 + CGFloat(arc4random_uniform(100)) * 0.01)
|
|
||||||
|
|
||||||
let particle = ContentParticle(position: c, direction: direction, velocity: velocity, alpha: 1.0, lifetime: lifetime, beginTime: timestamp)
|
|
||||||
self.particles.append(particle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
image = generateImage(CGSize(width: diameter + inset, height: diameter + inset), rotatedContext: { size, context in
|
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
||||||
context.setStrokeColor(color.cgColor)
|
|
||||||
context.setFillColor(color.cgColor)
|
|
||||||
context.setLineWidth(lineWidth)
|
|
||||||
context.setLineCap(.round)
|
|
||||||
|
|
||||||
let path = CGMutablePath()
|
|
||||||
path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
|
|
||||||
context.addPath(path)
|
|
||||||
context.strokePath()
|
|
||||||
|
|
||||||
if sparks {
|
|
||||||
for particle in self.particles {
|
|
||||||
let size: CGFloat = 2.0
|
|
||||||
context.setAlpha(particle.alpha)
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: particle.position.x - size / 2.0, y: particle.position.y - size / 2.0), size: CGSize(width: size, height: size)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.contentNode.contents = image?.cgImage
|
|
||||||
if let image = image {
|
|
||||||
self.contentNode.frame = CGRect(origin: CGPoint(), size: image.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fraction <= .ulpOfOne {
|
|
||||||
self.animator?.invalidate()
|
|
||||||
self.animator = nil
|
|
||||||
} else {
|
|
||||||
if self.animator == nil {
|
|
||||||
let animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
|
||||||
self?.updateValues()
|
|
||||||
})
|
|
||||||
self.animator = animator
|
|
||||||
animator.isPaused = self.inHierarchyValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,267 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import SwiftSignalKit
|
|
||||||
import SyncCore
|
|
||||||
import TelegramPresentationData
|
|
||||||
import ItemListUI
|
|
||||||
|
|
||||||
public class ItemListInviteLinkGridItem: ListViewItem, ItemListItem {
|
|
||||||
let presentationData: ItemListPresentationData
|
|
||||||
let invites: [ExportedInvitation]?
|
|
||||||
let count: Int
|
|
||||||
let share: Bool
|
|
||||||
public let sectionId: ItemListSectionId
|
|
||||||
let style: ItemListStyle
|
|
||||||
let tapAction: ((ExportedInvitation) -> Void)?
|
|
||||||
let contextAction: ((ExportedInvitation, ASDisplayNode) -> Void)?
|
|
||||||
public let tag: ItemListItemTag?
|
|
||||||
|
|
||||||
public init(
|
|
||||||
presentationData: ItemListPresentationData,
|
|
||||||
invites: [ExportedInvitation]?,
|
|
||||||
count: Int,
|
|
||||||
share: Bool,
|
|
||||||
sectionId: ItemListSectionId,
|
|
||||||
style: ItemListStyle,
|
|
||||||
tapAction: ((ExportedInvitation) -> Void)?,
|
|
||||||
contextAction: ((ExportedInvitation, ASDisplayNode) -> Void)?,
|
|
||||||
tag: ItemListItemTag? = nil
|
|
||||||
) {
|
|
||||||
self.presentationData = presentationData
|
|
||||||
self.invites = invites
|
|
||||||
self.count = count
|
|
||||||
self.share = share
|
|
||||||
self.sectionId = sectionId
|
|
||||||
self.style = style
|
|
||||||
self.tapAction = tapAction
|
|
||||||
self.contextAction = contextAction
|
|
||||||
self.tag = tag
|
|
||||||
}
|
|
||||||
|
|
||||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
|
||||||
async {
|
|
||||||
let node = ItemListInviteLinkGridItemNode()
|
|
||||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
|
||||||
|
|
||||||
node.contentSize = layout.contentSize
|
|
||||||
node.insets = layout.insets
|
|
||||||
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
completion(node, {
|
|
||||||
return (nil, { _ in apply() })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
if let nodeValue = node() as? ItemListInviteLinkGridItemNode {
|
|
||||||
let makeLayout = nodeValue.asyncLayout()
|
|
||||||
|
|
||||||
async {
|
|
||||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
completion(layout, { _ in
|
|
||||||
apply()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var selectable: Bool = false
|
|
||||||
|
|
||||||
public func selected(listView: ListView){
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode {
|
|
||||||
private let backgroundNode: ASDisplayNode
|
|
||||||
private let topStripeNode: ASDisplayNode
|
|
||||||
private let bottomStripeNode: ASDisplayNode
|
|
||||||
private let maskNode: ASImageNode
|
|
||||||
|
|
||||||
private let gridNode: InviteLinksGridNode
|
|
||||||
|
|
||||||
private var item: ItemListInviteLinkGridItem?
|
|
||||||
|
|
||||||
override public var canBeSelected: Bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
public var tag: ItemListItemTag? {
|
|
||||||
return self.item?.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
public init() {
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
|
||||||
self.backgroundNode.isLayerBacked = true
|
|
||||||
self.backgroundNode.backgroundColor = .white
|
|
||||||
|
|
||||||
self.maskNode = ASImageNode()
|
|
||||||
|
|
||||||
self.topStripeNode = ASDisplayNode()
|
|
||||||
self.topStripeNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.bottomStripeNode = ASDisplayNode()
|
|
||||||
self.bottomStripeNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.gridNode = InviteLinksGridNode()
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
|
||||||
|
|
||||||
self.addSubnode(self.gridNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func asyncLayout() -> (_ item: ItemListInviteLinkGridItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
|
||||||
let currentItem = self.item
|
|
||||||
|
|
||||||
return { item, params, neighbors in
|
|
||||||
var updatedTheme: PresentationTheme?
|
|
||||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
|
||||||
updatedTheme = item.presentationData.theme
|
|
||||||
}
|
|
||||||
|
|
||||||
let contentSize: CGSize
|
|
||||||
let insets: UIEdgeInsets
|
|
||||||
let separatorHeight = UIScreenPixel
|
|
||||||
let itemBackgroundColor: UIColor
|
|
||||||
let itemSeparatorColor: UIColor
|
|
||||||
|
|
||||||
let leftInset = 16.0 + params.leftInset
|
|
||||||
let topInset: CGFloat
|
|
||||||
if case .plain = item.style, case .otherSection = neighbors.top {
|
|
||||||
topInset = 16.0
|
|
||||||
} else if case .blocks = item.style, case .sameSection(true) = neighbors.top {
|
|
||||||
topInset = 16.0
|
|
||||||
} else {
|
|
||||||
topInset = 4.0
|
|
||||||
}
|
|
||||||
|
|
||||||
var height: CGFloat
|
|
||||||
let count = item.invites?.count ?? item.count
|
|
||||||
if count > 0 {
|
|
||||||
if count % 2 == 0 {
|
|
||||||
height = topInset + 122.0 + 6.0
|
|
||||||
} else {
|
|
||||||
height = topInset + 102.0 + 6.0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
height = 0.001
|
|
||||||
}
|
|
||||||
|
|
||||||
switch item.style {
|
|
||||||
case .plain:
|
|
||||||
itemBackgroundColor = item.presentationData.theme.list.blocksBackgroundColor
|
|
||||||
itemSeparatorColor = item.presentationData.theme.list.blocksBackgroundColor
|
|
||||||
insets = UIEdgeInsets()
|
|
||||||
case .blocks:
|
|
||||||
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
|
||||||
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
|
||||||
insets = itemListNeighborsGroupedInsets(neighbors)
|
|
||||||
}
|
|
||||||
if case .sameSection(false) = neighbors.bottom {
|
|
||||||
} else {
|
|
||||||
height += 10.0
|
|
||||||
}
|
|
||||||
contentSize = CGSize(width: params.width, height: height)
|
|
||||||
|
|
||||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.item = item
|
|
||||||
|
|
||||||
if let _ = updatedTheme {
|
|
||||||
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
|
||||||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
|
||||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
|
||||||
}
|
|
||||||
|
|
||||||
let gridSize = strongSelf.gridNode.update(size: contentSize, safeInset: params.leftInset, items: item.invites, count: item.count, share: item.share, presentationData: item.presentationData, transition: .immediate)
|
|
||||||
strongSelf.gridNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset - 4.0), size: gridSize)
|
|
||||||
strongSelf.gridNode.action = { invite in
|
|
||||||
item.tapAction?(invite)
|
|
||||||
}
|
|
||||||
strongSelf.gridNode.contextAction = { node, invite in
|
|
||||||
item.contextAction?(invite, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch item.style {
|
|
||||||
case .plain:
|
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
|
||||||
}
|
|
||||||
if strongSelf.topStripeNode.supernode != nil {
|
|
||||||
strongSelf.topStripeNode.removeFromSupernode()
|
|
||||||
}
|
|
||||||
if strongSelf.bottomStripeNode.supernode != nil {
|
|
||||||
strongSelf.bottomStripeNode.removeFromSupernode()
|
|
||||||
}
|
|
||||||
if strongSelf.maskNode.supernode != nil {
|
|
||||||
strongSelf.maskNode.removeFromSupernode()
|
|
||||||
}
|
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: contentSize.height))
|
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
|
|
||||||
case .blocks:
|
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
|
||||||
}
|
|
||||||
if strongSelf.topStripeNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
|
||||||
}
|
|
||||||
if strongSelf.bottomStripeNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
|
||||||
}
|
|
||||||
if strongSelf.maskNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
|
||||||
var hasTopCorners = false
|
|
||||||
var hasBottomCorners = false
|
|
||||||
switch neighbors.top {
|
|
||||||
case .sameSection(false):
|
|
||||||
strongSelf.topStripeNode.isHidden = true
|
|
||||||
default:
|
|
||||||
hasTopCorners = true
|
|
||||||
strongSelf.topStripeNode.isHidden = hasCorners
|
|
||||||
}
|
|
||||||
let bottomStripeInset: CGFloat
|
|
||||||
switch neighbors.bottom {
|
|
||||||
case .sameSection(false):
|
|
||||||
bottomStripeInset = leftInset
|
|
||||||
strongSelf.bottomStripeNode.isHidden = true
|
|
||||||
default:
|
|
||||||
bottomStripeInset = 0.0
|
|
||||||
hasBottomCorners = true
|
|
||||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
|
||||||
|
|
||||||
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)))
|
|
||||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
|
||||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
|
||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
|
||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
|
||||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -363,6 +363,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
if let expireDate = invite.expireDate, currentTime >= expireDate {
|
if let expireDate = invite.expireDate, currentTime >= expireDate {
|
||||||
isExpired = true
|
isExpired = true
|
||||||
}
|
}
|
||||||
|
var isFull = false
|
||||||
|
|
||||||
if let usageLimit = invite.usageLimit {
|
if let usageLimit = invite.usageLimit {
|
||||||
if !isExpired {
|
if !isExpired {
|
||||||
@ -376,6 +377,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
timerValue = .fraction(fraction)
|
timerValue = .fraction(fraction)
|
||||||
}
|
}
|
||||||
} else if remaining == 0 {
|
} else if remaining == 0 {
|
||||||
|
isFull = true
|
||||||
if !subtitleText.isEmpty {
|
if !subtitleText.isEmpty {
|
||||||
subtitleText += " • "
|
subtitleText += " • "
|
||||||
}
|
}
|
||||||
@ -383,7 +385,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let expireDate = invite.expireDate {
|
if let expireDate = invite.expireDate, !isFull {
|
||||||
if !isExpired {
|
if !isExpired {
|
||||||
if !subtitleText.isEmpty {
|
if !subtitleText.isEmpty {
|
||||||
subtitleText += " • "
|
subtitleText += " • "
|
||||||
@ -570,7 +572,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
timerNode = TimerNode()
|
timerNode = TimerNode()
|
||||||
timerNode.isUserInteractionEnabled = false
|
timerNode.isUserInteractionEnabled = false
|
||||||
strongSelf.timerNode = timerNode
|
strongSelf.timerNode = timerNode
|
||||||
strongSelf.addSubnode(timerNode)
|
strongSelf.offsetContainerNode.addSubnode(timerNode)
|
||||||
}
|
}
|
||||||
timerNode.update(color: iconColor, value: timerValue)
|
timerNode.update(color: iconColor, value: timerValue)
|
||||||
} else if let timerNode = strongSelf.timerNode {
|
} else if let timerNode = strongSelf.timerNode {
|
||||||
@ -785,7 +787,7 @@ private final class TimerNode: ASDisplayNode {
|
|||||||
let startAngle: CGFloat = -CGFloat.pi / 2.0
|
let startAngle: CGFloat = -CGFloat.pi / 2.0
|
||||||
let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * fraction
|
let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * fraction
|
||||||
|
|
||||||
let sparks = fraction > 0.1 && fraction != 1.0
|
let sparks = fraction > 0.05 && fraction != 1.0
|
||||||
if sparks {
|
if sparks {
|
||||||
let v = CGPoint(x: sin(endAngle), y: -cos(endAngle))
|
let v = CGPoint(x: sin(endAngle), y: -cos(endAngle))
|
||||||
let c = CGPoint(x: -v.y * radius + center.x, y: v.x * radius + center.y)
|
let c = CGPoint(x: -v.y * radius + center.x, y: v.x * radius + center.y)
|
||||||
|
@ -287,25 +287,6 @@ private func ChannelMembersControllerEntries(context: AccountContext, presentati
|
|||||||
|
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
let sortedParticipants = participants
|
let sortedParticipants = participants
|
||||||
/*
|
|
||||||
participants.sorted(by: { lhs, rhs in
|
|
||||||
let lhsInvitedAt: Int32
|
|
||||||
switch lhs.participant {
|
|
||||||
case .creator:
|
|
||||||
lhsInvitedAt = Int32.min
|
|
||||||
case let .member(_, invitedAt, _, _):
|
|
||||||
lhsInvitedAt = invitedAt
|
|
||||||
}
|
|
||||||
let rhsInvitedAt: Int32
|
|
||||||
switch rhs.participant {
|
|
||||||
case .creator:
|
|
||||||
rhsInvitedAt = Int32.min
|
|
||||||
case let .member(_, invitedAt, _, _):
|
|
||||||
rhsInvitedAt = invitedAt
|
|
||||||
}
|
|
||||||
return lhsInvitedAt < rhsInvitedAt
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
for participant in sortedParticipants {
|
for participant in sortedParticipants {
|
||||||
var editable = true
|
var editable = true
|
||||||
var canEditMembers = false
|
var canEditMembers = false
|
||||||
|
@ -968,8 +968,17 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
|||||||
return nil
|
return nil
|
||||||
} |> deliverOnMainQueue).start(next: { invite in
|
} |> deliverOnMainQueue).start(next: { invite in
|
||||||
if let invite = invite {
|
if let invite = invite {
|
||||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||||
presentControllerImpl?(controller, nil)
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
let isGroup: Bool
|
||||||
|
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||||
|
isGroup = false
|
||||||
|
} else {
|
||||||
|
isGroup = true
|
||||||
|
}
|
||||||
|
let controller = InviteLinkQRCodeController(context: context, invite: invite, isGroup: isGroup)
|
||||||
|
presentControllerImpl?(controller, nil)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
@ -78,7 +78,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
|||||||
if let reportReason = reportReason {
|
if let reportReason = reportReason {
|
||||||
switch subject {
|
switch subject {
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason)
|
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "")
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||||
parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
|
parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||||
@ -86,7 +86,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
|||||||
completion(reportReason, true)
|
completion(reportReason, true)
|
||||||
})
|
})
|
||||||
case let .messages(messageIds):
|
case let .messages(messageIds):
|
||||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason)
|
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "")
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||||
parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
|
parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||||
@ -155,12 +155,16 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if let reportReason = reportReason {
|
if let reportReason = reportReason {
|
||||||
|
var passthrough = passthrough
|
||||||
|
if case .fake = reportReason {
|
||||||
|
passthrough = false
|
||||||
|
}
|
||||||
switch subject {
|
switch subject {
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
if passthrough {
|
if passthrough {
|
||||||
completion(reportReason, true)
|
completion(reportReason, true)
|
||||||
} else {
|
} else {
|
||||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason)
|
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "")
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||||
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil)
|
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil)
|
||||||
@ -172,7 +176,7 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
|||||||
if passthrough {
|
if passthrough {
|
||||||
completion(reportReason, true)
|
completion(reportReason, true)
|
||||||
} else {
|
} else {
|
||||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason)
|
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "")
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||||
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil)
|
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil)
|
||||||
@ -326,7 +330,7 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !text.isEmpty {
|
if !text.isEmpty {
|
||||||
let reportReason: ReportReason = .custom(text)
|
let reportReason: ReportReason = .custom
|
||||||
let completed: () -> Void = {
|
let completed: () -> Void = {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
@ -335,12 +339,12 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
|
|||||||
}
|
}
|
||||||
switch subject {
|
switch subject {
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: reportReason)
|
reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: text)
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
completed()
|
completed()
|
||||||
}))
|
}))
|
||||||
case let .messages(messageIds):
|
case let .messages(messageIds):
|
||||||
reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason)
|
reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: text)
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
completed()
|
completed()
|
||||||
}))
|
}))
|
||||||
|
@ -1017,7 +1017,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
|||||||
let _ = removePeerChat(account: context.account, peerId: peerId, reportChatSpam: reportSpam).start()
|
let _ = removePeerChat(account: context.account, peerId: peerId, reportChatSpam: reportSpam).start()
|
||||||
popToRootImpl?()
|
popToRootImpl?()
|
||||||
} else if reportSpam {
|
} else if reportSpam {
|
||||||
let _ = reportPeer(account: context.account, peerId: peerId, reason: .spam).start()
|
let _ = reportPeer(account: context.account, peerId: peerId, reason: .spam, message: "").start()
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteSendMessageIntents(peerId: peerId)
|
deleteSendMessageIntents(peerId: peerId)
|
||||||
|
@ -117,8 +117,14 @@ private class SectionHeaderItemNode: ListViewItemNode {
|
|||||||
strongSelf.headerNode = headerNode
|
strongSelf.headerNode = headerNode
|
||||||
}
|
}
|
||||||
headerNode.title = item.title
|
headerNode.title = item.title
|
||||||
|
switch item.additionalText {
|
||||||
|
case .none, .generic:
|
||||||
|
headerNode.actionType = .generic
|
||||||
|
case .destructive:
|
||||||
|
headerNode.actionType = .destructive
|
||||||
|
|
||||||
|
}
|
||||||
headerNode.action = item.additionalText.text
|
headerNode.action = item.additionalText.text
|
||||||
|
|
||||||
headerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
|
headerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
|
||||||
headerNode.updateLayout(size: contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
headerNode.updateLayout(size: contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
|
|||||||
private let clearButton: HighlightableButtonNode
|
private let clearButton: HighlightableButtonNode
|
||||||
|
|
||||||
public var updateHeight: (() -> Void)?
|
public var updateHeight: (() -> Void)?
|
||||||
|
public var updateText: ((String) -> Void)?
|
||||||
|
|
||||||
private let backgroundInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 1.0, right: 16.0)
|
private let backgroundInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 1.0, right: 16.0)
|
||||||
private let inputInsets = UIEdgeInsets(top: 10.0, left: 8.0, bottom: 10.0, right: 16.0)
|
private let inputInsets = UIEdgeInsets(top: 10.0, left: 8.0, bottom: 10.0, right: 16.0)
|
||||||
@ -168,6 +169,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
|
|||||||
|
|
||||||
@objc public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
@objc public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||||
self.updateTextNodeText(animated: true)
|
self.updateTextNodeText(animated: true)
|
||||||
|
self.updateText?(editableTextNode.attributedText?.string ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
||||||
|
@ -144,6 +144,7 @@ public struct TelegramChannelFlags: OptionSet {
|
|||||||
public static let hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4)
|
public static let hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4)
|
||||||
public static let hasActiveVoiceChat = TelegramChannelFlags(rawValue: 1 << 5)
|
public static let hasActiveVoiceChat = TelegramChannelFlags(rawValue: 1 << 5)
|
||||||
public static let isFake = TelegramChannelFlags(rawValue: 1 << 6)
|
public static let isFake = TelegramChannelFlags(rawValue: 1 << 6)
|
||||||
|
public static let isGigagroup = TelegramChannelFlags(rawValue: 1 << 7)
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class TelegramChannel: Peer {
|
public final class TelegramChannel: Peer {
|
||||||
|
@ -371,7 +371,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) }
|
dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) }
|
||||||
dict[-1798033689] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilterEmpty($0) }
|
dict[-1798033689] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilterEmpty($0) }
|
||||||
dict[-847783593] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilter($0) }
|
dict[-847783593] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilter($0) }
|
||||||
dict[-559275508] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($0) }
|
dict[-539872497] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($0) }
|
||||||
dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) }
|
dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) }
|
||||||
dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) }
|
dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) }
|
||||||
dict[-1803769784] = { return Api.messages.BotResults.parse_botResults($0) }
|
dict[-1803769784] = { return Api.messages.BotResults.parse_botResults($0) }
|
||||||
@ -483,11 +483,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1490799288] = { return Api.ReportReason.parse_inputReportReasonSpam($0) }
|
dict[1490799288] = { return Api.ReportReason.parse_inputReportReasonSpam($0) }
|
||||||
dict[505595789] = { return Api.ReportReason.parse_inputReportReasonViolence($0) }
|
dict[505595789] = { return Api.ReportReason.parse_inputReportReasonViolence($0) }
|
||||||
dict[777640226] = { return Api.ReportReason.parse_inputReportReasonPornography($0) }
|
dict[777640226] = { return Api.ReportReason.parse_inputReportReasonPornography($0) }
|
||||||
dict[-512463606] = { return Api.ReportReason.parse_inputReportReasonOther($0) }
|
|
||||||
dict[-1685456582] = { return Api.ReportReason.parse_inputReportReasonCopyright($0) }
|
dict[-1685456582] = { return Api.ReportReason.parse_inputReportReasonCopyright($0) }
|
||||||
dict[-1376497949] = { return Api.ReportReason.parse_inputReportReasonChildAbuse($0) }
|
dict[-1376497949] = { return Api.ReportReason.parse_inputReportReasonChildAbuse($0) }
|
||||||
dict[-606798099] = { return Api.ReportReason.parse_inputReportReasonGeoIrrelevant($0) }
|
dict[-606798099] = { return Api.ReportReason.parse_inputReportReasonGeoIrrelevant($0) }
|
||||||
dict[-170010905] = { return Api.ReportReason.parse_inputReportReasonFake($0) }
|
dict[-170010905] = { return Api.ReportReason.parse_inputReportReasonFake($0) }
|
||||||
|
dict[-1041980751] = { return Api.ReportReason.parse_inputReportReasonOther($0) }
|
||||||
dict[-247351839] = { return Api.InputEncryptedChat.parse_inputEncryptedChat($0) }
|
dict[-247351839] = { return Api.InputEncryptedChat.parse_inputEncryptedChat($0) }
|
||||||
dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) }
|
dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) }
|
||||||
dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) }
|
dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) }
|
||||||
|
@ -9610,24 +9610,25 @@ public extension Api {
|
|||||||
|
|
||||||
}
|
}
|
||||||
public enum ChatAdminWithInvites: TypeConstructorDescription {
|
public enum ChatAdminWithInvites: TypeConstructorDescription {
|
||||||
case chatAdminWithInvites(adminId: Int32, invitesCount: Int32)
|
case chatAdminWithInvites(adminId: Int32, invitesCount: Int32, revokedInvitesCount: Int32)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .chatAdminWithInvites(let adminId, let invitesCount):
|
case .chatAdminWithInvites(let adminId, let invitesCount, let revokedInvitesCount):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-559275508)
|
buffer.appendInt32(-539872497)
|
||||||
}
|
}
|
||||||
serializeInt32(adminId, buffer: buffer, boxed: false)
|
serializeInt32(adminId, buffer: buffer, boxed: false)
|
||||||
serializeInt32(invitesCount, buffer: buffer, boxed: false)
|
serializeInt32(invitesCount, buffer: buffer, boxed: false)
|
||||||
|
serializeInt32(revokedInvitesCount, buffer: buffer, boxed: false)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .chatAdminWithInvites(let adminId, let invitesCount):
|
case .chatAdminWithInvites(let adminId, let invitesCount, let revokedInvitesCount):
|
||||||
return ("chatAdminWithInvites", [("adminId", adminId), ("invitesCount", invitesCount)])
|
return ("chatAdminWithInvites", [("adminId", adminId), ("invitesCount", invitesCount), ("revokedInvitesCount", revokedInvitesCount)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9636,10 +9637,13 @@ public extension Api {
|
|||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
var _2: Int32?
|
var _2: Int32?
|
||||||
_2 = reader.readInt32()
|
_2 = reader.readInt32()
|
||||||
|
var _3: Int32?
|
||||||
|
_3 = reader.readInt32()
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
if _c1 && _c2 {
|
let _c3 = _3 != nil
|
||||||
return Api.ChatAdminWithInvites.chatAdminWithInvites(adminId: _1!, invitesCount: _2!)
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.ChatAdminWithInvites.chatAdminWithInvites(adminId: _1!, invitesCount: _2!, revokedInvitesCount: _3!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
@ -12149,11 +12153,11 @@ public extension Api {
|
|||||||
case inputReportReasonSpam
|
case inputReportReasonSpam
|
||||||
case inputReportReasonViolence
|
case inputReportReasonViolence
|
||||||
case inputReportReasonPornography
|
case inputReportReasonPornography
|
||||||
case inputReportReasonOther(text: String)
|
|
||||||
case inputReportReasonCopyright
|
case inputReportReasonCopyright
|
||||||
case inputReportReasonChildAbuse
|
case inputReportReasonChildAbuse
|
||||||
case inputReportReasonGeoIrrelevant
|
case inputReportReasonGeoIrrelevant
|
||||||
case inputReportReasonFake
|
case inputReportReasonFake
|
||||||
|
case inputReportReasonOther
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -12174,12 +12178,6 @@ public extension Api {
|
|||||||
buffer.appendInt32(777640226)
|
buffer.appendInt32(777640226)
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
|
||||||
case .inputReportReasonOther(let text):
|
|
||||||
if boxed {
|
|
||||||
buffer.appendInt32(-512463606)
|
|
||||||
}
|
|
||||||
serializeString(text, buffer: buffer, boxed: false)
|
|
||||||
break
|
break
|
||||||
case .inputReportReasonCopyright:
|
case .inputReportReasonCopyright:
|
||||||
if boxed {
|
if boxed {
|
||||||
@ -12204,6 +12202,12 @@ public extension Api {
|
|||||||
buffer.appendInt32(-170010905)
|
buffer.appendInt32(-170010905)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
case .inputReportReasonOther:
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1041980751)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -12216,8 +12220,6 @@ public extension Api {
|
|||||||
return ("inputReportReasonViolence", [])
|
return ("inputReportReasonViolence", [])
|
||||||
case .inputReportReasonPornography:
|
case .inputReportReasonPornography:
|
||||||
return ("inputReportReasonPornography", [])
|
return ("inputReportReasonPornography", [])
|
||||||
case .inputReportReasonOther(let text):
|
|
||||||
return ("inputReportReasonOther", [("text", text)])
|
|
||||||
case .inputReportReasonCopyright:
|
case .inputReportReasonCopyright:
|
||||||
return ("inputReportReasonCopyright", [])
|
return ("inputReportReasonCopyright", [])
|
||||||
case .inputReportReasonChildAbuse:
|
case .inputReportReasonChildAbuse:
|
||||||
@ -12226,6 +12228,8 @@ public extension Api {
|
|||||||
return ("inputReportReasonGeoIrrelevant", [])
|
return ("inputReportReasonGeoIrrelevant", [])
|
||||||
case .inputReportReasonFake:
|
case .inputReportReasonFake:
|
||||||
return ("inputReportReasonFake", [])
|
return ("inputReportReasonFake", [])
|
||||||
|
case .inputReportReasonOther:
|
||||||
|
return ("inputReportReasonOther", [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12238,17 +12242,6 @@ public extension Api {
|
|||||||
public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? {
|
public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? {
|
||||||
return Api.ReportReason.inputReportReasonPornography
|
return Api.ReportReason.inputReportReasonPornography
|
||||||
}
|
}
|
||||||
public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? {
|
|
||||||
var _1: String?
|
|
||||||
_1 = parseString(reader)
|
|
||||||
let _c1 = _1 != nil
|
|
||||||
if _c1 {
|
|
||||||
return Api.ReportReason.inputReportReasonOther(text: _1!)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static func parse_inputReportReasonCopyright(_ reader: BufferReader) -> ReportReason? {
|
public static func parse_inputReportReasonCopyright(_ reader: BufferReader) -> ReportReason? {
|
||||||
return Api.ReportReason.inputReportReasonCopyright
|
return Api.ReportReason.inputReportReasonCopyright
|
||||||
}
|
}
|
||||||
@ -12261,6 +12254,9 @@ public extension Api {
|
|||||||
public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? {
|
public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? {
|
||||||
return Api.ReportReason.inputReportReasonFake
|
return Api.ReportReason.inputReportReasonFake
|
||||||
}
|
}
|
||||||
|
public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? {
|
||||||
|
return Api.ReportReason.inputReportReasonOther
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public enum InputEncryptedChat: TypeConstructorDescription {
|
public enum InputEncryptedChat: TypeConstructorDescription {
|
||||||
|
@ -2927,26 +2927,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func report(peer: Api.InputPeer, id: [Int32], reason: Api.ReportReason) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(-1115507112)
|
|
||||||
peer.serialize(buffer, true)
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(id.count))
|
|
||||||
for item in id {
|
|
||||||
serializeInt32(item, buffer: buffer, boxed: false)
|
|
||||||
}
|
|
||||||
reason.serialize(buffer, true)
|
|
||||||
return (FunctionDescription(name: "messages.report", parameters: [("peer", peer), ("id", id), ("reason", reason)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.Bool?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func getRecentLocations(peer: Api.InputPeer, limit: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
|
public static func getRecentLocations(peer: Api.InputPeer, limit: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-1144759543)
|
buffer.appendInt32(-1144759543)
|
||||||
@ -4126,6 +4106,27 @@ public extension Api {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func report(peer: Api.InputPeer, id: [Int32], reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-1991005362)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(id.count))
|
||||||
|
for item in id {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
reason.serialize(buffer, true)
|
||||||
|
serializeString(message, buffer: buffer, boxed: false)
|
||||||
|
return (FunctionDescription(name: "messages.report", parameters: [("peer", peer), ("id", id), ("reason", reason), ("message", message)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Bool?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public struct channels {
|
public struct channels {
|
||||||
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
@ -6204,21 +6205,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func reportPeer(peer: Api.InputPeer, reason: Api.ReportReason) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(-1374118561)
|
|
||||||
peer.serialize(buffer, true)
|
|
||||||
reason.serialize(buffer, true)
|
|
||||||
return (FunctionDescription(name: "account.reportPeer", parameters: [("peer", peer), ("reason", reason)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.Bool?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
public static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(655677548)
|
buffer.appendInt32(655677548)
|
||||||
@ -7125,6 +7111,39 @@ public extension Api {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func reportPeer(peer: Api.InputPeer, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-977650298)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
reason.serialize(buffer, true)
|
||||||
|
serializeString(message, buffer: buffer, boxed: false)
|
||||||
|
return (FunctionDescription(name: "account.reportPeer", parameters: [("peer", peer), ("reason", reason), ("message", message)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Bool?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func reportProfilePhoto(peer: Api.InputPeer, photoId: Api.InputPhoto, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-91437323)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
photoId.serialize(buffer, true)
|
||||||
|
reason.serialize(buffer, true)
|
||||||
|
serializeString(message, buffer: buffer, boxed: false)
|
||||||
|
return (FunctionDescription(name: "account.reportProfilePhoto", parameters: [("peer", peer), ("photoId", photoId), ("reason", reason), ("message", message)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Bool?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public struct wallet {
|
public struct wallet {
|
||||||
public static func sendLiteRequest(body: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.wallet.LiteResponse>) {
|
public static func sendLiteRequest(body: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.wallet.LiteResponse>) {
|
||||||
|
@ -108,6 +108,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
|
|||||||
if (flags & Int32(1 << 25)) != 0 {
|
if (flags & Int32(1 << 25)) != 0 {
|
||||||
channelFlags.insert(.isFake)
|
channelFlags.insert(.isFake)
|
||||||
}
|
}
|
||||||
|
if (flags & Int32(1 << 25)) != 0 {
|
||||||
|
channelFlags.insert(.isGigagroup)
|
||||||
|
}
|
||||||
|
|
||||||
let restrictionInfo: PeerAccessRestrictionInfo?
|
let restrictionInfo: PeerAccessRestrictionInfo?
|
||||||
if let restrictionReason = restrictionReason {
|
if let restrictionReason = restrictionReason {
|
||||||
|
@ -62,6 +62,7 @@ public enum AdminLogEventAction {
|
|||||||
case deleteExportedInvitation(ExportedInvitation)
|
case deleteExportedInvitation(ExportedInvitation)
|
||||||
case revokeExportedInvitation(ExportedInvitation)
|
case revokeExportedInvitation(ExportedInvitation)
|
||||||
case editExportedInvitation(previous: ExportedInvitation, updated: ExportedInvitation)
|
case editExportedInvitation(previous: ExportedInvitation, updated: ExportedInvitation)
|
||||||
|
case participantJoinedViaInvite(ExportedInvitation)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ChannelAdminLogEventError {
|
public enum ChannelAdminLogEventError {
|
||||||
@ -241,11 +242,11 @@ public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: Pe
|
|||||||
action = .revokeExportedInvitation(ExportedInvitation(apiExportedInvite: invite))
|
action = .revokeExportedInvitation(ExportedInvitation(apiExportedInvite: invite))
|
||||||
case let .channelAdminLogEventActionExportedInviteEdit(prevInvite, newInvite):
|
case let .channelAdminLogEventActionExportedInviteEdit(prevInvite, newInvite):
|
||||||
action = .editExportedInvitation(previous: ExportedInvitation(apiExportedInvite: prevInvite), updated: ExportedInvitation(apiExportedInvite: newInvite))
|
action = .editExportedInvitation(previous: ExportedInvitation(apiExportedInvite: prevInvite), updated: ExportedInvitation(apiExportedInvite: newInvite))
|
||||||
|
case let .channelAdminLogEventActionParticipantJoinByInvite(invite):
|
||||||
|
action = .participantJoinedViaInvite(ExportedInvitation(apiExportedInvite: invite))
|
||||||
case let .channelAdminLogEventActionParticipantVolume(participant):
|
case let .channelAdminLogEventActionParticipantVolume(participant):
|
||||||
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
|
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
|
||||||
action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000)
|
action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000)
|
||||||
case let .channelAdminLogEventActionParticipantJoinByInvite(invite):
|
|
||||||
action = nil
|
|
||||||
}
|
}
|
||||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||||
if let action = action {
|
if let action = action {
|
||||||
|
@ -808,6 +808,7 @@ public final class PeerInvitationImportersContext {
|
|||||||
public struct ExportedInvitationCreator : Equatable {
|
public struct ExportedInvitationCreator : Equatable {
|
||||||
public let peer: RenderedPeer
|
public let peer: RenderedPeer
|
||||||
public let count: Int32
|
public let count: Int32
|
||||||
|
public let revokedCount: Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peerExportedInvitationsCreators(account: Account, peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> {
|
public func peerExportedInvitationsCreators(account: Account, peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> {
|
||||||
@ -839,10 +840,10 @@ public func peerExportedInvitationsCreators(account: Account, peerId: PeerId) ->
|
|||||||
peersMap[telegramUser.id] = telegramUser
|
peersMap[telegramUser.id] = telegramUser
|
||||||
}
|
}
|
||||||
|
|
||||||
for case let .chatAdminWithInvites(adminId, invitesCount) in admins {
|
for case let .chatAdminWithInvites(adminId, invitesCount, revokedInvitesCount) in admins {
|
||||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId)
|
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId)
|
||||||
if let peer = peersMap[peerId], peerId != account.peerId {
|
if let peer = peersMap[peerId], peerId != account.peerId {
|
||||||
creators.append(ExportedInvitationCreator(peer: RenderedPeer(peer: peer), count: invitesCount))
|
creators.append(ExportedInvitationCreator(peer: RenderedPeer(peer: peer), count: invitesCount, revokedCount: revokedInvitesCount))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ public enum ReportReason: Equatable {
|
|||||||
case childAbuse
|
case childAbuse
|
||||||
case copyright
|
case copyright
|
||||||
case irrelevantLocation
|
case irrelevantLocation
|
||||||
case custom(String)
|
case custom
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ReportReason {
|
private extension ReportReason {
|
||||||
@ -104,16 +104,16 @@ private extension ReportReason {
|
|||||||
return .inputReportReasonCopyright
|
return .inputReportReasonCopyright
|
||||||
case .irrelevantLocation:
|
case .irrelevantLocation:
|
||||||
return .inputReportReasonGeoIrrelevant
|
return .inputReportReasonGeoIrrelevant
|
||||||
case let .custom(text):
|
case let .custom:
|
||||||
return .inputReportReasonOther(text: text)
|
return .inputReportReasonOther
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reportPeer(account: Account, peerId: PeerId, reason: ReportReason) -> Signal<Void, NoError> {
|
public func reportPeer(account: Account, peerId: PeerId, reason: ReportReason, message: String) -> Signal<Void, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
return account.network.request(Api.functions.account.reportPeer(peer: inputPeer, reason: reason.apiReason))
|
return account.network.request(Api.functions.account.reportPeer(peer: inputPeer, reason: reason.apiReason, message: message))
|
||||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
return .single(.boolFalse)
|
return .single(.boolFalse)
|
||||||
}
|
}
|
||||||
@ -126,14 +126,14 @@ public func reportPeer(account: Account, peerId: PeerId, reason: ReportReason) -
|
|||||||
} |> switchToLatest
|
} |> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reportPeerMessages(account: Account, messageIds: [MessageId], reason: ReportReason) -> Signal<Void, NoError> {
|
public func reportPeerMessages(account: Account, messageIds: [MessageId], reason: ReportReason, message: String) -> Signal<Void, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
let groupedIds = messagesIdsGroupedByPeerId(messageIds)
|
let groupedIds = messagesIdsGroupedByPeerId(messageIds)
|
||||||
let signals = groupedIds.values.compactMap { ids -> Signal<Void, NoError>? in
|
let signals = groupedIds.values.compactMap { ids -> Signal<Void, NoError>? in
|
||||||
guard let peerId = ids.first?.peerId, let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else {
|
guard let peerId = ids.first?.peerId, let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return account.network.request(Api.functions.messages.report(peer: inputPeer, id: ids.map { $0.id }, reason: reason.apiReason))
|
return account.network.request(Api.functions.messages.report(peer: inputPeer, id: ids.map { $0.id }, reason: reason.apiReason, message: message))
|
||||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
return .single(.boolFalse)
|
return .single(.boolFalse)
|
||||||
}
|
}
|
||||||
|
@ -359,7 +359,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
|||||||
pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3),
|
pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3),
|
||||||
inputClearButtonColor: UIColor(rgb: 0x8b9197),
|
inputClearButtonColor: UIColor(rgb: 0x8b9197),
|
||||||
itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0xffffff), color2: UIColor(rgb: 0x929196), color3: UIColor(rgb: 0x333333)),
|
itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0xffffff), color2: UIColor(rgb: 0x929196), color3: UIColor(rgb: 0x333333)),
|
||||||
itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0x1c1c1d), strokeColor: UIColor(rgb: 0x1c1c1d), placeholderColor: UIColor(rgb: 0x8f8f8f), primaryColor: UIColor(rgb: 0xffffff), controlColor: UIColor(rgb: 0x8f8f8f))
|
itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0x0f0f0f), strokeColor: UIColor(rgb: 0x0f0f0f), placeholderColor: UIColor(rgb: 0x8f8f8f), primaryColor: UIColor(rgb: 0xffffff), controlColor: UIColor(rgb: 0x8f8f8f))
|
||||||
)
|
)
|
||||||
|
|
||||||
let chatList = PresentationThemeChatList(
|
let chatList = PresentationThemeChatList(
|
||||||
|
@ -612,7 +612,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres
|
|||||||
pageIndicatorInactiveColor: mainSecondaryTextColor.withAlphaComponent(0.4),
|
pageIndicatorInactiveColor: mainSecondaryTextColor.withAlphaComponent(0.4),
|
||||||
inputClearButtonColor: mainSecondaryColor,
|
inputClearButtonColor: mainSecondaryColor,
|
||||||
itemBarChart: PresentationThemeItemBarChart(color1: accentColor, color2: mainSecondaryTextColor.withAlphaComponent(0.5), color3: accentColor.withMultiplied(hue: 1.038, saturation: 0.329, brightness: 0.33)),
|
itemBarChart: PresentationThemeItemBarChart(color1: accentColor, color2: mainSecondaryTextColor.withAlphaComponent(0.5), color3: accentColor.withMultiplied(hue: 1.038, saturation: 0.329, brightness: 0.33)),
|
||||||
itemInputField: PresentationInputFieldTheme(backgroundColor: mainBackgroundColor, strokeColor: mainBackgroundColor, placeholderColor: mainSecondaryColor, primaryColor: UIColor(rgb: 0xffffff), controlColor: mainSecondaryColor)
|
itemInputField: PresentationInputFieldTheme(backgroundColor: mainInputColor, strokeColor: mainInputColor, placeholderColor: mainSecondaryColor, primaryColor: UIColor(rgb: 0xffffff), controlColor: mainSecondaryColor)
|
||||||
)
|
)
|
||||||
|
|
||||||
let chatList = PresentationThemeChatList(
|
let chatList = PresentationThemeChatList(
|
||||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -2767,10 +2767,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let peer = peerViewMainPeer(peerView) {
|
if let peer = peerViewMainPeer(peerView) {
|
||||||
if let selectionState = presentationInterfaceState.interfaceState.selectionState {
|
if let selectionState = presentationInterfaceState.interfaceState.selectionState {
|
||||||
if selectionState.selectedIds.count > 0 {
|
if selectionState.selectedIds.count > 0 {
|
||||||
strongSelf.chatTitleView?.titleContent = .custom(strongSelf.presentationData.strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count ?? 1)), nil, false)
|
strongSelf.chatTitleView?.titleContent = .custom(strongSelf.presentationData.strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count)), nil, false)
|
||||||
} else {
|
} else {
|
||||||
if let reportReason = presentationInterfaceState.reportReason {
|
if let reportReason = presentationInterfaceState.reportReason {
|
||||||
strongSelf.chatTitleView?.titleContent = .custom("Report Smth", strongSelf.presentationInterfaceState.strings.Conversation_SelectMessages, false)
|
let title: String
|
||||||
|
switch reportReason {
|
||||||
|
case .spam:
|
||||||
|
title = strongSelf.presentationData.strings.ReportPeer_ReasonSpam
|
||||||
|
case .fake:
|
||||||
|
title = strongSelf.presentationData.strings.ReportPeer_ReasonFake
|
||||||
|
case .violence:
|
||||||
|
title = strongSelf.presentationData.strings.ReportPeer_ReasonViolence
|
||||||
|
case .porno:
|
||||||
|
title = strongSelf.presentationData.strings.ReportPeer_ReasonPornography
|
||||||
|
case .childAbuse:
|
||||||
|
title = strongSelf.presentationData.strings.ReportPeer_ReasonChildAbuse
|
||||||
|
case .copyright:
|
||||||
|
title = strongSelf.presentationData.strings.ReportPeer_ReasonCopyright
|
||||||
|
case .custom:
|
||||||
|
title = strongSelf.presentationData.strings.ReportPeer_ReasonOther
|
||||||
|
case .irrelevantLocation:
|
||||||
|
title = ""
|
||||||
|
}
|
||||||
|
strongSelf.chatTitleView?.titleContent = .custom(title, strongSelf.presentationInterfaceState.strings.Conversation_SelectMessages, false)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.chatTitleView?.titleContent = .custom(strongSelf.presentationInterfaceState.strings.Conversation_SelectMessages, nil, false)
|
strongSelf.chatTitleView?.titleContent = .custom(strongSelf.presentationInterfaceState.strings.Conversation_SelectMessages, nil, false)
|
||||||
}
|
}
|
||||||
@ -4753,13 +4772,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self?.view.window?.endEditing(true)
|
self?.view.window?.endEditing(true)
|
||||||
controller?.dismissAnimated()
|
controller?.dismissAnimated()
|
||||||
}
|
}
|
||||||
|
var message = ""
|
||||||
var items: [ActionSheetItem] = []
|
var items: [ActionSheetItem] = []
|
||||||
items.append(ReportPeerHeaderActionSheetItem(context: strongSelf.context, text: presentationData.strings.Report_AdditionalDetailsText))
|
items.append(ReportPeerHeaderActionSheetItem(context: strongSelf.context, text: presentationData.strings.Report_AdditionalDetailsText))
|
||||||
items.append(ReportPeerDetailsActionSheetItem(context: strongSelf.context, placeholderText: presentationData.strings.Report_AdditionalDetailsPlaceholder))
|
items.append(ReportPeerDetailsActionSheetItem(context: strongSelf.context, placeholderText: presentationData.strings.Report_AdditionalDetailsPlaceholder, textUpdated: { text in
|
||||||
|
message = text
|
||||||
|
}))
|
||||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Report_Report, color: .accent, font: .bold, enabled: true, action: {
|
items.append(ActionSheetButtonItem(title: presentationData.strings.Report_Report, color: .accent, font: .bold, enabled: true, action: {
|
||||||
dismissAction()
|
dismissAction()
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }, completion: { _ in
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }, completion: { _ in
|
||||||
let _ = (reportPeerMessages(account: strongSelf.context.account, messageIds: Array(messageIds), reason: reportReason)
|
let _ = (reportPeerMessages(account: strongSelf.context.account, messageIds: Array(messageIds), reason: reportReason, message: message)
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
if let strongSelf = self, let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
if let strongSelf = self, let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
|
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||||
@ -6211,7 +6233,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.reportIrrelvantGeoDisposable = (TelegramCore.reportPeer(account: strongSelf.context.account, peerId: peerId, reason: .irrelevantLocation)
|
strongSelf.reportIrrelvantGeoDisposable = (TelegramCore.reportPeer(account: strongSelf.context.account, peerId: peerId, reason: .irrelevantLocation, message: "")
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.reportIrrelvantGeoNoticePromise.set(.single(true))
|
strongSelf.reportIrrelvantGeoNoticePromise.set(.single(true))
|
||||||
@ -10676,7 +10698,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let _ = removePeerChat(account: strongSelf.context.account, peerId: chatPeer.id, reportChatSpam: reportSpam).start()
|
let _ = removePeerChat(account: strongSelf.context.account, peerId: chatPeer.id, reportChatSpam: reportSpam).start()
|
||||||
strongSelf.effectiveNavigationController?.filterController(strongSelf, animated: true)
|
strongSelf.effectiveNavigationController?.filterController(strongSelf, animated: true)
|
||||||
} else if reportSpam {
|
} else if reportSpam {
|
||||||
let _ = TelegramCore.reportPeer(account: strongSelf.context.account, peerId: peer.id, reason: .spam).start()
|
let _ = TelegramCore.reportPeer(account: strongSelf.context.account, peerId: peer.id, reason: .spam, message: "").start()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
] as [ActionSheetItem])
|
] as [ActionSheetItem])
|
||||||
|
@ -716,7 +716,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
(.canPostMessages, self.presentationData.strings.Channel_AdminLog_CanSendMessages),
|
(.canPostMessages, self.presentationData.strings.Channel_AdminLog_CanSendMessages),
|
||||||
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessagesOfOthers),
|
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessagesOfOthers),
|
||||||
(.canEditMessages, self.presentationData.strings.Channel_AdminLog_CanEditMessages),
|
(.canEditMessages, self.presentationData.strings.Channel_AdminLog_CanEditMessages),
|
||||||
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers),
|
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsersViaLink),
|
||||||
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
|
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
|
||||||
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins),
|
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins),
|
||||||
(.canManageCalls, self.presentationData.strings.Channel_AdminLog_CanManageCalls)
|
(.canManageCalls, self.presentationData.strings.Channel_AdminLog_CanManageCalls)
|
||||||
@ -726,7 +726,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
(.canChangeInfo, self.presentationData.strings.Channel_AdminLog_CanChangeInfo),
|
(.canChangeInfo, self.presentationData.strings.Channel_AdminLog_CanChangeInfo),
|
||||||
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessages),
|
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessages),
|
||||||
(.canBanUsers, self.presentationData.strings.Channel_AdminLog_CanBanUsers),
|
(.canBanUsers, self.presentationData.strings.Channel_AdminLog_CanBanUsers),
|
||||||
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers),
|
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsersViaLink),
|
||||||
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
|
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
|
||||||
(.canBeAnonymous, self.presentationData.strings.Channel_AdminLog_CanBeAnonymous),
|
(.canBeAnonymous, self.presentationData.strings.Channel_AdminLog_CanBeAnonymous),
|
||||||
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins),
|
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins),
|
||||||
@ -1192,6 +1192,8 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
appendAttributedText(text: rawText, generateEntities: { index in
|
appendAttributedText(text: rawText, generateEntities: { index in
|
||||||
if index == 0, let author = author {
|
if index == 0, let author = author {
|
||||||
return [.TextMention(peerId: author.id)]
|
return [.TextMention(peerId: author.id)]
|
||||||
|
} else if index == 1 {
|
||||||
|
return [.Bold]
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}, to: &text, entities: &entities)
|
}, to: &text, entities: &entities)
|
||||||
@ -1211,11 +1213,13 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
var text: String = ""
|
var text: String = ""
|
||||||
var entities: [MessageTextEntity] = []
|
var entities: [MessageTextEntity] = []
|
||||||
|
|
||||||
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_DeletedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link)
|
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_DeletedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link.replacingOccurrences(of: "https://", with: ""))
|
||||||
|
|
||||||
appendAttributedText(text: rawText, generateEntities: { index in
|
appendAttributedText(text: rawText, generateEntities: { index in
|
||||||
if index == 0, let author = author {
|
if index == 0, let author = author {
|
||||||
return [.TextMention(peerId: author.id)]
|
return [.TextMention(peerId: author.id)]
|
||||||
|
} else if index == 1 {
|
||||||
|
return [.Bold]
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}, to: &text, entities: &entities)
|
}, to: &text, entities: &entities)
|
||||||
@ -1235,11 +1239,13 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
var text: String = ""
|
var text: String = ""
|
||||||
var entities: [MessageTextEntity] = []
|
var entities: [MessageTextEntity] = []
|
||||||
|
|
||||||
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_RevokedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link)
|
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_RevokedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link.replacingOccurrences(of: "https://", with: ""))
|
||||||
|
|
||||||
appendAttributedText(text: rawText, generateEntities: { index in
|
appendAttributedText(text: rawText, generateEntities: { index in
|
||||||
if index == 0, let author = author {
|
if index == 0, let author = author {
|
||||||
return [.TextMention(peerId: author.id)]
|
return [.TextMention(peerId: author.id)]
|
||||||
|
} else if index == 1 {
|
||||||
|
return [.Bold]
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}, to: &text, entities: &entities)
|
}, to: &text, entities: &entities)
|
||||||
@ -1259,11 +1265,39 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
var text: String = ""
|
var text: String = ""
|
||||||
var entities: [MessageTextEntity] = []
|
var entities: [MessageTextEntity] = []
|
||||||
|
|
||||||
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_EditedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", updatedInvite.link)
|
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_EditedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", updatedInvite.link.replacingOccurrences(of: "https://", with: ""))
|
||||||
|
|
||||||
appendAttributedText(text: rawText, generateEntities: { index in
|
appendAttributedText(text: rawText, generateEntities: { index in
|
||||||
if index == 0, let author = author {
|
if index == 0, let author = author {
|
||||||
return [.TextMention(peerId: author.id)]
|
return [.TextMention(peerId: author.id)]
|
||||||
|
} else if index == 1 {
|
||||||
|
return [.Bold]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}, to: &text, entities: &entities)
|
||||||
|
|
||||||
|
let action = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||||
|
|
||||||
|
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
|
||||||
|
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()))
|
||||||
|
case let .participantJoinedViaInvite(invite):
|
||||||
|
var peers = SimpleDictionary<PeerId, Peer>()
|
||||||
|
var author: Peer?
|
||||||
|
if let peer = self.entry.peers[self.entry.event.peerId] {
|
||||||
|
author = peer
|
||||||
|
peers[peer.id] = peer
|
||||||
|
}
|
||||||
|
|
||||||
|
var text: String = ""
|
||||||
|
var entities: [MessageTextEntity] = []
|
||||||
|
|
||||||
|
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_JoinedViaInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link.replacingOccurrences(of: "https://", with: ""))
|
||||||
|
|
||||||
|
appendAttributedText(text: rawText, generateEntities: { index in
|
||||||
|
if index == 0, let author = author {
|
||||||
|
return [.TextMention(peerId: author.id)]
|
||||||
|
} else if index == 1 {
|
||||||
|
return [.Bold]
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}, to: &text, entities: &entities)
|
}, to: &text, entities: &entities)
|
||||||
|
@ -87,7 +87,7 @@ private func peerButtons(_ state: ChatPresentationInterfaceState) -> [ChatReport
|
|||||||
buttons.append(.shareMyPhoneNumber)
|
buttons.append(.shareMyPhoneNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let _ = state.renderedPeer?.chatMainPeer {
|
} else if let _ = state.renderedPeer?.chatMainPeer, case .peer = state.chatLocation {
|
||||||
if let contactStatus = state.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.suggestAddMembers) {
|
if let contactStatus = state.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.suggestAddMembers) {
|
||||||
buttons.append(.addMembers)
|
buttons.append(.addMembers)
|
||||||
} else if let contactStatus = state.contactStatus, contactStatus.canReportIrrelevantLocation, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
|
} else if let contactStatus = state.contactStatus, contactStatus.canReportIrrelevantLocation, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
|
||||||
|
@ -656,7 +656,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
|
|
||||||
if currentInvitationsContext == nil {
|
if currentInvitationsContext == nil {
|
||||||
var canManageInvitations = false
|
var canManageInvitations = false
|
||||||
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
|
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
|
||||||
canManageInvitations = true
|
canManageInvitations = true
|
||||||
}
|
}
|
||||||
if canManageInvitations {
|
if canManageInvitations {
|
||||||
@ -813,9 +813,13 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
|
|
||||||
if currentInvitationsContext == nil {
|
if currentInvitationsContext == nil {
|
||||||
var canManageInvitations = false
|
var canManageInvitations = false
|
||||||
if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role {
|
if let group = peerViewMainPeer(peerView) as? TelegramGroup {
|
||||||
canManageInvitations = true
|
if case .creator = group.role {
|
||||||
} else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) {
|
canManageInvitations = true
|
||||||
|
} else if case let .admin(rights, _) = group.role, rights.flags.contains(.canInviteUsers) {
|
||||||
|
canManageInvitations = true
|
||||||
|
}
|
||||||
|
} else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
|
||||||
canManageInvitations = true
|
canManageInvitations = true
|
||||||
}
|
}
|
||||||
if canManageInvitations {
|
if canManageInvitations {
|
||||||
@ -1081,14 +1085,33 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func peerInfoCanEdit(peer: Peer?, cachedData: CachedPeerData?) -> Bool {
|
func peerInfoCanEdit(peer: Peer?, cachedData: CachedPeerData?, isContact: Bool?) -> Bool {
|
||||||
if let user = peer as? TelegramUser {
|
if let user = peer as? TelegramUser {
|
||||||
if user.isDeleted {
|
if user.isDeleted {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if let isContact = isContact, !isContact {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
} else if peer is TelegramChannel || peer is TelegramGroup {
|
} else if let peer = peer as? TelegramChannel {
|
||||||
return true
|
if peer.flags.contains(.isCreator) {
|
||||||
|
return true
|
||||||
|
} else if let adminRights = peer.adminRights, adminRights.flags.contains(.canAddAdmins) || adminRights.flags.contains(.canBanUsers) || adminRights.flags.contains(.canChangeInfo) || adminRights.flags.contains(.canInviteUsers) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else if let peer = peer as? TelegramGroup {
|
||||||
|
if case .creator = peer.role {
|
||||||
|
return true
|
||||||
|
} else if case let .admin(rights, _) = peer.role {
|
||||||
|
if rights.flags.contains(.canAddAdmins) || rights.flags.contains(.canBanUsers) || rights.flags.contains(.canChangeInfo) || rights.flags.contains(.canInviteUsers) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1201,18 +1201,19 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
if channel.flags.contains(.isCreator) || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) {
|
if channel.flags.contains(.isCreator) || (channel.adminRights?.flags.contains(.canInviteUsers) == true) {
|
||||||
let invitesText: String
|
let invitesText: String
|
||||||
if let count = data.invitations?.count, count > 0 {
|
if let count = data.invitations?.count, count > 0 {
|
||||||
invitesText = "\(count)"
|
invitesText = "\(count)"
|
||||||
} else {
|
} else {
|
||||||
invitesText = ""
|
invitesText = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
|
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
|
||||||
interaction.editingOpenInviteLinksSetup()
|
interaction.editingOpenInviteLinksSetup()
|
||||||
}))
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel.flags.contains(.isCreator) || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) {
|
||||||
let discussionGroupTitle: String
|
let discussionGroupTitle: String
|
||||||
if let _ = data.cachedData as? CachedChannelData {
|
if let _ = data.cachedData as? CachedChannelData {
|
||||||
if let peer = data.linkedDiscussionPeer {
|
if let peer = data.linkedDiscussionPeer {
|
||||||
@ -1336,39 +1337,22 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
interaction.editingOpenPreHistorySetup()
|
interaction.editingOpenPreHistorySetup()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
if channel.hasPermission(.inviteMembers) {
|
|
||||||
let invitesText: String
|
|
||||||
if let count = data.invitations?.count, count > 0 {
|
|
||||||
invitesText = "\(count)"
|
|
||||||
} else {
|
|
||||||
invitesText = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
|
|
||||||
interaction.editingOpenInviteLinksSetup()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if channel.hasPermission(.changeInfo) {
|
|
||||||
let timeoutString: String
|
|
||||||
if case let .known(value) = cachedData.autoremoveTimeout {
|
|
||||||
if let value = value?.effectiveValue {
|
|
||||||
timeoutString = timeIntervalString(strings: presentationData.strings, value: value)
|
|
||||||
} else {
|
|
||||||
timeoutString = presentationData.strings.PeerInfo_AutoremoveMessagesDisabled
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
timeoutString = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAutoremove, label: .text(timeoutString), text: presentationData.strings.PeerInfo_AutoremoveMessages, action: {
|
|
||||||
interaction.editingOpenAutoremoveMesages()
|
|
||||||
}))
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isCreator || (channel.hasPermission(.inviteMembers)) {
|
||||||
|
let invitesText: String
|
||||||
|
if let count = data.invitations?.count, count > 0 {
|
||||||
|
invitesText = "\(count)"
|
||||||
|
} else {
|
||||||
|
invitesText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
|
||||||
|
interaction.editingOpenInviteLinksSetup()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
if cachedData.flags.contains(.canSetStickerSet) && canEditPeerInfo(context: context, peer: channel) {
|
if cachedData.flags.contains(.canSetStickerSet) && canEditPeerInfo(context: context, peer: channel) {
|
||||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStickerPack, label: .text(cachedData.stickerPack?.title ?? presentationData.strings.GroupInfo_SharedMediaNone), text: presentationData.strings.Stickers_GroupStickers, icon: UIImage(bundleImageName: "Settings/MenuIcons/Stickers"), action: {
|
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStickerPack, label: .text(cachedData.stickerPack?.title ?? presentationData.strings.GroupInfo_SharedMediaNone), text: presentationData.strings.Stickers_GroupStickers, icon: UIImage(bundleImageName: "Settings/MenuIcons/Stickers"), action: {
|
||||||
interaction.editingOpenStickerPackSetup()
|
interaction.editingOpenStickerPackSetup()
|
||||||
@ -1472,8 +1456,19 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
|||||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: {
|
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: {
|
||||||
interaction.openParticipantsSection(.admins)
|
interaction.openParticipantsSection(.admins)
|
||||||
}))
|
}))
|
||||||
} else if case .admin = group.role {
|
} else if case let .admin(rights, _) = group.role {
|
||||||
|
if rights.flags.contains(.canInviteUsers) {
|
||||||
|
let invitesText: String
|
||||||
|
if let count = data.invitations?.count, count > 0 {
|
||||||
|
invitesText = "\(count)"
|
||||||
|
} else {
|
||||||
|
invitesText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
|
||||||
|
interaction.editingOpenInviteLinksSetup()
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3744,7 +3739,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
let _ = removePeerChat(account: strongSelf.context.account, peerId: strongSelf.peerId, reportChatSpam: reportSpam).start()
|
let _ = removePeerChat(account: strongSelf.context.account, peerId: strongSelf.peerId, reportChatSpam: reportSpam).start()
|
||||||
(strongSelf.controller?.navigationController as? NavigationController)?.popToRoot(animated: true)
|
(strongSelf.controller?.navigationController as? NavigationController)?.popToRoot(animated: true)
|
||||||
} else if reportSpam {
|
} else if reportSpam {
|
||||||
let _ = reportPeer(account: strongSelf.context.account, peerId: strongSelf.peerId, reason: .spam).start()
|
let _ = reportPeer(account: strongSelf.context.account, peerId: strongSelf.peerId, reason: .spam, message: "").start()
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteSendMessageIntents(peerId: strongSelf.peerId)
|
deleteSendMessageIntents(peerId: strongSelf.peerId)
|
||||||
@ -3875,6 +3870,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func editingOpenInviteLinksSetup() {
|
private func editingOpenInviteLinksSetup() {
|
||||||
|
|
||||||
self.controller?.push(inviteLinkListController(context: self.context, peerId: self.peerId, admin: nil))
|
self.controller?.push(inviteLinkListController(context: self.context, peerId: self.peerId, admin: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5307,7 +5303,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
if self.isSettings {
|
if self.isSettings {
|
||||||
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
|
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
|
||||||
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
|
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
|
||||||
} else if peerInfoCanEdit(peer: self.data?.peer, cachedData: self.data?.cachedData) {
|
} else if peerInfoCanEdit(peer: self.data?.peer, cachedData: self.data?.cachedData, isContact: self.data?.isContact) {
|
||||||
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
|
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
|
||||||
}
|
}
|
||||||
if self.state.selectedMessageIds == nil {
|
if self.state.selectedMessageIds == nil {
|
||||||
@ -6230,14 +6226,16 @@ func presentAddMembers(context: AccountContext, parentController: ViewController
|
|||||||
var canCreateInviteLink = false
|
var canCreateInviteLink = false
|
||||||
if let group = groupPeer as? TelegramGroup {
|
if let group = groupPeer as? TelegramGroup {
|
||||||
switch group.role {
|
switch group.role {
|
||||||
case .creator, .admin:
|
case .creator:
|
||||||
canCreateInviteLink = true
|
canCreateInviteLink = true
|
||||||
|
case let .admin(rights, _):
|
||||||
|
canCreateInviteLink = rights.flags.contains(.canInviteUsers)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if let channel = groupPeer as? TelegramChannel {
|
} else if let channel = groupPeer as? TelegramChannel {
|
||||||
if channel.hasPermission(.inviteMembers) {
|
if channel.hasPermission(.inviteMembers) {
|
||||||
if channel.flags.contains(.isCreator) || (channel.adminRights != nil && channel.username == nil) {
|
if channel.flags.contains(.isCreator) || (channel.hasPermission(.inviteMembers)) {
|
||||||
canCreateInviteLink = true
|
canCreateInviteLink = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,14 +13,16 @@ import AppBundle
|
|||||||
public final class ReportPeerDetailsActionSheetItem: ActionSheetItem {
|
public final class ReportPeerDetailsActionSheetItem: ActionSheetItem {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let placeholderText: String
|
let placeholderText: String
|
||||||
|
let textUpdated: (String) -> Void
|
||||||
|
|
||||||
public init(context: AccountContext, placeholderText: String) {
|
public init(context: AccountContext, placeholderText: String, textUpdated: @escaping (String) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.placeholderText = placeholderText
|
self.placeholderText = placeholderText
|
||||||
|
self.textUpdated = textUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||||
return ReportPeerDetailsActionSheetItemNode(theme: theme, context: self.context, placeholderText: self.placeholderText)
|
return ReportPeerDetailsActionSheetItemNode(theme: theme, context: self.context, placeholderText: self.placeholderText, textUpdated: self.textUpdated)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateNode(_ node: ActionSheetItemNode) {
|
public func updateNode(_ node: ActionSheetItemNode) {
|
||||||
@ -34,7 +36,7 @@ private final class ReportPeerDetailsActionSheetItemNode: ActionSheetItemNode {
|
|||||||
|
|
||||||
private let accessibilityArea: AccessibilityAreaNode
|
private let accessibilityArea: AccessibilityAreaNode
|
||||||
|
|
||||||
init(theme: ActionSheetControllerTheme, context: AccountContext, placeholderText: String) {
|
init(theme: ActionSheetControllerTheme, context: AccountContext, placeholderText: String, textUpdated: @escaping (String) -> Void) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -48,7 +50,12 @@ private final class ReportPeerDetailsActionSheetItemNode: ActionSheetItemNode {
|
|||||||
|
|
||||||
self.addSubnode(self.inputFieldNode)
|
self.addSubnode(self.inputFieldNode)
|
||||||
|
|
||||||
// self.inputFieldNode.
|
self.inputFieldNode.updateText = { text in
|
||||||
|
textUpdated(text)
|
||||||
|
}
|
||||||
|
self.inputFieldNode.updateHeight = {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||||
|
@ -195,7 +195,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.textNode.attributedText = attributedText
|
self.textNode.attributedText = attributedText
|
||||||
self.textNode.maximumNumberOfLines = 2
|
self.textNode.maximumNumberOfLines = 2
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
self.originalRemainingSeconds = 4
|
self.originalRemainingSeconds = 3
|
||||||
case let .banned(text):
|
case let .banned(text):
|
||||||
self.avatarNode = nil
|
self.avatarNode = nil
|
||||||
self.iconNode = nil
|
self.iconNode = nil
|
||||||
|
Loading…
x
Reference in New Issue
Block a user