Various fixes

This commit is contained in:
Ilya Laktyushin 2021-02-11 22:51:56 +04:00
parent fdbd3ceb7a
commit ad2678645d
40 changed files with 5557 additions and 5839 deletions

15
.gitattributes vendored Normal file
View 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

View File

@ -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";

View File

@ -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"/>

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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?.controller?.dismiss()
self?.actionDisposable.set((deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
}))
let _ = (deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
})
self?.controller?.revokedInvitationsContext?.remove(invite)
})
@ -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 {

View File

@ -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
}
}
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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

View File

@ -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)
})
}
})
})))

View File

@ -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()
}))

View File

@ -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)

View File

@ -117,8 +117,14 @@ private class SectionHeaderItemNode: ListViewItemNode {
strongSelf.headerNode = headerNode
}
headerNode.title = item.title
headerNode.action = item.additionalText.text
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)
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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) }

View File

@ -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 {

View File

@ -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>) {

View File

@ -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 {

View File

@ -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 {

View File

@ -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))
}
}

View File

@ -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)
}

View File

@ -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(

View File

@ -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(

View File

@ -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])

View File

@ -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)

View File

@ -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) {

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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