mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 05:25:20 +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_any" = "%@ people joined";
|
||||
"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.PeopleJoinedShortNoneExpired" = "no one joined";
|
||||
@ -5899,6 +5900,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"InviteLink.QRCode.Title" = "Invite by QR Code";
|
||||
"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.InviteLink" = "Invite Link";
|
||||
@ -6021,6 +6023,9 @@ Sorry for the inconvenience.";
|
||||
"Channel.AdminLog.DeletedInviteLink" = "%1$@ deleted invite link %2$@";
|
||||
"Channel.AdminLog.RevokedInviteLink" = "%1$@ revoked 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.BroadcastConvert" = "Convert Group to Channel";
|
||||
@ -6056,7 +6061,6 @@ Sorry for the inconvenience.";
|
||||
"Report.Report" = "Report";
|
||||
"Report.Succeed" = "Telegram moderators will study your report. Thank you!";
|
||||
|
||||
|
||||
"Conversation.AutoremoveRemainingTime" = "auto-delete in %@";
|
||||
"Conversation.AutoremoveRemainingDays_1" = "auto-delete in %@ day";
|
||||
"Conversation.AutoremoveRemainingDays_any" = "auto-delete in %@ days";
|
||||
@ -6066,3 +6070,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"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"?>
|
||||
<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"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="17034"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="17500"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--TGNeoConversationController-->
|
||||
@ -1569,11 +1569,445 @@ contacts found.</string>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="729" y="757.25"/>
|
||||
</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>
|
||||
<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"/>
|
||||
<resources>
|
||||
<image name="BotCommandIcon" width="128" height="128"/>
|
||||
<image name="BubbleNotification" 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="LoginIcon" 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]
|
||||
switch fitzModifier {
|
||||
case .type12:
|
||||
replacementColors = [0xca907a, 0xedc5a5, 0xf7e3c3, 0xfbefd6].map { UIColor(rgb: $0) }
|
||||
replacementColors = [0xcb7b55, 0xf6b689, 0xffcda7, 0xffdfc5].map { UIColor(rgb: $0) }
|
||||
case .type3:
|
||||
replacementColors = [0xaa7c60, 0xc8a987, 0xddc89f, 0xe6d6b2].map { UIColor(rgb: $0) }
|
||||
replacementColors = [0xa45a38, 0xdf986b, 0xedb183, 0xf4c3a0].map { UIColor(rgb: $0) }
|
||||
case .type4:
|
||||
replacementColors = [0x8c6148, 0xad8562, 0xc49e76, 0xd4b188].map { UIColor(rgb: $0) }
|
||||
replacementColors = [0x703a17, 0xab673d, 0xc37f4e, 0xd89667].map { UIColor(rgb: $0) }
|
||||
case .type5:
|
||||
replacementColors = [0x6e3c2c, 0x925a34, 0xa16e46, 0xac7a52].map { UIColor(rgb: $0) }
|
||||
replacementColors = [0x4a2409, 0x7d3e0e, 0x965529, 0xa96337].map { UIColor(rgb: $0) }
|
||||
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 {
|
||||
|
@ -835,7 +835,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
|
||||
let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start()
|
||||
|
||||
strongSelf.updateState { state in
|
||||
@ -859,7 +858,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
|
||||
let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start()
|
||||
|
||||
strongSelf.updateState { state in
|
||||
|
@ -307,23 +307,6 @@ public final class DatePickerNode: ASDisplayNode {
|
||||
|
||||
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 {
|
||||
get {
|
||||
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)
|
||||
self.updateState(updatedState, animated: false)
|
||||
|
||||
self.pickerNode.minimumDate = newValue
|
||||
|
||||
if let size = self.validLayout {
|
||||
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 {
|
||||
get {
|
||||
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
|
||||
monthChangedImpl?(date)
|
||||
})
|
||||
self.pickerNode.minimumDate = self.state.minDate
|
||||
self.pickerNode.maximumDate = self.state.maxDate
|
||||
|
||||
self.monthButtonNode = HighlightTrackingButtonNode()
|
||||
self.monthTextNode = ImmediateTextNode()
|
||||
@ -768,8 +776,8 @@ private final class MonthPickerNode: ASDisplayNode, UIPickerViewDelegate, UIPick
|
||||
}
|
||||
}
|
||||
|
||||
var minDate: Date?
|
||||
var maxDate: Date?
|
||||
var minimumDate: Date?
|
||||
var maximumDate: Date?
|
||||
|
||||
private let valueChanged: (Date) -> Void
|
||||
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
|
||||
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.valueChanged(date)
|
||||
@ -924,7 +951,7 @@ private final class TimePickerNode: ASDisplayNode {
|
||||
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: "")
|
||||
|
||||
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)
|
||||
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 {
|
||||
case header
|
||||
case mainLink
|
||||
case links(Int32)
|
||||
case manage
|
||||
}
|
||||
|
||||
private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||
case header(PresentationTheme, String, String)
|
||||
case mainLink(PresentationTheme, ExportedInvitation)
|
||||
case links(Int32, PresentationTheme, [ExportedInvitation])
|
||||
case manage(PresentationTheme, String, Bool)
|
||||
|
||||
var stableId: InviteLinkInviteEntryId {
|
||||
@ -64,8 +62,6 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||
return .header
|
||||
case .mainLink:
|
||||
return .mainLink
|
||||
case let .links(index, _, _):
|
||||
return .links(index)
|
||||
case .manage:
|
||||
return .manage
|
||||
}
|
||||
@ -85,12 +81,6 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
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):
|
||||
if case let .manage(rhsTheme, rhsText, rhsStandalone) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStandalone == rhsStandalone {
|
||||
return true
|
||||
@ -106,28 +96,19 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||
switch rhs {
|
||||
case .header:
|
||||
return false
|
||||
case .mainLink, .links, .manage:
|
||||
case .mainLink, .manage:
|
||||
return true
|
||||
}
|
||||
case .mainLink:
|
||||
switch rhs {
|
||||
case .header, .mainLink:
|
||||
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:
|
||||
return true
|
||||
}
|
||||
case .manage:
|
||||
switch rhs {
|
||||
case .header, .mainLink, .links:
|
||||
case .header, .mainLink:
|
||||
return false
|
||||
case .manage:
|
||||
return true
|
||||
@ -148,12 +129,6 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||
interaction.mainLinkContextAction(invite, node, nil)
|
||||
}, 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):
|
||||
return InviteLinkInviteManageItem(theme: theme, text: text, standalone: standalone, action: {
|
||||
interaction.manageLinks()
|
||||
@ -372,8 +347,17 @@ public final class InviteLinkInviteController: ViewController {
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
if let invite = invite {
|
||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||
self?.controller?.present(controller, in: .window(.root))
|
||||
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))
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
||||
@ -392,10 +376,6 @@ public final class InviteLinkInviteController: ViewController {
|
||||
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
||||
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() })])
|
||||
@ -430,7 +410,13 @@ public final class InviteLinkInviteController: ViewController {
|
||||
if let strongSelf = self {
|
||||
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?
|
||||
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))
|
||||
}
|
||||
|
||||
let additionalInvites = invites.invitations.filter { $0.link != mainInvite?.link }
|
||||
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))
|
||||
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, true))
|
||||
|
||||
let previousEntries = previousEntries.swap(entries)
|
||||
|
||||
|
@ -70,7 +70,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
|
||||
case revokedLinksHeader(PresentationTheme, String)
|
||||
case revokedLinksDeleteAll(PresentationTheme, String)
|
||||
case revokedLinks(Int32, PresentationTheme, ExportedInvitation?)
|
||||
case revokedLink(Int32, PresentationTheme, ExportedInvitation?)
|
||||
|
||||
case adminsHeader(PresentationTheme, String)
|
||||
case admin(Int32, PresentationTheme, ExportedInvitationCreator)
|
||||
@ -83,7 +83,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
return InviteLinksListSection.mainLink.rawValue
|
||||
case .linksHeader, .linksCreate, .link, .linksInfo:
|
||||
return InviteLinksListSection.links.rawValue
|
||||
case .revokedLinksHeader, .revokedLinksDeleteAll, .revokedLinks:
|
||||
case .revokedLinksHeader, .revokedLinksDeleteAll, .revokedLink:
|
||||
return InviteLinksListSection.revokedLinks.rawValue
|
||||
case .adminsHeader, .admin:
|
||||
return InviteLinksListSection.admins.rawValue
|
||||
@ -112,7 +112,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
return 10001
|
||||
case .revokedLinksDeleteAll:
|
||||
return 10002
|
||||
case let .revokedLinks(index, _, _):
|
||||
case let .revokedLink(index, _, _):
|
||||
return 10003 + index
|
||||
case .adminsHeader:
|
||||
return 20001
|
||||
@ -183,8 +183,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .revokedLinks(lhsIndex, lhsTheme, lhsLink):
|
||||
if case let .revokedLinks(rhsIndex, rhsTheme, rhsLink) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink {
|
||||
case let .revokedLink(lhsIndex, lhsTheme, lhsLink):
|
||||
if case let .revokedLink(rhsIndex, rhsTheme, rhsLink) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink {
|
||||
return true
|
||||
} else {
|
||||
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: {
|
||||
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
|
||||
arguments.openLink(invite)
|
||||
} contextAction: { invite, node, gesture in
|
||||
@ -273,7 +273,13 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
||||
var entries: [InviteLinksListEntry] = []
|
||||
|
||||
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?
|
||||
@ -308,21 +314,32 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
||||
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]?
|
||||
if let invites = invites {
|
||||
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 {
|
||||
var index: Int32 = 0
|
||||
for invite in additionalInvites {
|
||||
entries.append(.link(index, presentationData.theme, invite, invite.expireDate != nil ? tick : nil))
|
||||
index += 1
|
||||
}
|
||||
} else if let admin = admin, admin.count > 0 {
|
||||
} else if let admin = admin, admin.count > 1 {
|
||||
var index: Int32 = 0
|
||||
for _ in 0 ..< admin.count - 1 {
|
||||
entries.append(.link(index, presentationData.theme, nil, nil))
|
||||
@ -340,7 +357,13 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
||||
}
|
||||
var index: Int32 = 0
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -387,7 +410,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
|
||||
var getControllerImpl: (() -> ViewController?)?
|
||||
|
||||
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 creators: Signal<[ExportedInvitationCreator], NoError>
|
||||
@ -435,8 +458,17 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||
presentControllerImpl?(controller, nil)
|
||||
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> 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 {
|
||||
@ -545,8 +577,17 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
|
||||
}, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||
presentControllerImpl?(controller, nil)
|
||||
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> 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
|
||||
|
@ -37,14 +37,16 @@ public final class InviteLinkQRCodeController: ViewController {
|
||||
|
||||
private let context: AccountContext
|
||||
private let invite: ExportedInvitation
|
||||
private let isGroup: Bool
|
||||
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
private let idleTimerExtensionDisposable = MetaDisposable()
|
||||
|
||||
public init(context: AccountContext, invite: ExportedInvitation) {
|
||||
public init(context: AccountContext, invite: ExportedInvitation, isGroup: Bool) {
|
||||
self.context = context
|
||||
self.invite = invite
|
||||
self.isGroup = isGroup
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
@ -74,7 +76,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
||||
}
|
||||
|
||||
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?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
@ -133,7 +135,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
||||
var dismiss: (() -> Void)?
|
||||
var cancel: (() -> Void)?
|
||||
|
||||
init(context: AccountContext, invite: ExportedInvitation) {
|
||||
init(context: AccountContext, invite: ExportedInvitation, isGroup: Bool) {
|
||||
self.context = context
|
||||
self.invite = invite
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -211,7 +213,7 @@ public final class InviteLinkQRCodeController: ViewController {
|
||||
|
||||
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.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
@ -343,7 +343,6 @@ public final class InviteLinkViewController: ViewController {
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
private var disposable: Disposable?
|
||||
private let actionDisposable = MetaDisposable()
|
||||
|
||||
private let dimNode: ASDisplayNode
|
||||
private let contentNode: ASDisplayNode
|
||||
@ -473,9 +472,10 @@ public final class InviteLinkViewController: ViewController {
|
||||
ActionSheetTextItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Text),
|
||||
ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Action, color: .destructive, action: {
|
||||
dismissAction()
|
||||
|
||||
self?.actionDisposable.set((deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
|
||||
}))
|
||||
self?.controller?.dismiss()
|
||||
|
||||
let _ = (deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
|
||||
})
|
||||
|
||||
self?.controller?.revokedInvitationsContext?.remove(invite)
|
||||
})
|
||||
@ -490,7 +490,42 @@ public final class InviteLinkViewController: ViewController {
|
||||
}, action: { [weak self] _, f in
|
||||
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))
|
||||
})))
|
||||
}
|
||||
@ -739,6 +774,7 @@ public final class InviteLinkViewController: ViewController {
|
||||
subtitleText = self.presentationData.strings.InviteLink_Revoked
|
||||
} else if let usageLimit = self.invite.usageLimit, let count = self.invite.count, count >= usageLimit {
|
||||
subtitleText = self.presentationData.strings.InviteLink_UsageLimitReached
|
||||
subtitleColor = self.presentationData.theme.list.itemDestructiveColor
|
||||
} else if let expireDate = self.invite.expireDate {
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
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 {
|
||||
isExpired = true
|
||||
}
|
||||
var isFull = false
|
||||
|
||||
if let usageLimit = invite.usageLimit {
|
||||
if !isExpired {
|
||||
@ -376,6 +377,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
timerValue = .fraction(fraction)
|
||||
}
|
||||
} else if remaining == 0 {
|
||||
isFull = true
|
||||
if !subtitleText.isEmpty {
|
||||
subtitleText += " • "
|
||||
}
|
||||
@ -383,7 +385,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let expireDate = invite.expireDate {
|
||||
if let expireDate = invite.expireDate, !isFull {
|
||||
if !isExpired {
|
||||
if !subtitleText.isEmpty {
|
||||
subtitleText += " • "
|
||||
@ -570,7 +572,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
timerNode = TimerNode()
|
||||
timerNode.isUserInteractionEnabled = false
|
||||
strongSelf.timerNode = timerNode
|
||||
strongSelf.addSubnode(timerNode)
|
||||
strongSelf.offsetContainerNode.addSubnode(timerNode)
|
||||
}
|
||||
timerNode.update(color: iconColor, value: timerValue)
|
||||
} else if let timerNode = strongSelf.timerNode {
|
||||
@ -785,7 +787,7 @@ private final class TimerNode: ASDisplayNode {
|
||||
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
|
||||
let sparks = fraction > 0.05 && 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)
|
||||
|
@ -287,25 +287,6 @@ private func ChannelMembersControllerEntries(context: AccountContext, presentati
|
||||
|
||||
var index: Int32 = 0
|
||||
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 {
|
||||
var editable = true
|
||||
var canEditMembers = false
|
||||
|
@ -968,8 +968,17 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
||||
return nil
|
||||
} |> deliverOnMainQueue).start(next: { invite in
|
||||
if let invite = invite {
|
||||
let controller = InviteLinkQRCodeController(context: context, invite: invite)
|
||||
presentControllerImpl?(controller, nil)
|
||||
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> 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 {
|
||||
switch subject {
|
||||
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: {
|
||||
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)
|
||||
@ -86,7 +86,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
||||
completion(reportReason, true)
|
||||
})
|
||||
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: {
|
||||
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)
|
||||
@ -155,12 +155,16 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
break
|
||||
}
|
||||
if let reportReason = reportReason {
|
||||
var passthrough = passthrough
|
||||
if case .fake = reportReason {
|
||||
passthrough = false
|
||||
}
|
||||
switch subject {
|
||||
case let .peer(peerId):
|
||||
if passthrough {
|
||||
completion(reportReason, true)
|
||||
} else {
|
||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason)
|
||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "")
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
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)
|
||||
@ -172,7 +176,7 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
if passthrough {
|
||||
completion(reportReason, true)
|
||||
} else {
|
||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason)
|
||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "")
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
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)
|
||||
@ -326,7 +330,7 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
|
||||
}
|
||||
|
||||
if !text.isEmpty {
|
||||
let reportReason: ReportReason = .custom(text)
|
||||
let reportReason: ReportReason = .custom
|
||||
let completed: () -> Void = {
|
||||
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)
|
||||
@ -335,12 +339,12 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
|
||||
}
|
||||
switch subject {
|
||||
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: {
|
||||
completed()
|
||||
}))
|
||||
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: {
|
||||
completed()
|
||||
}))
|
||||
|
@ -1017,7 +1017,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
let _ = removePeerChat(account: context.account, peerId: peerId, reportChatSpam: reportSpam).start()
|
||||
popToRootImpl?()
|
||||
} 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)
|
||||
|
@ -117,8 +117,14 @@ private class SectionHeaderItemNode: ListViewItemNode {
|
||||
strongSelf.headerNode = headerNode
|
||||
}
|
||||
headerNode.title = item.title
|
||||
switch item.additionalText {
|
||||
case .none, .generic:
|
||||
headerNode.actionType = .generic
|
||||
case .destructive:
|
||||
headerNode.actionType = .destructive
|
||||
|
||||
}
|
||||
headerNode.action = item.additionalText.text
|
||||
|
||||
headerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
|
||||
headerNode.updateLayout(size: contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
|
||||
private let clearButton: HighlightableButtonNode
|
||||
|
||||
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 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) {
|
||||
self.updateTextNodeText(animated: true)
|
||||
self.updateText?(editableTextNode.attributedText?.string ?? "")
|
||||
}
|
||||
|
||||
public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
|
@ -144,6 +144,7 @@ public struct TelegramChannelFlags: OptionSet {
|
||||
public static let hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4)
|
||||
public static let hasActiveVoiceChat = TelegramChannelFlags(rawValue: 1 << 5)
|
||||
public static let isFake = TelegramChannelFlags(rawValue: 1 << 6)
|
||||
public static let isGigagroup = TelegramChannelFlags(rawValue: 1 << 7)
|
||||
}
|
||||
|
||||
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[-1798033689] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilterEmpty($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[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($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[505595789] = { return Api.ReportReason.parse_inputReportReasonViolence($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[-1376497949] = { return Api.ReportReason.parse_inputReportReasonChildAbuse($0) }
|
||||
dict[-606798099] = { return Api.ReportReason.parse_inputReportReasonGeoIrrelevant($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[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) }
|
||||
dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) }
|
||||
|
@ -9610,24 +9610,25 @@ public extension Api {
|
||||
|
||||
}
|
||||
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) {
|
||||
switch self {
|
||||
case .chatAdminWithInvites(let adminId, let invitesCount):
|
||||
case .chatAdminWithInvites(let adminId, let invitesCount, let revokedInvitesCount):
|
||||
if boxed {
|
||||
buffer.appendInt32(-559275508)
|
||||
buffer.appendInt32(-539872497)
|
||||
}
|
||||
serializeInt32(adminId, buffer: buffer, boxed: false)
|
||||
serializeInt32(invitesCount, buffer: buffer, boxed: false)
|
||||
serializeInt32(revokedInvitesCount, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .chatAdminWithInvites(let adminId, let invitesCount):
|
||||
return ("chatAdminWithInvites", [("adminId", adminId), ("invitesCount", invitesCount)])
|
||||
case .chatAdminWithInvites(let adminId, let invitesCount, let revokedInvitesCount):
|
||||
return ("chatAdminWithInvites", [("adminId", adminId), ("invitesCount", invitesCount), ("revokedInvitesCount", revokedInvitesCount)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -9636,10 +9637,13 @@ public extension Api {
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.ChatAdminWithInvites.chatAdminWithInvites(adminId: _1!, invitesCount: _2!)
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.ChatAdminWithInvites.chatAdminWithInvites(adminId: _1!, invitesCount: _2!, revokedInvitesCount: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -12149,11 +12153,11 @@ public extension Api {
|
||||
case inputReportReasonSpam
|
||||
case inputReportReasonViolence
|
||||
case inputReportReasonPornography
|
||||
case inputReportReasonOther(text: String)
|
||||
case inputReportReasonCopyright
|
||||
case inputReportReasonChildAbuse
|
||||
case inputReportReasonGeoIrrelevant
|
||||
case inputReportReasonFake
|
||||
case inputReportReasonOther
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -12174,12 +12178,6 @@ public extension Api {
|
||||
buffer.appendInt32(777640226)
|
||||
}
|
||||
|
||||
break
|
||||
case .inputReportReasonOther(let text):
|
||||
if boxed {
|
||||
buffer.appendInt32(-512463606)
|
||||
}
|
||||
serializeString(text, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .inputReportReasonCopyright:
|
||||
if boxed {
|
||||
@ -12204,6 +12202,12 @@ public extension Api {
|
||||
buffer.appendInt32(-170010905)
|
||||
}
|
||||
|
||||
break
|
||||
case .inputReportReasonOther:
|
||||
if boxed {
|
||||
buffer.appendInt32(-1041980751)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -12216,8 +12220,6 @@ public extension Api {
|
||||
return ("inputReportReasonViolence", [])
|
||||
case .inputReportReasonPornography:
|
||||
return ("inputReportReasonPornography", [])
|
||||
case .inputReportReasonOther(let text):
|
||||
return ("inputReportReasonOther", [("text", text)])
|
||||
case .inputReportReasonCopyright:
|
||||
return ("inputReportReasonCopyright", [])
|
||||
case .inputReportReasonChildAbuse:
|
||||
@ -12226,6 +12228,8 @@ public extension Api {
|
||||
return ("inputReportReasonGeoIrrelevant", [])
|
||||
case .inputReportReasonFake:
|
||||
return ("inputReportReasonFake", [])
|
||||
case .inputReportReasonOther:
|
||||
return ("inputReportReasonOther", [])
|
||||
}
|
||||
}
|
||||
|
||||
@ -12238,17 +12242,6 @@ public extension Api {
|
||||
public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? {
|
||||
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? {
|
||||
return Api.ReportReason.inputReportReasonCopyright
|
||||
}
|
||||
@ -12261,6 +12254,9 @@ public extension Api {
|
||||
public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? {
|
||||
return Api.ReportReason.inputReportReasonFake
|
||||
}
|
||||
public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? {
|
||||
return Api.ReportReason.inputReportReasonOther
|
||||
}
|
||||
|
||||
}
|
||||
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>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1144759543)
|
||||
@ -4126,6 +4106,27 @@ public extension Api {
|
||||
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 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>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(655677548)
|
||||
@ -7125,6 +7111,39 @@ public extension Api {
|
||||
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 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 {
|
||||
channelFlags.insert(.isFake)
|
||||
}
|
||||
if (flags & Int32(1 << 25)) != 0 {
|
||||
channelFlags.insert(.isGigagroup)
|
||||
}
|
||||
|
||||
let restrictionInfo: PeerAccessRestrictionInfo?
|
||||
if let restrictionReason = restrictionReason {
|
||||
|
@ -62,6 +62,7 @@ public enum AdminLogEventAction {
|
||||
case deleteExportedInvitation(ExportedInvitation)
|
||||
case revokeExportedInvitation(ExportedInvitation)
|
||||
case editExportedInvitation(previous: ExportedInvitation, updated: ExportedInvitation)
|
||||
case participantJoinedViaInvite(ExportedInvitation)
|
||||
}
|
||||
|
||||
public enum ChannelAdminLogEventError {
|
||||
@ -241,11 +242,11 @@ public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: Pe
|
||||
action = .revokeExportedInvitation(ExportedInvitation(apiExportedInvite: invite))
|
||||
case let .channelAdminLogEventActionExportedInviteEdit(prevInvite, newInvite):
|
||||
action = .editExportedInvitation(previous: ExportedInvitation(apiExportedInvite: prevInvite), updated: ExportedInvitation(apiExportedInvite: newInvite))
|
||||
case let .channelAdminLogEventActionParticipantJoinByInvite(invite):
|
||||
action = .participantJoinedViaInvite(ExportedInvitation(apiExportedInvite: invite))
|
||||
case let .channelAdminLogEventActionParticipantVolume(participant):
|
||||
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
|
||||
action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000)
|
||||
case let .channelAdminLogEventActionParticipantJoinByInvite(invite):
|
||||
action = nil
|
||||
}
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||
if let action = action {
|
||||
|
@ -808,6 +808,7 @@ public final class PeerInvitationImportersContext {
|
||||
public struct ExportedInvitationCreator : Equatable {
|
||||
public let peer: RenderedPeer
|
||||
public let count: Int32
|
||||
public let revokedCount: Int32
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
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 copyright
|
||||
case irrelevantLocation
|
||||
case custom(String)
|
||||
case custom
|
||||
}
|
||||
|
||||
private extension ReportReason {
|
||||
@ -104,16 +104,16 @@ private extension ReportReason {
|
||||
return .inputReportReasonCopyright
|
||||
case .irrelevantLocation:
|
||||
return .inputReportReasonGeoIrrelevant
|
||||
case let .custom(text):
|
||||
return .inputReportReasonOther(text: text)
|
||||
case let .custom:
|
||||
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
|
||||
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
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
@ -126,14 +126,14 @@ public func reportPeer(account: Account, peerId: PeerId, reason: ReportReason) -
|
||||
} |> 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
|
||||
let groupedIds = messagesIdsGroupedByPeerId(messageIds)
|
||||
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 {
|
||||
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
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|
@ -359,7 +359,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3),
|
||||
inputClearButtonColor: UIColor(rgb: 0x8b9197),
|
||||
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(
|
||||
|
@ -612,7 +612,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres
|
||||
pageIndicatorInactiveColor: mainSecondaryTextColor.withAlphaComponent(0.4),
|
||||
inputClearButtonColor: mainSecondaryColor,
|
||||
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(
|
||||
|
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 selectionState = presentationInterfaceState.interfaceState.selectionState {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
var message = ""
|
||||
var items: [ActionSheetItem] = []
|
||||
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: {
|
||||
dismissAction()
|
||||
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
|
||||
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)
|
||||
@ -6211,7 +6233,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self else {
|
||||
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
|
||||
if let strongSelf = self {
|
||||
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()
|
||||
strongSelf.effectiveNavigationController?.filterController(strongSelf, animated: true)
|
||||
} 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])
|
||||
|
@ -716,7 +716,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
(.canPostMessages, self.presentationData.strings.Channel_AdminLog_CanSendMessages),
|
||||
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessagesOfOthers),
|
||||
(.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),
|
||||
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins),
|
||||
(.canManageCalls, self.presentationData.strings.Channel_AdminLog_CanManageCalls)
|
||||
@ -726,7 +726,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
(.canChangeInfo, self.presentationData.strings.Channel_AdminLog_CanChangeInfo),
|
||||
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessages),
|
||||
(.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),
|
||||
(.canBeAnonymous, self.presentationData.strings.Channel_AdminLog_CanBeAnonymous),
|
||||
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins),
|
||||
@ -1192,6 +1192,8 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
appendAttributedText(text: rawText, generateEntities: { index in
|
||||
if index == 0, let author = author {
|
||||
return [.TextMention(peerId: author.id)]
|
||||
} else if index == 1 {
|
||||
return [.Bold]
|
||||
}
|
||||
return []
|
||||
}, to: &text, entities: &entities)
|
||||
@ -1211,11 +1213,13 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
var text: String = ""
|
||||
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
|
||||
if index == 0, let author = author {
|
||||
return [.TextMention(peerId: author.id)]
|
||||
} else if index == 1 {
|
||||
return [.Bold]
|
||||
}
|
||||
return []
|
||||
}, to: &text, entities: &entities)
|
||||
@ -1235,11 +1239,13 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
var text: String = ""
|
||||
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
|
||||
if index == 0, let author = author {
|
||||
return [.TextMention(peerId: author.id)]
|
||||
} else if index == 1 {
|
||||
return [.Bold]
|
||||
}
|
||||
return []
|
||||
}, to: &text, entities: &entities)
|
||||
@ -1259,11 +1265,39 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
var text: String = ""
|
||||
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
|
||||
if index == 0, let author = author {
|
||||
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 []
|
||||
}, to: &text, entities: &entities)
|
||||
|
@ -87,7 +87,7 @@ private func peerButtons(_ state: ChatPresentationInterfaceState) -> [ChatReport
|
||||
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) {
|
||||
buttons.append(.addMembers)
|
||||
} 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 {
|
||||
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
|
||||
}
|
||||
if canManageInvitations {
|
||||
@ -813,9 +813,13 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
|
||||
if currentInvitationsContext == nil {
|
||||
var canManageInvitations = false
|
||||
if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role {
|
||||
canManageInvitations = true
|
||||
} 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)) {
|
||||
if let group = peerViewMainPeer(peerView) as? TelegramGroup {
|
||||
if case .creator = group.role {
|
||||
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
|
||||
}
|
||||
if canManageInvitations {
|
||||
@ -1081,14 +1085,33 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
||||
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 user.isDeleted {
|
||||
return false
|
||||
}
|
||||
if let isContact = isContact, !isContact {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else if peer is TelegramChannel || peer is TelegramGroup {
|
||||
return true
|
||||
} else if let peer = peer as? TelegramChannel {
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
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.flags.contains(.isCreator) || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) {
|
||||
let discussionGroupTitle: String
|
||||
if let _ = data.cachedData as? CachedChannelData {
|
||||
if let peer = data.linkedDiscussionPeer {
|
||||
@ -1336,39 +1337,22 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
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) {
|
||||
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()
|
||||
@ -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: {
|
||||
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()
|
||||
(strongSelf.controller?.navigationController as? NavigationController)?.popToRoot(animated: true)
|
||||
} 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)
|
||||
@ -3875,6 +3870,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
private func editingOpenInviteLinksSetup() {
|
||||
|
||||
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 {
|
||||
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
|
||||
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))
|
||||
}
|
||||
if self.state.selectedMessageIds == nil {
|
||||
@ -6230,14 +6226,16 @@ func presentAddMembers(context: AccountContext, parentController: ViewController
|
||||
var canCreateInviteLink = false
|
||||
if let group = groupPeer as? TelegramGroup {
|
||||
switch group.role {
|
||||
case .creator, .admin:
|
||||
case .creator:
|
||||
canCreateInviteLink = true
|
||||
case let .admin(rights, _):
|
||||
canCreateInviteLink = rights.flags.contains(.canInviteUsers)
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if let channel = groupPeer as? TelegramChannel {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -13,14 +13,16 @@ import AppBundle
|
||||
public final class ReportPeerDetailsActionSheetItem: ActionSheetItem {
|
||||
let context: AccountContext
|
||||
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.placeholderText = placeholderText
|
||||
self.textUpdated = textUpdated
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -34,7 +36,7 @@ private final class ReportPeerDetailsActionSheetItemNode: ActionSheetItemNode {
|
||||
|
||||
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
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -48,7 +50,12 @@ private final class ReportPeerDetailsActionSheetItemNode: ActionSheetItemNode {
|
||||
|
||||
self.addSubnode(self.inputFieldNode)
|
||||
|
||||
// self.inputFieldNode.
|
||||
self.inputFieldNode.updateText = { text in
|
||||
textUpdated(text)
|
||||
}
|
||||
self.inputFieldNode.updateHeight = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
|
@ -195,7 +195,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 4
|
||||
self.originalRemainingSeconds = 3
|
||||
case let .banned(text):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user