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_many" = "%@ people joined";
"InviteLink.PeopleJoined_any" = "%@ people joined"; "InviteLink.PeopleJoined_any" = "%@ people joined";
"InviteLink.CreatePrivateLinkHelp" = "Anyone who has Telegram installed will be able to join your group by following this link."; "InviteLink.CreatePrivateLinkHelp" = "Anyone who has Telegram installed will be able to join your group by following this link.";
"InviteLink.CreatePrivateLinkHelpChannel" = "Anyone who has Telegram installed will be able to join your channel by following this link.";
"InviteLink.Manage" = "Manage Invite Links"; "InviteLink.Manage" = "Manage Invite Links";
"InviteLink.PeopleJoinedShortNoneExpired" = "no one joined"; "InviteLink.PeopleJoinedShortNoneExpired" = "no one joined";
@ -5899,6 +5900,7 @@ Sorry for the inconvenience.";
"InviteLink.QRCode.Title" = "Invite by QR Code"; "InviteLink.QRCode.Title" = "Invite by QR Code";
"InviteLink.QRCode.Info" = "Everyone on Telegram can scan this code to join your group."; "InviteLink.QRCode.Info" = "Everyone on Telegram can scan this code to join your group.";
"InviteLink.QRCode.InfoChannel" = "Everyone on Telegram can scan this code to join your channel.";
"InviteLink.QRCode.Share" = "Share QR Code"; "InviteLink.QRCode.Share" = "Share QR Code";
"InviteLink.InviteLink" = "Invite Link"; "InviteLink.InviteLink" = "Invite Link";
@ -6021,6 +6023,9 @@ Sorry for the inconvenience.";
"Channel.AdminLog.DeletedInviteLink" = "%1$@ deleted invite link %2$@"; "Channel.AdminLog.DeletedInviteLink" = "%1$@ deleted invite link %2$@";
"Channel.AdminLog.RevokedInviteLink" = "%1$@ revoked invite link %2$@"; "Channel.AdminLog.RevokedInviteLink" = "%1$@ revoked invite link %2$@";
"Channel.AdminLog.EditedInviteLink" = "%1$@ edited invite link %2$@"; "Channel.AdminLog.EditedInviteLink" = "%1$@ edited invite link %2$@";
"Channel.AdminLog.CreatedInviteLink" = "%1$@ created invite link %2$@";
"Channel.AdminLog.JoinedViaInviteLink" = "%1$@ joined via invite link %2$@";
"GroupInfo.Permissions.BroadcastTitle" = "Broadcast Channel"; "GroupInfo.Permissions.BroadcastTitle" = "Broadcast Channel";
"GroupInfo.Permissions.BroadcastConvert" = "Convert Group to Channel"; "GroupInfo.Permissions.BroadcastConvert" = "Convert Group to Channel";
@ -6056,7 +6061,6 @@ Sorry for the inconvenience.";
"Report.Report" = "Report"; "Report.Report" = "Report";
"Report.Succeed" = "Telegram moderators will study your report. Thank you!"; "Report.Succeed" = "Telegram moderators will study your report. Thank you!";
"Conversation.AutoremoveRemainingTime" = "auto-delete in %@"; "Conversation.AutoremoveRemainingTime" = "auto-delete in %@";
"Conversation.AutoremoveRemainingDays_1" = "auto-delete in %@ day"; "Conversation.AutoremoveRemainingDays_1" = "auto-delete in %@ day";
"Conversation.AutoremoveRemainingDays_any" = "auto-delete in %@ days"; "Conversation.AutoremoveRemainingDays_any" = "auto-delete in %@ days";
@ -6066,3 +6070,7 @@ Sorry for the inconvenience.";
"Conversation.AutoremoveChanged" = "Auto-Delete timer set to %@"; "Conversation.AutoremoveChanged" = "Auto-Delete timer set to %@";
"PeerInfo.ReportProfilePhoto" = "Report Profile Photo";
"PeerInfo.ReportProfileVideo" = "Report Profile Video";
"Channel.AdminLog.CanInviteUsersViaLink" = "Invite Users via Link";

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="17156" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="umr-Wa-jBL"> <document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="17701" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="umr-Wa-jBL">
<device id="watch38"/> <device id="watch38"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="17034"/> <plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="17500"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--TGNeoConversationController--> <!--TGNeoConversationController-->
@ -1569,11 +1569,445 @@ contacts found.</string>
</objects> </objects>
<point key="canvasLocation" x="729" y="757.25"/> <point key="canvasLocation" x="729" y="757.25"/>
</scene> </scene>
<!--Static M-->
<scene sceneID="uW0-5W-J6c">
<objects>
<notificationController backgroundImage="BubbleNotification" spacing="0.0" id="JcB-1W-jcv" userLabel="Static M">
<items>
<group width="1" alignment="left" radius="0.0" id="11B-ry-7nl">
<items>
<label alignment="left" text="Text" numberOfLines="0" id="RMV-rW-0qs">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" style="UICTFontTextStyleBody"/>
</label>
</items>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<edgeInsets key="margins" left="8" right="8" top="10" bottom="11"/>
</group>
</items>
<notificationCategory key="notificationCategory" identifier="m" id="MXx-dC-nsP">
<color key="titleColor" red="0.10051588710000001" green="0.10051287709999999" blue="0.1005146056" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="sashColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</notificationCategory>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="4"/>
<connections>
<outlet property="notificationAlertLabel" destination="RMV-rW-0qs" id="buR-qh-N93"/>
<segue destination="RQh-4n-Jyy" kind="relationship" relationship="dynamicNotificationInterface" id="wDy-DD-bRl"/>
<segue destination="RQh-4n-Jyy" kind="relationship" relationship="dynamicInteractiveNotificationInterface" id="MKi-5u-rhv"/>
</connections>
</notificationController>
</objects>
<point key="canvasLocation" x="263" y="875"/>
</scene>
<!--Dynamic M-->
<scene sceneID="aFy-up-fB6">
<objects>
<controller backgroundImage="BubbleNotification" id="RQh-4n-Jyy" userLabel="Dynamic M" customClass="TGNotificationController">
<items>
<group width="1" alignment="left" layout="vertical" radius="0.0" spacing="0.0" id="Dd4-YD-hsp">
<items>
<group width="1" alignment="left" layout="vertical" spacing="0.0" id="ZlG-DJ-Ctv">
<items>
<label alignment="left" hidden="YES" text="Chat Title" id="frP-KX-c0b">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
</label>
<label alignment="left" hidden="YES" text="Name" id="JUM-Bm-hxM">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="16"/>
</label>
<group width="1" height="29" alignment="left" hidden="YES" layout="vertical" spacing="0.0" id="r84-Ll-prj">
<items>
<label alignment="left" verticalAlignment="center" text="Forwarded from" id="Br6-65-UTH" userLabel="ForwardTitle">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" pointSize="12"/>
</label>
<label alignment="left" verticalAlignment="center" text="Name" id="DI0-a4-1u0" userLabel="ForwardFrom">
<color key="textColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
</label>
</items>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
<group width="1" height="29" alignment="left" hidden="YES" spacing="4" id="Th4-sR-kDF" userLabel="ReplyHeader">
<items>
<group width="2" height="26" alignment="left" verticalAlignment="center" radius="0.0" spacing="0.0" id="n46-ZY-9jJ" userLabel="ReplyLine">
<color key="backgroundColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
<group width="26" height="26" alignment="left" verticalAlignment="center" radius="2" id="FmK-ex-jTs" userLabel="ReplyImage">
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.089999999999999997" colorSpace="custom" customColorSpace="sRGB"/>
</group>
<group width="1" alignment="left" layout="vertical" spacing="0.0" id="tQQ-et-qfm" userLabel="ReplyMessage">
<items>
<label alignment="left" text="Name" id="ifF-tf-ens" userLabel="ReplyAuthor">
<color key="textColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
</label>
<label alignment="left" text="Text" id="tWb-zc-3NN" userLabel="ReplyText">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" pointSize="12"/>
</label>
</items>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
</items>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
<label alignment="left" text="Text" numberOfLines="0" id="t9S-qg-lnm">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" style="UICTFontTextStyleBody"/>
</label>
</items>
<edgeInsets key="margins" left="8" right="8" top="0.0" bottom="5"/>
</group>
<group width="1" alignment="left" hidden="YES" layout="vertical" id="CMM-a2-p1K" userLabel="WrapperGroup">
<items>
<group width="1" alignment="left" layout="vertical" radius="10" spacing="0.0" id="r4K-n7-uKy" userLabel="LocationGroup">
<items>
<map height="92" alignment="left" id="PY6-r9-1nn"/>
</items>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
<group width="1" alignment="left" spacing="5" id="XJr-R0-HOA" userLabel="FileGroup">
<items>
<imageView width="26" height="26" alignment="left" verticalAlignment="center" hidden="YES" image="Location" contentMode="center" id="vZ3-mt-ICu" userLabel="VenueIcon">
<color key="tintColor" red="0.35566622019999999" green="0.68838506939999999" blue="0.91561108830000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</imageView>
<group width="26" height="26" alignment="left" verticalAlignment="center" hidden="YES" radius="13" spacing="0.0" id="IJt-Cp-BKm" userLabel="AudioGroup">
<items>
<imageView width="26" height="26" alignment="left" image="MediaAudioPlay" contentMode="center" id="PDu-Bz-gy5"/>
</items>
<color key="backgroundColor" red="0.35566622019999999" green="0.68838506939999999" blue="0.91561108830000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
<group width="26" height="26" alignment="left" verticalAlignment="center" radius="0.0" id="Gd3-ap-jO4" userLabel="FileIconGroup">
<items>
<imageView alignment="center" verticalAlignment="center" image="File.png" contentMode="center" id="dZu-99-pMR">
<color key="tintColor" red="0.14697439970000001" green="0.5607914329" blue="0.88162887099999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</imageView>
</items>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</group>
<group alignment="left" verticalAlignment="center" layout="vertical" spacing="0.0" id="DHa-mY-ceB" userLabel="FileMetaGroup">
<items>
<label alignment="left" text="File Name" id="tul-U8-7fj">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
</label>
<label alignment="left" text="Size" id="m2I-fn-zCe">
<color key="textColor" red="0.41865724329999998" green="0.41825520989999998" blue="0.4306421876" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" pointSize="12"/>
</label>
</items>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
</items>
<edgeInsets key="margins" left="6.5" right="6.5" top="2" bottom="1"/>
</group>
<group width="1" alignment="left" spacing="0.0" id="DLM-Wi-QFQ" userLabel="StickerWrapper">
<items>
<group width="0.5" height="64" alignment="left" contentMode="scaleAspectFit" id="7TZ-8f-EgD" userLabel="StickerGroup">
<variation key="device=watch42mm" height="72"/>
</group>
</items>
<edgeInsets key="margins" left="6.5" right="0.0" top="0.0" bottom="0.0"/>
</group>
<group width="1" alignment="left" radius="12" id="sxE-kX-fH5" userLabel="MediaGroup">
<items>
<group alignment="right" verticalAlignment="bottom" radius="10" id="GEf-6R-cw1" userLabel="DurationGroup">
<items>
<label alignment="left" text="2:34" id="wRk-Fm-UIl" userLabel="Duration">
<color key="textColor" red="0.2461894453" green="0.24618205430000001" blue="0.2461862564" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" pointSize="12"/>
</label>
</items>
<color key="backgroundColor" red="0.89292949440000002" green="0.91148859260000004" blue="0.93112039570000005" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="6" right="6" top="2" bottom="2"/>
</group>
</items>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.089999999999999997" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="4" right="4" top="4" bottom="4"/>
</group>
<group width="1" alignment="left" id="W49-eu-bIX" userLabel="CaptionGroup">
<items>
<label alignment="left" text="Caption" numberOfLines="0" id="3dY-xL-bio">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" pointSize="16"/>
</label>
</items>
<edgeInsets key="margins" left="8" right="8" top="4" bottom="4"/>
</group>
</items>
<edgeInsets key="margins" left="1.5" right="1.5" top="0.0" bottom="0.0"/>
</group>
</items>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="10" bottom="5"/>
</group>
</items>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="4"/>
<connections>
<outlet property="audioGroup" destination="IJt-Cp-BKm" id="UDG-gj-AJm"/>
<outlet property="captionGroup" destination="W49-eu-bIX" id="fLo-8u-qJ7"/>
<outlet property="captionLabel" destination="3dY-xL-bio" id="HPd-qv-6SR"/>
<outlet property="chatTitleLabel" destination="frP-KX-c0b" id="gQB-RH-eAC"/>
<outlet property="durationGroup" destination="GEf-6R-cw1" id="b6I-rw-1ma"/>
<outlet property="durationLabel" destination="wRk-Fm-UIl" id="OY4-mh-oGN"/>
<outlet property="fileGroup" destination="XJr-R0-HOA" id="jAA-6n-GcD"/>
<outlet property="fileIconGroup" destination="Gd3-ap-jO4" id="Gk0-pX-7xT"/>
<outlet property="forwardFromLabel" destination="DI0-a4-1u0" id="97d-FM-hvd"/>
<outlet property="forwardHeaderGroup" destination="r84-Ll-prj" id="eOS-0I-fH2"/>
<outlet property="forwardTitleLabel" destination="Br6-65-UTH" id="ZXg-hL-4Nt"/>
<outlet property="map" destination="PY6-r9-1nn" id="QtL-Ma-9i3"/>
<outlet property="mapGroup" destination="r4K-n7-uKy" id="QaM-0N-n60"/>
<outlet property="mediaGroup" destination="sxE-kX-fH5" id="oMl-K9-upS"/>
<outlet property="messageTextLabel" destination="t9S-qg-lnm" id="NTR-1N-c27"/>
<outlet property="nameLabel" destination="JUM-Bm-hxM" id="Eiz-Pp-a1C"/>
<outlet property="replyAuthorNameLabel" destination="ifF-tf-ens" id="NSF-eV-jEP"/>
<outlet property="replyHeaderGroup" destination="Th4-sR-kDF" id="SjQ-KI-BYD"/>
<outlet property="replyHeaderImageGroup" destination="FmK-ex-jTs" id="j7i-Sc-BUV"/>
<outlet property="replyMessageTextLabel" destination="tWb-zc-3NN" id="aSf-v0-kQf"/>
<outlet property="stickerGroup" destination="7TZ-8f-EgD" id="f6z-Rx-GYV"/>
<outlet property="stickerWrapperGroup" destination="DLM-Wi-QFQ" id="wXu-ff-inw"/>
<outlet property="subtitleLabel" destination="m2I-fn-zCe" id="WMT-u4-Tgp"/>
<outlet property="titleLabel" destination="tul-U8-7fj" id="VxJ-io-0DL"/>
<outlet property="venueIcon" destination="vZ3-mt-ICu" id="WvO-Db-hRw"/>
<outlet property="wrapperGroup" destination="CMM-a2-p1K" id="SSt-aA-79a"/>
</connections>
</controller>
</objects>
<point key="canvasLocation" x="503" y="874.75"/>
</scene>
<!--Static R-->
<scene sceneID="Aix-4r-fFK">
<objects>
<notificationController backgroundImage="BubbleNotification" spacing="0.0" id="crw-Qo-dti" userLabel="Static R">
<items>
<group width="1" alignment="left" layout="vertical" radius="0.0" id="RTo-Ed-NsD">
<items>
<label alignment="left" text="Text" numberOfLines="0" id="6f0-WK-Add">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" style="UICTFontTextStyleBody"/>
</label>
</items>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<edgeInsets key="margins" left="8" right="8" top="10" bottom="11"/>
</group>
</items>
<notificationCategory key="notificationCategory" id="pgn-NP-bt0">
<color key="titleColor" red="0.10051588710000001" green="0.10051287709999999" blue="0.1005146056" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="sashColor" red="1" green="0.99997437" blue="0.99999129769999995" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</notificationCategory>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="4"/>
<connections>
<outlet property="notificationAlertLabel" destination="6f0-WK-Add" id="ioT-Xv-KJp"/>
<segue destination="79a-X2-tmF" kind="relationship" relationship="dynamicNotificationInterface" id="dmG-n7-HwD"/>
<segue destination="79a-X2-tmF" kind="relationship" relationship="dynamicInteractiveNotificationInterface" id="UyA-Ra-UVy"/>
</connections>
</notificationController>
</objects>
<point key="canvasLocation" x="263" y="1281"/>
</scene>
<!--Dynamic R-->
<scene sceneID="K4P-nV-Yqh">
<objects>
<controller backgroundImage="BubbleNotification" spacing="0.0" id="79a-X2-tmF" userLabel="Dynamic R" customClass="TGNotificationController">
<items>
<group width="1" alignment="left" layout="vertical" radius="0.0" spacing="0.0" id="Go3-q1-pnJ">
<items>
<group width="1" alignment="left" layout="vertical" spacing="0.0" id="JAA-Ky-cyx">
<items>
<label alignment="left" hidden="YES" text="Chat Title" id="HtY-WB-aFI">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
</label>
<label alignment="left" hidden="YES" text="Name" id="Hqd-Pr-2zp">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="16"/>
</label>
<group width="1" height="29" alignment="left" hidden="YES" layout="vertical" spacing="0.0" id="Kzq-HH-8Ev">
<items>
<label alignment="left" verticalAlignment="center" text="Forwarded from" id="NTL-lz-9dd" userLabel="ForwardTitle">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" pointSize="12"/>
</label>
<label alignment="left" verticalAlignment="center" text="Name" id="B2R-Qa-MeP" userLabel="ForwardFrom">
<color key="textColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
</label>
</items>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
<group width="1" height="29" alignment="left" hidden="YES" spacing="4" id="KRV-O6-45y" userLabel="ReplyHeader">
<items>
<group width="2" height="26" alignment="left" verticalAlignment="center" radius="0.0" spacing="0.0" id="eW6-JQ-FAY" userLabel="ReplyLine">
<color key="backgroundColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
<group width="26" height="26" alignment="left" verticalAlignment="center" radius="2" id="swd-Nx-MDo" userLabel="ReplyImage">
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.089999999999999997" colorSpace="custom" customColorSpace="sRGB"/>
</group>
<group width="1" alignment="left" layout="vertical" spacing="0.0" id="h1Y-se-IEW" userLabel="ReplyMessage">
<items>
<label alignment="left" text="Name" id="g6r-Nm-SOQ" userLabel="ReplyAuthor">
<color key="textColor" red="0.1131299585" green="0.50641471149999995" blue="0.96399867530000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
</label>
<label alignment="left" text="Text" id="dAz-x8-jbh" userLabel="ReplyText">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" pointSize="12"/>
</label>
</items>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
</items>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
<label alignment="left" text="Text" numberOfLines="0" id="wxg-r7-aSG">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" style="UICTFontTextStyleBody"/>
</label>
</items>
<edgeInsets key="margins" left="8" right="8" top="0.0" bottom="5"/>
</group>
<group width="1" alignment="left" hidden="YES" layout="vertical" id="0ch-zY-dJX" userLabel="WrapperGroup">
<items>
<group width="1" alignment="left" layout="vertical" radius="10" spacing="0.0" id="vel-8f-OFm" userLabel="LocationGroup">
<items>
<map height="92" alignment="left" id="k4c-6T-xVa"/>
</items>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
<group width="1" alignment="left" spacing="5" id="pRF-iD-Gt4" userLabel="FileGroup">
<items>
<imageView width="26" height="26" alignment="left" verticalAlignment="center" hidden="YES" image="Location" contentMode="center" id="uxs-Nx-we9" userLabel="VenueIcon">
<color key="tintColor" red="0.35566622019999999" green="0.68838506939999999" blue="0.91561108830000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</imageView>
<group width="26" height="26" alignment="left" verticalAlignment="center" hidden="YES" radius="13" spacing="0.0" id="Y04-zP-Wh2" userLabel="AudioGroup">
<items>
<imageView width="26" height="26" alignment="left" image="MediaAudioPlay" contentMode="center" id="pcd-Ly-8eO"/>
</items>
<color key="backgroundColor" red="0.35566622019999999" green="0.68838506939999999" blue="0.91561108830000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
<group width="26" height="26" alignment="left" verticalAlignment="center" radius="0.0" id="vs3-R3-hff" userLabel="FileIconGroup">
<items>
<imageView alignment="center" verticalAlignment="center" image="File.png" contentMode="center" id="XqZ-BE-Abt">
<color key="tintColor" red="0.14697439970000001" green="0.5607914329" blue="0.88162887099999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</imageView>
</items>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</group>
<group alignment="left" verticalAlignment="center" layout="vertical" spacing="0.0" id="7Mg-3H-okj" userLabel="FileMetaGroup">
<items>
<label alignment="left" text="File Name" id="xUk-Hc-qsr">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="12"/>
</label>
<label alignment="left" text="Size" id="d5k-bL-6BP">
<color key="textColor" red="0.41865724329999998" green="0.41825520989999998" blue="0.4306421876" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" pointSize="12"/>
</label>
</items>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
</group>
</items>
<edgeInsets key="margins" left="6.5" right="6.5" top="2" bottom="1"/>
</group>
<group width="1" alignment="left" spacing="0.0" id="zqd-Tm-ZRg" userLabel="StickerWrapper">
<items>
<group width="84" height="84" alignment="left" contentMode="scaleAspectFit" id="CH0-jD-uni" userLabel="StickerGroup">
<variation key="device=watch38mm" height="72" width="72"/>
<variation key="device=watch40mm" height="88" width="88"/>
<variation key="device=watch44mm" height="100" width="100"/>
</group>
</items>
<edgeInsets key="margins" left="6.5" right="0.0" top="0.0" bottom="0.0"/>
</group>
<group width="1" alignment="left" radius="12" id="hTA-bS-Jf1" userLabel="MediaGroup">
<items>
<group alignment="right" verticalAlignment="bottom" radius="10" id="eEx-yh-cyr" userLabel="DurationGroup">
<items>
<label alignment="left" text="2:34" id="RiW-Br-zCj" userLabel="Duration">
<color key="textColor" red="0.2461894453" green="0.24618205430000001" blue="0.2461862564" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" pointSize="12"/>
</label>
</items>
<color key="backgroundColor" red="0.89292949440000002" green="0.91148859260000004" blue="0.93112039570000005" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="6" right="6" top="2" bottom="2"/>
</group>
</items>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.089999999999999997" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="4" right="4" top="4" bottom="4"/>
</group>
<group width="1" alignment="left" id="0B5-0H-Py3" userLabel="CaptionGroup">
<items>
<label alignment="left" text="Caption" numberOfLines="0" id="isV-kh-Mdr">
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="font" type="system" pointSize="16"/>
</label>
</items>
<edgeInsets key="margins" left="8" right="8" top="4" bottom="4"/>
</group>
</items>
<edgeInsets key="margins" left="1.5" right="1.5" top="0.0" bottom="0.0"/>
</group>
</items>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="10" bottom="5"/>
</group>
</items>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<edgeInsets key="margins" left="0.0" right="0.0" top="0.0" bottom="4"/>
<connections>
<outlet property="audioGroup" destination="Y04-zP-Wh2" id="qTp-zF-Epj"/>
<outlet property="captionGroup" destination="0B5-0H-Py3" id="jwq-FC-JCS"/>
<outlet property="captionLabel" destination="isV-kh-Mdr" id="egK-Dy-gRD"/>
<outlet property="chatTitleLabel" destination="HtY-WB-aFI" id="sSF-mw-4JJ"/>
<outlet property="durationGroup" destination="eEx-yh-cyr" id="8Qk-ml-oO8"/>
<outlet property="durationLabel" destination="RiW-Br-zCj" id="19H-az-0O6"/>
<outlet property="fileGroup" destination="pRF-iD-Gt4" id="I5Q-Fx-UjF"/>
<outlet property="fileIconGroup" destination="vs3-R3-hff" id="MGI-5h-Lv8"/>
<outlet property="forwardFromLabel" destination="B2R-Qa-MeP" id="kPS-wB-hNR"/>
<outlet property="forwardHeaderGroup" destination="Kzq-HH-8Ev" id="bhU-ZI-H0w"/>
<outlet property="forwardTitleLabel" destination="NTL-lz-9dd" id="CCG-3i-IZ9"/>
<outlet property="map" destination="k4c-6T-xVa" id="2aI-Tb-Oqf"/>
<outlet property="mapGroup" destination="vel-8f-OFm" id="mDw-hR-bxF"/>
<outlet property="mediaGroup" destination="hTA-bS-Jf1" id="nQw-W2-W4B"/>
<outlet property="messageTextLabel" destination="wxg-r7-aSG" id="yS9-nx-01R"/>
<outlet property="nameLabel" destination="Hqd-Pr-2zp" id="ija-IM-5QH"/>
<outlet property="replyAuthorNameLabel" destination="g6r-Nm-SOQ" id="dK1-3l-hWN"/>
<outlet property="replyHeaderGroup" destination="KRV-O6-45y" id="RZE-Vt-aui"/>
<outlet property="replyHeaderImageGroup" destination="swd-Nx-MDo" id="aYu-g3-upQ"/>
<outlet property="replyMessageTextLabel" destination="dAz-x8-jbh" id="6Cn-zA-Kcs"/>
<outlet property="stickerGroup" destination="CH0-jD-uni" id="CcD-rg-vXX"/>
<outlet property="stickerWrapperGroup" destination="zqd-Tm-ZRg" id="kps-Rx-vsc"/>
<outlet property="subtitleLabel" destination="d5k-bL-6BP" id="7va-0d-xfX"/>
<outlet property="titleLabel" destination="xUk-Hc-qsr" id="ck7-ks-w9e"/>
<outlet property="venueIcon" destination="uxs-Nx-we9" id="ab7-gA-XrO"/>
<outlet property="wrapperGroup" destination="0ch-zY-dJX" id="YqT-J5-azh"/>
</connections>
</controller>
</objects>
<point key="canvasLocation" x="503" y="1283"/>
</scene>
</scenes> </scenes>
<inferredMetricsTieBreakers>
<segue reference="UyA-Ra-UVy"/>
<segue reference="wDy-DD-bRl"/>
</inferredMetricsTieBreakers>
<color key="tintColor" red="0.15550534427165985" green="0.57037848234176636" blue="0.8720671534538269" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.15550534427165985" green="0.57037848234176636" blue="0.8720671534538269" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<resources> <resources>
<image name="BotCommandIcon" width="128" height="128"/> <image name="BotCommandIcon" width="128" height="128"/>
<image name="BubbleNotification" width="128" height="128"/>
<image name="File.png" width="128" height="128"/> <image name="File.png" width="128" height="128"/>
<image name="Location" width="128" height="128"/>
<image name="LocationIcon" width="128" height="128"/> <image name="LocationIcon" width="128" height="128"/>
<image name="LoginIcon" width="128" height="128"/> <image name="LoginIcon" width="128" height="128"/>
<image name="MediaAudioPlay" width="128" height="128"/> <image name="MediaAudioPlay" width="128" height="128"/>

View File

@ -10,15 +10,15 @@ public func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModif
let replacementColors: [UIColor] let replacementColors: [UIColor]
switch fitzModifier { switch fitzModifier {
case .type12: case .type12:
replacementColors = [0xca907a, 0xedc5a5, 0xf7e3c3, 0xfbefd6].map { UIColor(rgb: $0) } replacementColors = [0xcb7b55, 0xf6b689, 0xffcda7, 0xffdfc5].map { UIColor(rgb: $0) }
case .type3: case .type3:
replacementColors = [0xaa7c60, 0xc8a987, 0xddc89f, 0xe6d6b2].map { UIColor(rgb: $0) } replacementColors = [0xa45a38, 0xdf986b, 0xedb183, 0xf4c3a0].map { UIColor(rgb: $0) }
case .type4: case .type4:
replacementColors = [0x8c6148, 0xad8562, 0xc49e76, 0xd4b188].map { UIColor(rgb: $0) } replacementColors = [0x703a17, 0xab673d, 0xc37f4e, 0xd89667].map { UIColor(rgb: $0) }
case .type5: case .type5:
replacementColors = [0x6e3c2c, 0x925a34, 0xa16e46, 0xac7a52].map { UIColor(rgb: $0) } replacementColors = [0x4a2409, 0x7d3e0e, 0x965529, 0xa96337].map { UIColor(rgb: $0) }
case .type6: case .type6:
replacementColors = [0x291c12, 0x472a22, 0x573b30, 0x68493c].map { UIColor(rgb: $0) } replacementColors = [0x200f0a, 0x412924, 0x593d37, 0x63453f].map { UIColor(rgb: $0) }
} }
func colorToString(_ color: UIColor) -> String { func colorToString(_ color: UIColor) -> String {

View File

@ -835,7 +835,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
if let strongSelf = self { if let strongSelf = self {
// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start() let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start()
strongSelf.updateState { state in strongSelf.updateState { state in
@ -859,7 +858,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
if let strongSelf = self { if let strongSelf = self {
// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start() let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start()
strongSelf.updateState { state in strongSelf.updateState { state in

View File

@ -307,23 +307,6 @@ public final class DatePickerNode: ASDisplayNode {
public var valueUpdated: ((Date) -> Void)? public var valueUpdated: ((Date) -> Void)?
public var maximumDate: Date {
get {
return self.state.maxDate
}
set {
guard newValue != self.maximumDate else {
return
}
let updatedState = State(minDate: self.state.minDate, maxDate: newValue, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
self.updateState(updatedState, animated: false)
if let size = self.validLayout {
let _ = self.updateLayout(size: size, transition: .immediate)
}
}
}
public var minimumDate: Date { public var minimumDate: Date {
get { get {
return self.state.minDate return self.state.minDate
@ -336,11 +319,34 @@ public final class DatePickerNode: ASDisplayNode {
let updatedState = State(minDate: newValue, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth) let updatedState = State(minDate: newValue, maxDate: self.state.maxDate, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
self.updateState(updatedState, animated: false) self.updateState(updatedState, animated: false)
self.pickerNode.minimumDate = newValue
if let size = self.validLayout { if let size = self.validLayout {
let _ = self.updateLayout(size: size, transition: .immediate) let _ = self.updateLayout(size: size, transition: .immediate)
} }
} }
} }
public var maximumDate: Date {
get {
return self.state.maxDate
}
set {
guard newValue != self.maximumDate else {
return
}
let updatedState = State(minDate: self.state.minDate, maxDate: newValue, date: self.state.date, displayingMonthSelection: self.state.displayingMonthSelection, selectedMonth: self.state.selectedMonth)
self.updateState(updatedState, animated: false)
self.pickerNode.maximumDate = newValue
if let size = self.validLayout {
let _ = self.updateLayout(size: size, transition: .immediate)
}
}
}
public var date: Date { public var date: Date {
get { get {
return self.state.date return self.state.date
@ -385,6 +391,8 @@ public final class DatePickerNode: ASDisplayNode {
self.pickerNode = MonthPickerNode(theme: theme, strings: strings, date: self.state.date, yearRange: yearRange(for: self.state), valueChanged: { date in self.pickerNode = MonthPickerNode(theme: theme, strings: strings, date: self.state.date, yearRange: yearRange(for: self.state), valueChanged: { date in
monthChangedImpl?(date) monthChangedImpl?(date)
}) })
self.pickerNode.minimumDate = self.state.minDate
self.pickerNode.maximumDate = self.state.maxDate
self.monthButtonNode = HighlightTrackingButtonNode() self.monthButtonNode = HighlightTrackingButtonNode()
self.monthTextNode = ImmediateTextNode() self.monthTextNode = ImmediateTextNode()
@ -768,8 +776,8 @@ private final class MonthPickerNode: ASDisplayNode, UIPickerViewDelegate, UIPick
} }
} }
var minDate: Date? var minimumDate: Date?
var maxDate: Date? var maximumDate: Date?
private let valueChanged: (Date) -> Void private let valueChanged: (Date) -> Void
private let pickerView: UIPickerView private let pickerView: UIPickerView
@ -847,7 +855,26 @@ private final class MonthPickerNode: ASDisplayNode, UIPickerViewDelegate, UIPick
let numberOfDays = calendar.range(of: .day, in: .month, for: tempDate)!.count let numberOfDays = calendar.range(of: .day, in: .month, for: tempDate)!.count
components.day = min(day, numberOfDays) components.day = min(day, numberOfDays)
let date = calendar.date(from: components)! var date = calendar.date(from: components)!
if let minimumDate = self.minimumDate, let maximumDate = self.maximumDate {
var changed = false
if date < minimumDate {
date = minimumDate
changed = true
}
if date > maximumDate {
date = maximumDate
changed = true
}
if changed {
let month = calendar.component(.month, from: date)
let year = calendar.component(.year, from: date)
self.pickerView.selectRow(month - 1, inComponent: 0, animated: true)
self.pickerView.selectRow(year - yearRange.startIndex, inComponent: 1, animated: true)
}
}
self.date = date self.date = date
self.valueChanged(date) self.valueChanged(date)
@ -924,7 +951,7 @@ private final class TimePickerNode: ASDisplayNode {
let minutes = Int32(calendar.component(.hour, from: self.date)) let minutes = Int32(calendar.component(.hour, from: self.date))
let string = stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: self.dateTimeFormat).replacingOccurrences(of: " AM", with: "").replacingOccurrences(of: " PM", with: "") let string = stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: self.dateTimeFormat).replacingOccurrences(of: " AM", with: "").replacingOccurrences(of: " PM", with: "")
self.textNode.attributedText = NSAttributedString(string: string, font: Font.with(size: 21.0, design: .monospace, weight: .regular, traits: []), textColor: self.theme.textColor) self.textNode.attributedText = NSAttributedString(string: string, font: Font.with(size: 21.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]), textColor: self.theme.textColor)
let textSize = self.textNode.updateLayout(size) let textSize = self.textNode.updateLayout(size)
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.backgroundNode.frame.width - textSize.width) / 2.0), y: floorToScreenPixels((self.backgroundNode.frame.height - textSize.height) / 2.0)), size: textSize) self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.backgroundNode.frame.width - textSize.width) / 2.0), y: floorToScreenPixels((self.backgroundNode.frame.height - textSize.height) / 2.0)), size: textSize)

View File

@ -48,14 +48,12 @@ private struct InviteLinkInviteTransaction {
private enum InviteLinkInviteEntryId: Hashable { private enum InviteLinkInviteEntryId: Hashable {
case header case header
case mainLink case mainLink
case links(Int32)
case manage case manage
} }
private enum InviteLinkInviteEntry: Comparable, Identifiable { private enum InviteLinkInviteEntry: Comparable, Identifiable {
case header(PresentationTheme, String, String) case header(PresentationTheme, String, String)
case mainLink(PresentationTheme, ExportedInvitation) case mainLink(PresentationTheme, ExportedInvitation)
case links(Int32, PresentationTheme, [ExportedInvitation])
case manage(PresentationTheme, String, Bool) case manage(PresentationTheme, String, Bool)
var stableId: InviteLinkInviteEntryId { var stableId: InviteLinkInviteEntryId {
@ -64,8 +62,6 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
return .header return .header
case .mainLink: case .mainLink:
return .mainLink return .mainLink
case let .links(index, _, _):
return .links(index)
case .manage: case .manage:
return .manage return .manage
} }
@ -85,12 +81,6 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .links(lhsIndex, lhsTheme, lhsInvitations):
if case let .links(rhsIndex, rhsTheme, rhsInvitations) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsInvitations == rhsInvitations {
return true
} else {
return false
}
case let .manage(lhsTheme, lhsText, lhsStandalone): case let .manage(lhsTheme, lhsText, lhsStandalone):
if case let .manage(rhsTheme, rhsText, rhsStandalone) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStandalone == rhsStandalone { if case let .manage(rhsTheme, rhsText, rhsStandalone) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStandalone == rhsStandalone {
return true return true
@ -106,28 +96,19 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
switch rhs { switch rhs {
case .header: case .header:
return false return false
case .mainLink, .links, .manage: case .mainLink, .manage:
return true return true
} }
case .mainLink: case .mainLink:
switch rhs { switch rhs {
case .header, .mainLink: case .header, .mainLink:
return false return false
case .links, .manage:
return true
}
case let .links(lhsIndex, _, _):
switch rhs {
case .header, .mainLink:
return false
case let .links(rhsIndex, _, _):
return lhsIndex < rhsIndex
case .manage: case .manage:
return true return true
} }
case .manage: case .manage:
switch rhs { switch rhs {
case .header, .mainLink, .links: case .header, .mainLink:
return false return false
case .manage: case .manage:
return true return true
@ -148,12 +129,6 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
interaction.mainLinkContextAction(invite, node, nil) interaction.mainLinkContextAction(invite, node, nil)
}, viewAction: { }, viewAction: {
}) })
case let .links(_, _, invites):
return ItemListInviteLinkGridItem(presentationData: ItemListPresentationData(presentationData), invites: invites, count: 0, share: true, sectionId: 1, style: .plain, tapAction: { invite in
interaction.copyLink(invite)
}, contextAction: { invite, _ in
interaction.shareLink(invite)
})
case let .manage(theme, text, standalone): case let .manage(theme, text, standalone):
return InviteLinkInviteManageItem(theme: theme, text: text, standalone: standalone, action: { return InviteLinkInviteManageItem(theme: theme, text: text, standalone: standalone, action: {
interaction.manageLinks() interaction.manageLinks()
@ -372,8 +347,17 @@ public final class InviteLinkInviteController: ViewController {
f(.dismissWithoutContent) f(.dismissWithoutContent)
if let invite = invite { if let invite = invite {
let controller = InviteLinkQRCodeController(context: context, invite: invite) let _ = (context.account.postbox.loadedPeerWithId(peerId)
self?.controller?.present(controller, in: .window(.root)) |> deliverOnMainQueue).start(next: { [weak self] peer in
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
let controller = InviteLinkQRCodeController(context: context, invite: invite, isGroup: isGroup)
self?.controller?.present(controller, in: .window(.root))
})
} }
}))) })))
@ -392,10 +376,6 @@ public final class InviteLinkInviteController: ViewController {
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction() dismissAction()
// revokePeerExportedInvitation(account: <#T##Account#>, peerId: <#T##PeerId#>, link: <#T##String#>)
// self?.revokeDisposable.set((revokePersistentPeerExportedInvitation(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
//
// }))
}) })
]), ]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
@ -430,7 +410,13 @@ public final class InviteLinkInviteController: ViewController {
if let strongSelf = self { if let strongSelf = self {
var entries: [InviteLinkInviteEntry] = [] var entries: [InviteLinkInviteEntry] = []
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_InviteLink, presentationData.strings.InviteLink_CreatePrivateLinkHelp)) let helpText: String
if let peer = peerViewMainPeer(view) as? TelegramChannel, case .broadcast = peer.info {
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelpChannel
} else {
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
}
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_InviteLink, helpText))
let mainInvite: ExportedInvitation? let mainInvite: ExportedInvitation?
if let cachedData = view.cachedData as? CachedGroupData, let invite = cachedData.exportedInvitation { if let cachedData = view.cachedData as? CachedGroupData, let invite = cachedData.exportedInvitation {
@ -444,19 +430,7 @@ public final class InviteLinkInviteController: ViewController {
entries.append(.mainLink(presentationData.theme, mainInvite)) entries.append(.mainLink(presentationData.theme, mainInvite))
} }
let additionalInvites = invites.invitations.filter { $0.link != mainInvite?.link } entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, true))
var index: Int32 = 0
for i in stride(from: 0, to: additionalInvites.endIndex, by: 2) {
var invitesPair: [ExportedInvitation] = []
invitesPair.append(additionalInvites[i])
if i + 1 < additionalInvites.count {
invitesPair.append(additionalInvites[i + 1])
}
entries.append(.links(index, presentationData.theme, invitesPair))
index += 1
}
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, additionalInvites.isEmpty))
let previousEntries = previousEntries.swap(entries) let previousEntries = previousEntries.swap(entries)

View File

@ -70,7 +70,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
case revokedLinksHeader(PresentationTheme, String) case revokedLinksHeader(PresentationTheme, String)
case revokedLinksDeleteAll(PresentationTheme, String) case revokedLinksDeleteAll(PresentationTheme, String)
case revokedLinks(Int32, PresentationTheme, ExportedInvitation?) case revokedLink(Int32, PresentationTheme, ExportedInvitation?)
case adminsHeader(PresentationTheme, String) case adminsHeader(PresentationTheme, String)
case admin(Int32, PresentationTheme, ExportedInvitationCreator) case admin(Int32, PresentationTheme, ExportedInvitationCreator)
@ -83,7 +83,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return InviteLinksListSection.mainLink.rawValue return InviteLinksListSection.mainLink.rawValue
case .linksHeader, .linksCreate, .link, .linksInfo: case .linksHeader, .linksCreate, .link, .linksInfo:
return InviteLinksListSection.links.rawValue return InviteLinksListSection.links.rawValue
case .revokedLinksHeader, .revokedLinksDeleteAll, .revokedLinks: case .revokedLinksHeader, .revokedLinksDeleteAll, .revokedLink:
return InviteLinksListSection.revokedLinks.rawValue return InviteLinksListSection.revokedLinks.rawValue
case .adminsHeader, .admin: case .adminsHeader, .admin:
return InviteLinksListSection.admins.rawValue return InviteLinksListSection.admins.rawValue
@ -112,7 +112,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return 10001 return 10001
case .revokedLinksDeleteAll: case .revokedLinksDeleteAll:
return 10002 return 10002
case let .revokedLinks(index, _, _): case let .revokedLink(index, _, _):
return 10003 + index return 10003 + index
case .adminsHeader: case .adminsHeader:
return 20001 return 20001
@ -183,8 +183,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .revokedLinks(lhsIndex, lhsTheme, lhsLink): case let .revokedLink(lhsIndex, lhsTheme, lhsLink):
if case let .revokedLinks(rhsIndex, rhsTheme, rhsLink) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink { if case let .revokedLink(rhsIndex, rhsTheme, rhsLink) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink {
return true return true
} else { } else {
return false return false
@ -253,7 +253,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, color: .destructive, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, color: .destructive, editing: false, action: {
arguments.deleteAllRevokedLinks() arguments.deleteAllRevokedLinks()
}) })
case let .revokedLinks(_, _, invite): case let .revokedLink(_, _, invite):
return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
arguments.openLink(invite) arguments.openLink(invite)
} contextAction: { invite, node, gesture in } contextAction: { invite, node, gesture in
@ -273,7 +273,13 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
var entries: [InviteLinksListEntry] = [] var entries: [InviteLinksListEntry] = []
if admin == nil { if admin == nil {
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_CreatePrivateLinkHelp)) let helpText: String
if let peer = peerViewMainPeer(view) as? TelegramChannel, case .broadcast = peer.info {
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelpChannel
} else {
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
}
entries.append(.header(presentationData.theme, helpText))
} }
let mainInvite: ExportedInvitation? let mainInvite: ExportedInvitation?
@ -308,21 +314,32 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
entries.append(.mainLinkOtherInfo(presentationData.theme, string.0)) entries.append(.mainLinkOtherInfo(presentationData.theme, string.0))
} }
entries.append(.linksHeader(presentationData.theme, presentationData.strings.InviteLink_AdditionalLinks.uppercased()))
if admin == nil {
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
}
var additionalInvites: [ExportedInvitation]? var additionalInvites: [ExportedInvitation]?
if let invites = invites { if let invites = invites {
additionalInvites = invites.filter { $0.link != mainInvite?.link } additionalInvites = invites.filter { $0.link != mainInvite?.link }
} }
var hasLinks = false
if let additionalInvites = additionalInvites, !additionalInvites.isEmpty {
hasLinks = true
} else if let admin = admin, admin.count > 1 {
hasLinks = true
}
if hasLinks {
entries.append(.linksHeader(presentationData.theme, presentationData.strings.InviteLink_AdditionalLinks.uppercased()))
}
if admin == nil {
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
}
if let additionalInvites = additionalInvites { if let additionalInvites = additionalInvites {
var index: Int32 = 0 var index: Int32 = 0
for invite in additionalInvites { for invite in additionalInvites {
entries.append(.link(index, presentationData.theme, invite, invite.expireDate != nil ? tick : nil)) entries.append(.link(index, presentationData.theme, invite, invite.expireDate != nil ? tick : nil))
index += 1 index += 1
} }
} else if let admin = admin, admin.count > 0 { } else if let admin = admin, admin.count > 1 {
var index: Int32 = 0 var index: Int32 = 0
for _ in 0 ..< admin.count - 1 { for _ in 0 ..< admin.count - 1 {
entries.append(.link(index, presentationData.theme, nil, nil)) entries.append(.link(index, presentationData.theme, nil, nil))
@ -340,7 +357,13 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
} }
var index: Int32 = 0 var index: Int32 = 0
for invite in revokedInvites { for invite in revokedInvites {
entries.append(.revokedLinks(index, presentationData.theme, invite)) entries.append(.revokedLink(index, presentationData.theme, invite))
index += 1
}
} else if let admin = admin, admin.revokedCount > 0 {
var index: Int32 = 0
for _ in 0 ..< admin.revokedCount {
entries.append(.revokedLink(index, presentationData.theme, nil))
index += 1 index += 1
} }
} }
@ -387,7 +410,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
var getControllerImpl: (() -> ViewController?)? var getControllerImpl: (() -> ViewController?)?
let adminId = admin?.peer.peer?.id let adminId = admin?.peer.peer?.id
let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: false, forceUpdate: false) let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: false, forceUpdate: true)
let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: true, forceUpdate: true) let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: true, forceUpdate: true)
let creators: Signal<[ExportedInvitationCreator], NoError> let creators: Signal<[ExportedInvitationCreator], NoError>
@ -435,8 +458,17 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
let controller = InviteLinkQRCodeController(context: context, invite: invite) let _ = (context.account.postbox.loadedPeerWithId(peerId)
presentControllerImpl?(controller, nil) |> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
let controller = InviteLinkQRCodeController(context: context, invite: invite, isGroup: isGroup)
presentControllerImpl?(controller, nil)
})
}))) })))
if invite.adminId.toInt64() != 0 { if invite.adminId.toInt64() != 0 {
@ -545,8 +577,17 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
}, action: { _, f in }, action: { _, f in
f(.default) f(.default)
let controller = InviteLinkQRCodeController(context: context, invite: invite) let _ = (context.account.postbox.loadedPeerWithId(peerId)
presentControllerImpl?(controller, nil) |> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
let controller = InviteLinkQRCodeController(context: context, invite: invite, isGroup: isGroup)
presentControllerImpl?(controller, nil)
})
}))) })))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextEdit, icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextEdit, icon: { theme in

View File

@ -37,14 +37,16 @@ public final class InviteLinkQRCodeController: ViewController {
private let context: AccountContext private let context: AccountContext
private let invite: ExportedInvitation private let invite: ExportedInvitation
private let isGroup: Bool
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private let idleTimerExtensionDisposable = MetaDisposable() private let idleTimerExtensionDisposable = MetaDisposable()
public init(context: AccountContext, invite: ExportedInvitation) { public init(context: AccountContext, invite: ExportedInvitation, isGroup: Bool) {
self.context = context self.context = context
self.invite = invite self.invite = invite
self.isGroup = isGroup
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
@ -74,7 +76,7 @@ public final class InviteLinkQRCodeController: ViewController {
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = Node(context: self.context, invite: self.invite) self.displayNode = Node(context: self.context, invite: self.invite, isGroup: self.isGroup)
self.controllerNode.dismiss = { [weak self] in self.controllerNode.dismiss = { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil) self?.presentingViewController?.dismiss(animated: false, completion: nil)
} }
@ -133,7 +135,7 @@ public final class InviteLinkQRCodeController: ViewController {
var dismiss: (() -> Void)? var dismiss: (() -> Void)?
var cancel: (() -> Void)? var cancel: (() -> Void)?
init(context: AccountContext, invite: ExportedInvitation) { init(context: AccountContext, invite: ExportedInvitation, isGroup: Bool) {
self.context = context self.context = context
self.invite = invite self.invite = invite
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -211,7 +213,7 @@ public final class InviteLinkQRCodeController: ViewController {
let textFont = Font.regular(13.0) let textFont = Font.regular(13.0)
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_QRCode_Info, font: textFont, textColor: secondaryTextColor) self.textNode.attributedText = NSAttributedString(string: isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel, font: textFont, textColor: secondaryTextColor)
self.buttonNode.title = self.presentationData.strings.InviteLink_QRCode_Share self.buttonNode.title = self.presentationData.strings.InviteLink_QRCode_Share
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)

View File

@ -343,7 +343,6 @@ public final class InviteLinkViewController: ViewController {
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private var disposable: Disposable? private var disposable: Disposable?
private let actionDisposable = MetaDisposable()
private let dimNode: ASDisplayNode private let dimNode: ASDisplayNode
private let contentNode: ASDisplayNode private let contentNode: ASDisplayNode
@ -473,9 +472,10 @@ public final class InviteLinkViewController: ViewController {
ActionSheetTextItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Text), ActionSheetTextItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Action, color: .destructive, action: { ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Action, color: .destructive, action: {
dismissAction() dismissAction()
self?.controller?.dismiss()
self?.actionDisposable.set((deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
})) let _ = (deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
})
self?.controller?.revokedInvitationsContext?.remove(invite) self?.controller?.revokedInvitationsContext?.remove(invite)
}) })
@ -490,7 +490,42 @@ public final class InviteLinkViewController: ViewController {
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
let controller = InviteLinkQRCodeController(context: context, invite: invite) let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { [weak self] peer in
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
let controller = InviteLinkQRCodeController(context: context, invite: invite, isGroup: isGroup)
self?.controller?.present(controller, in: .window(.root))
})
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
self?.controller?.dismiss()
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
})
self?.controller?.revokedInvitationsContext?.remove(invite)
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
self?.controller?.present(controller, in: .window(.root)) self?.controller?.present(controller, in: .window(.root))
}))) })))
} }
@ -739,6 +774,7 @@ public final class InviteLinkViewController: ViewController {
subtitleText = self.presentationData.strings.InviteLink_Revoked subtitleText = self.presentationData.strings.InviteLink_Revoked
} else if let usageLimit = self.invite.usageLimit, let count = self.invite.count, count >= usageLimit { } else if let usageLimit = self.invite.usageLimit, let count = self.invite.count, count >= usageLimit {
subtitleText = self.presentationData.strings.InviteLink_UsageLimitReached subtitleText = self.presentationData.strings.InviteLink_UsageLimitReached
subtitleColor = self.presentationData.theme.list.itemDestructiveColor
} else if let expireDate = self.invite.expireDate { } else if let expireDate = self.invite.expireDate {
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if currentTime >= expireDate { if currentTime >= expireDate {

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 { if let expireDate = invite.expireDate, currentTime >= expireDate {
isExpired = true isExpired = true
} }
var isFull = false
if let usageLimit = invite.usageLimit { if let usageLimit = invite.usageLimit {
if !isExpired { if !isExpired {
@ -376,6 +377,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
timerValue = .fraction(fraction) timerValue = .fraction(fraction)
} }
} else if remaining == 0 { } else if remaining == 0 {
isFull = true
if !subtitleText.isEmpty { if !subtitleText.isEmpty {
subtitleText += "" subtitleText += ""
} }
@ -383,7 +385,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
} }
} }
} }
if let expireDate = invite.expireDate { if let expireDate = invite.expireDate, !isFull {
if !isExpired { if !isExpired {
if !subtitleText.isEmpty { if !subtitleText.isEmpty {
subtitleText += "" subtitleText += ""
@ -570,7 +572,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
timerNode = TimerNode() timerNode = TimerNode()
timerNode.isUserInteractionEnabled = false timerNode.isUserInteractionEnabled = false
strongSelf.timerNode = timerNode strongSelf.timerNode = timerNode
strongSelf.addSubnode(timerNode) strongSelf.offsetContainerNode.addSubnode(timerNode)
} }
timerNode.update(color: iconColor, value: timerValue) timerNode.update(color: iconColor, value: timerValue)
} else if let timerNode = strongSelf.timerNode { } else if let timerNode = strongSelf.timerNode {
@ -785,7 +787,7 @@ private final class TimerNode: ASDisplayNode {
let startAngle: CGFloat = -CGFloat.pi / 2.0 let startAngle: CGFloat = -CGFloat.pi / 2.0
let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * fraction let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * fraction
let sparks = fraction > 0.1 && fraction != 1.0 let sparks = fraction > 0.05 && fraction != 1.0
if sparks { if sparks {
let v = CGPoint(x: sin(endAngle), y: -cos(endAngle)) let v = CGPoint(x: sin(endAngle), y: -cos(endAngle))
let c = CGPoint(x: -v.y * radius + center.x, y: v.x * radius + center.y) let c = CGPoint(x: -v.y * radius + center.x, y: v.x * radius + center.y)

View File

@ -287,25 +287,6 @@ private func ChannelMembersControllerEntries(context: AccountContext, presentati
var index: Int32 = 0 var index: Int32 = 0
let sortedParticipants = participants let sortedParticipants = participants
/*
participants.sorted(by: { lhs, rhs in
let lhsInvitedAt: Int32
switch lhs.participant {
case .creator:
lhsInvitedAt = Int32.min
case let .member(_, invitedAt, _, _):
lhsInvitedAt = invitedAt
}
let rhsInvitedAt: Int32
switch rhs.participant {
case .creator:
rhsInvitedAt = Int32.min
case let .member(_, invitedAt, _, _):
rhsInvitedAt = invitedAt
}
return lhsInvitedAt < rhsInvitedAt
})
*/
for participant in sortedParticipants { for participant in sortedParticipants {
var editable = true var editable = true
var canEditMembers = false var canEditMembers = false

View File

@ -968,8 +968,17 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
return nil return nil
} |> deliverOnMainQueue).start(next: { invite in } |> deliverOnMainQueue).start(next: { invite in
if let invite = invite { if let invite = invite {
let controller = InviteLinkQRCodeController(context: context, invite: invite) let _ = (context.account.postbox.loadedPeerWithId(peerId)
presentControllerImpl?(controller, nil) |> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
let controller = InviteLinkQRCodeController(context: context, invite: invite, isGroup: isGroup)
presentControllerImpl?(controller, nil)
})
} }
}) })
}))) })))

View File

@ -78,7 +78,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
if let reportReason = reportReason { if let reportReason = reportReason {
switch subject { switch subject {
case let .peer(peerId): case let .peer(peerId):
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason) let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "")
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") { if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current) parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
@ -86,7 +86,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
completion(reportReason, true) completion(reportReason, true)
}) })
case let .messages(messageIds): case let .messages(messageIds):
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason) let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "")
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") { if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current) parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
@ -155,12 +155,16 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
break break
} }
if let reportReason = reportReason { if let reportReason = reportReason {
var passthrough = passthrough
if case .fake = reportReason {
passthrough = false
}
switch subject { switch subject {
case let .peer(peerId): case let .peer(peerId):
if passthrough { if passthrough {
completion(reportReason, true) completion(reportReason, true)
} else { } else {
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason) let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "")
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") { if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil) present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil)
@ -172,7 +176,7 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
if passthrough { if passthrough {
completion(reportReason, true) completion(reportReason, true)
} else { } else {
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason) let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "")
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") { if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil) present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil)
@ -326,7 +330,7 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
} }
if !text.isEmpty { if !text.isEmpty {
let reportReason: ReportReason = .custom(text) let reportReason: ReportReason = .custom
let completed: () -> Void = { let completed: () -> Void = {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
@ -335,12 +339,12 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
} }
switch subject { switch subject {
case let .peer(peerId): case let .peer(peerId):
reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: reportReason) reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: text)
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
completed() completed()
})) }))
case let .messages(messageIds): case let .messages(messageIds):
reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason) reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: text)
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
completed() completed()
})) }))

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() let _ = removePeerChat(account: context.account, peerId: peerId, reportChatSpam: reportSpam).start()
popToRootImpl?() popToRootImpl?()
} else if reportSpam { } else if reportSpam {
let _ = reportPeer(account: context.account, peerId: peerId, reason: .spam).start() let _ = reportPeer(account: context.account, peerId: peerId, reason: .spam, message: "").start()
} }
deleteSendMessageIntents(peerId: peerId) deleteSendMessageIntents(peerId: peerId)

View File

@ -117,8 +117,14 @@ private class SectionHeaderItemNode: ListViewItemNode {
strongSelf.headerNode = headerNode strongSelf.headerNode = headerNode
} }
headerNode.title = item.title headerNode.title = item.title
switch item.additionalText {
case .none, .generic:
headerNode.actionType = .generic
case .destructive:
headerNode.actionType = .destructive
}
headerNode.action = item.additionalText.text headerNode.action = item.additionalText.text
headerNode.frame = CGRect(origin: CGPoint(), size: contentSize) headerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
headerNode.updateLayout(size: contentSize, leftInset: params.leftInset, rightInset: params.rightInset) headerNode.updateLayout(size: contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
} }

View File

@ -63,6 +63,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
private let clearButton: HighlightableButtonNode private let clearButton: HighlightableButtonNode
public var updateHeight: (() -> Void)? public var updateHeight: (() -> Void)?
public var updateText: ((String) -> Void)?
private let backgroundInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 1.0, right: 16.0) private let backgroundInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 1.0, right: 16.0)
private let inputInsets = UIEdgeInsets(top: 10.0, left: 8.0, bottom: 10.0, right: 16.0) private let inputInsets = UIEdgeInsets(top: 10.0, left: 8.0, bottom: 10.0, right: 16.0)
@ -168,6 +169,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
@objc public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { @objc public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
self.updateTextNodeText(animated: true) self.updateTextNodeText(animated: true)
self.updateText?(editableTextNode.attributedText?.string ?? "")
} }
public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) { public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {

View File

@ -144,6 +144,7 @@ public struct TelegramChannelFlags: OptionSet {
public static let hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4) public static let hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4)
public static let hasActiveVoiceChat = TelegramChannelFlags(rawValue: 1 << 5) public static let hasActiveVoiceChat = TelegramChannelFlags(rawValue: 1 << 5)
public static let isFake = TelegramChannelFlags(rawValue: 1 << 6) public static let isFake = TelegramChannelFlags(rawValue: 1 << 6)
public static let isGigagroup = TelegramChannelFlags(rawValue: 1 << 7)
} }
public final class TelegramChannel: Peer { public final class TelegramChannel: Peer {

View File

@ -371,7 +371,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) } dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) }
dict[-1798033689] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilterEmpty($0) } dict[-1798033689] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilterEmpty($0) }
dict[-847783593] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilter($0) } dict[-847783593] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilter($0) }
dict[-559275508] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($0) } dict[-539872497] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($0) }
dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) } dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) }
dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) } dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) }
dict[-1803769784] = { return Api.messages.BotResults.parse_botResults($0) } dict[-1803769784] = { return Api.messages.BotResults.parse_botResults($0) }
@ -483,11 +483,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1490799288] = { return Api.ReportReason.parse_inputReportReasonSpam($0) } dict[1490799288] = { return Api.ReportReason.parse_inputReportReasonSpam($0) }
dict[505595789] = { return Api.ReportReason.parse_inputReportReasonViolence($0) } dict[505595789] = { return Api.ReportReason.parse_inputReportReasonViolence($0) }
dict[777640226] = { return Api.ReportReason.parse_inputReportReasonPornography($0) } dict[777640226] = { return Api.ReportReason.parse_inputReportReasonPornography($0) }
dict[-512463606] = { return Api.ReportReason.parse_inputReportReasonOther($0) }
dict[-1685456582] = { return Api.ReportReason.parse_inputReportReasonCopyright($0) } dict[-1685456582] = { return Api.ReportReason.parse_inputReportReasonCopyright($0) }
dict[-1376497949] = { return Api.ReportReason.parse_inputReportReasonChildAbuse($0) } dict[-1376497949] = { return Api.ReportReason.parse_inputReportReasonChildAbuse($0) }
dict[-606798099] = { return Api.ReportReason.parse_inputReportReasonGeoIrrelevant($0) } dict[-606798099] = { return Api.ReportReason.parse_inputReportReasonGeoIrrelevant($0) }
dict[-170010905] = { return Api.ReportReason.parse_inputReportReasonFake($0) } dict[-170010905] = { return Api.ReportReason.parse_inputReportReasonFake($0) }
dict[-1041980751] = { return Api.ReportReason.parse_inputReportReasonOther($0) }
dict[-247351839] = { return Api.InputEncryptedChat.parse_inputEncryptedChat($0) } dict[-247351839] = { return Api.InputEncryptedChat.parse_inputEncryptedChat($0) }
dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) } dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) }
dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) } dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) }

View File

@ -9610,24 +9610,25 @@ public extension Api {
} }
public enum ChatAdminWithInvites: TypeConstructorDescription { public enum ChatAdminWithInvites: TypeConstructorDescription {
case chatAdminWithInvites(adminId: Int32, invitesCount: Int32) case chatAdminWithInvites(adminId: Int32, invitesCount: Int32, revokedInvitesCount: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .chatAdminWithInvites(let adminId, let invitesCount): case .chatAdminWithInvites(let adminId, let invitesCount, let revokedInvitesCount):
if boxed { if boxed {
buffer.appendInt32(-559275508) buffer.appendInt32(-539872497)
} }
serializeInt32(adminId, buffer: buffer, boxed: false) serializeInt32(adminId, buffer: buffer, boxed: false)
serializeInt32(invitesCount, buffer: buffer, boxed: false) serializeInt32(invitesCount, buffer: buffer, boxed: false)
serializeInt32(revokedInvitesCount, buffer: buffer, boxed: false)
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .chatAdminWithInvites(let adminId, let invitesCount): case .chatAdminWithInvites(let adminId, let invitesCount, let revokedInvitesCount):
return ("chatAdminWithInvites", [("adminId", adminId), ("invitesCount", invitesCount)]) return ("chatAdminWithInvites", [("adminId", adminId), ("invitesCount", invitesCount), ("revokedInvitesCount", revokedInvitesCount)])
} }
} }
@ -9636,10 +9637,13 @@ public extension Api {
_1 = reader.readInt32() _1 = reader.readInt32()
var _2: Int32? var _2: Int32?
_2 = reader.readInt32() _2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
if _c1 && _c2 { let _c3 = _3 != nil
return Api.ChatAdminWithInvites.chatAdminWithInvites(adminId: _1!, invitesCount: _2!) if _c1 && _c2 && _c3 {
return Api.ChatAdminWithInvites.chatAdminWithInvites(adminId: _1!, invitesCount: _2!, revokedInvitesCount: _3!)
} }
else { else {
return nil return nil
@ -12149,11 +12153,11 @@ public extension Api {
case inputReportReasonSpam case inputReportReasonSpam
case inputReportReasonViolence case inputReportReasonViolence
case inputReportReasonPornography case inputReportReasonPornography
case inputReportReasonOther(text: String)
case inputReportReasonCopyright case inputReportReasonCopyright
case inputReportReasonChildAbuse case inputReportReasonChildAbuse
case inputReportReasonGeoIrrelevant case inputReportReasonGeoIrrelevant
case inputReportReasonFake case inputReportReasonFake
case inputReportReasonOther
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -12174,12 +12178,6 @@ public extension Api {
buffer.appendInt32(777640226) buffer.appendInt32(777640226)
} }
break
case .inputReportReasonOther(let text):
if boxed {
buffer.appendInt32(-512463606)
}
serializeString(text, buffer: buffer, boxed: false)
break break
case .inputReportReasonCopyright: case .inputReportReasonCopyright:
if boxed { if boxed {
@ -12204,6 +12202,12 @@ public extension Api {
buffer.appendInt32(-170010905) buffer.appendInt32(-170010905)
} }
break
case .inputReportReasonOther:
if boxed {
buffer.appendInt32(-1041980751)
}
break break
} }
} }
@ -12216,8 +12220,6 @@ public extension Api {
return ("inputReportReasonViolence", []) return ("inputReportReasonViolence", [])
case .inputReportReasonPornography: case .inputReportReasonPornography:
return ("inputReportReasonPornography", []) return ("inputReportReasonPornography", [])
case .inputReportReasonOther(let text):
return ("inputReportReasonOther", [("text", text)])
case .inputReportReasonCopyright: case .inputReportReasonCopyright:
return ("inputReportReasonCopyright", []) return ("inputReportReasonCopyright", [])
case .inputReportReasonChildAbuse: case .inputReportReasonChildAbuse:
@ -12226,6 +12228,8 @@ public extension Api {
return ("inputReportReasonGeoIrrelevant", []) return ("inputReportReasonGeoIrrelevant", [])
case .inputReportReasonFake: case .inputReportReasonFake:
return ("inputReportReasonFake", []) return ("inputReportReasonFake", [])
case .inputReportReasonOther:
return ("inputReportReasonOther", [])
} }
} }
@ -12238,17 +12242,6 @@ public extension Api {
public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? { public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonPornography return Api.ReportReason.inputReportReasonPornography
} }
public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.ReportReason.inputReportReasonOther(text: _1!)
}
else {
return nil
}
}
public static func parse_inputReportReasonCopyright(_ reader: BufferReader) -> ReportReason? { public static func parse_inputReportReasonCopyright(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonCopyright return Api.ReportReason.inputReportReasonCopyright
} }
@ -12261,6 +12254,9 @@ public extension Api {
public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? { public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonFake return Api.ReportReason.inputReportReasonFake
} }
public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? {
return Api.ReportReason.inputReportReasonOther
}
} }
public enum InputEncryptedChat: TypeConstructorDescription { public enum InputEncryptedChat: TypeConstructorDescription {

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>) { public static func getRecentLocations(peer: Api.InputPeer, limit: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1144759543) buffer.appendInt32(-1144759543)
@ -4126,6 +4106,27 @@ public extension Api {
return result return result
}) })
} }
public static func report(peer: Api.InputPeer, id: [Int32], reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1991005362)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(id.count))
for item in id {
serializeInt32(item, buffer: buffer, boxed: false)
}
reason.serialize(buffer, true)
serializeString(message, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.report", parameters: [("peer", peer), ("id", id), ("reason", reason), ("message", message)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
} }
public struct channels { public struct channels {
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
@ -6204,21 +6205,6 @@ public extension Api {
}) })
} }
public static func reportPeer(peer: Api.InputPeer, reason: Api.ReportReason) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1374118561)
peer.serialize(buffer, true)
reason.serialize(buffer, true)
return (FunctionDescription(name: "account.reportPeer", parameters: [("peer", peer), ("reason", reason)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { public static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(655677548) buffer.appendInt32(655677548)
@ -7125,6 +7111,39 @@ public extension Api {
return result return result
}) })
} }
public static func reportPeer(peer: Api.InputPeer, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-977650298)
peer.serialize(buffer, true)
reason.serialize(buffer, true)
serializeString(message, buffer: buffer, boxed: false)
return (FunctionDescription(name: "account.reportPeer", parameters: [("peer", peer), ("reason", reason), ("message", message)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func reportProfilePhoto(peer: Api.InputPeer, photoId: Api.InputPhoto, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-91437323)
peer.serialize(buffer, true)
photoId.serialize(buffer, true)
reason.serialize(buffer, true)
serializeString(message, buffer: buffer, boxed: false)
return (FunctionDescription(name: "account.reportProfilePhoto", parameters: [("peer", peer), ("photoId", photoId), ("reason", reason), ("message", message)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
} }
public struct wallet { public struct wallet {
public static func sendLiteRequest(body: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.wallet.LiteResponse>) { public static func sendLiteRequest(body: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.wallet.LiteResponse>) {

View File

@ -108,6 +108,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
if (flags & Int32(1 << 25)) != 0 { if (flags & Int32(1 << 25)) != 0 {
channelFlags.insert(.isFake) channelFlags.insert(.isFake)
} }
if (flags & Int32(1 << 25)) != 0 {
channelFlags.insert(.isGigagroup)
}
let restrictionInfo: PeerAccessRestrictionInfo? let restrictionInfo: PeerAccessRestrictionInfo?
if let restrictionReason = restrictionReason { if let restrictionReason = restrictionReason {

View File

@ -62,6 +62,7 @@ public enum AdminLogEventAction {
case deleteExportedInvitation(ExportedInvitation) case deleteExportedInvitation(ExportedInvitation)
case revokeExportedInvitation(ExportedInvitation) case revokeExportedInvitation(ExportedInvitation)
case editExportedInvitation(previous: ExportedInvitation, updated: ExportedInvitation) case editExportedInvitation(previous: ExportedInvitation, updated: ExportedInvitation)
case participantJoinedViaInvite(ExportedInvitation)
} }
public enum ChannelAdminLogEventError { public enum ChannelAdminLogEventError {
@ -241,11 +242,11 @@ public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: Pe
action = .revokeExportedInvitation(ExportedInvitation(apiExportedInvite: invite)) action = .revokeExportedInvitation(ExportedInvitation(apiExportedInvite: invite))
case let .channelAdminLogEventActionExportedInviteEdit(prevInvite, newInvite): case let .channelAdminLogEventActionExportedInviteEdit(prevInvite, newInvite):
action = .editExportedInvitation(previous: ExportedInvitation(apiExportedInvite: prevInvite), updated: ExportedInvitation(apiExportedInvite: newInvite)) action = .editExportedInvitation(previous: ExportedInvitation(apiExportedInvite: prevInvite), updated: ExportedInvitation(apiExportedInvite: newInvite))
case let .channelAdminLogEventActionParticipantJoinByInvite(invite):
action = .participantJoinedViaInvite(ExportedInvitation(apiExportedInvite: invite))
case let .channelAdminLogEventActionParticipantVolume(participant): case let .channelAdminLogEventActionParticipantVolume(participant):
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant) let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000) action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000)
case let .channelAdminLogEventActionParticipantJoinByInvite(invite):
action = nil
} }
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
if let action = action { if let action = action {

View File

@ -808,6 +808,7 @@ public final class PeerInvitationImportersContext {
public struct ExportedInvitationCreator : Equatable { public struct ExportedInvitationCreator : Equatable {
public let peer: RenderedPeer public let peer: RenderedPeer
public let count: Int32 public let count: Int32
public let revokedCount: Int32
} }
public func peerExportedInvitationsCreators(account: Account, peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> { public func peerExportedInvitationsCreators(account: Account, peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> {
@ -839,10 +840,10 @@ public func peerExportedInvitationsCreators(account: Account, peerId: PeerId) ->
peersMap[telegramUser.id] = telegramUser peersMap[telegramUser.id] = telegramUser
} }
for case let .chatAdminWithInvites(adminId, invitesCount) in admins { for case let .chatAdminWithInvites(adminId, invitesCount, revokedInvitesCount) in admins {
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId) let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId)
if let peer = peersMap[peerId], peerId != account.peerId { if let peer = peersMap[peerId], peerId != account.peerId {
creators.append(ExportedInvitationCreator(peer: RenderedPeer(peer: peer), count: invitesCount)) creators.append(ExportedInvitationCreator(peer: RenderedPeer(peer: peer), count: invitesCount, revokedCount: revokedInvitesCount))
} }
} }

View File

@ -84,7 +84,7 @@ public enum ReportReason: Equatable {
case childAbuse case childAbuse
case copyright case copyright
case irrelevantLocation case irrelevantLocation
case custom(String) case custom
} }
private extension ReportReason { private extension ReportReason {
@ -104,16 +104,16 @@ private extension ReportReason {
return .inputReportReasonCopyright return .inputReportReasonCopyright
case .irrelevantLocation: case .irrelevantLocation:
return .inputReportReasonGeoIrrelevant return .inputReportReasonGeoIrrelevant
case let .custom(text): case let .custom:
return .inputReportReasonOther(text: text) return .inputReportReasonOther
} }
} }
} }
public func reportPeer(account: Account, peerId: PeerId, reason: ReportReason) -> Signal<Void, NoError> { public func reportPeer(account: Account, peerId: PeerId, reason: ReportReason, message: String) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.account.reportPeer(peer: inputPeer, reason: reason.apiReason)) return account.network.request(Api.functions.account.reportPeer(peer: inputPeer, reason: reason.apiReason, message: message))
|> `catch` { _ -> Signal<Api.Bool, NoError> in |> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse) return .single(.boolFalse)
} }
@ -126,14 +126,14 @@ public func reportPeer(account: Account, peerId: PeerId, reason: ReportReason) -
} |> switchToLatest } |> switchToLatest
} }
public func reportPeerMessages(account: Account, messageIds: [MessageId], reason: ReportReason) -> Signal<Void, NoError> { public func reportPeerMessages(account: Account, messageIds: [MessageId], reason: ReportReason, message: String) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in return account.postbox.transaction { transaction -> Signal<Void, NoError> in
let groupedIds = messagesIdsGroupedByPeerId(messageIds) let groupedIds = messagesIdsGroupedByPeerId(messageIds)
let signals = groupedIds.values.compactMap { ids -> Signal<Void, NoError>? in let signals = groupedIds.values.compactMap { ids -> Signal<Void, NoError>? in
guard let peerId = ids.first?.peerId, let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else { guard let peerId = ids.first?.peerId, let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else {
return nil return nil
} }
return account.network.request(Api.functions.messages.report(peer: inputPeer, id: ids.map { $0.id }, reason: reason.apiReason)) return account.network.request(Api.functions.messages.report(peer: inputPeer, id: ids.map { $0.id }, reason: reason.apiReason, message: message))
|> `catch` { _ -> Signal<Api.Bool, NoError> in |> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse) return .single(.boolFalse)
} }

View File

@ -359,7 +359,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3), pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3),
inputClearButtonColor: UIColor(rgb: 0x8b9197), inputClearButtonColor: UIColor(rgb: 0x8b9197),
itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0xffffff), color2: UIColor(rgb: 0x929196), color3: UIColor(rgb: 0x333333)), itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0xffffff), color2: UIColor(rgb: 0x929196), color3: UIColor(rgb: 0x333333)),
itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0x1c1c1d), strokeColor: UIColor(rgb: 0x1c1c1d), placeholderColor: UIColor(rgb: 0x8f8f8f), primaryColor: UIColor(rgb: 0xffffff), controlColor: UIColor(rgb: 0x8f8f8f)) itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0x0f0f0f), strokeColor: UIColor(rgb: 0x0f0f0f), placeholderColor: UIColor(rgb: 0x8f8f8f), primaryColor: UIColor(rgb: 0xffffff), controlColor: UIColor(rgb: 0x8f8f8f))
) )
let chatList = PresentationThemeChatList( let chatList = PresentationThemeChatList(

View File

@ -612,7 +612,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres
pageIndicatorInactiveColor: mainSecondaryTextColor.withAlphaComponent(0.4), pageIndicatorInactiveColor: mainSecondaryTextColor.withAlphaComponent(0.4),
inputClearButtonColor: mainSecondaryColor, inputClearButtonColor: mainSecondaryColor,
itemBarChart: PresentationThemeItemBarChart(color1: accentColor, color2: mainSecondaryTextColor.withAlphaComponent(0.5), color3: accentColor.withMultiplied(hue: 1.038, saturation: 0.329, brightness: 0.33)), itemBarChart: PresentationThemeItemBarChart(color1: accentColor, color2: mainSecondaryTextColor.withAlphaComponent(0.5), color3: accentColor.withMultiplied(hue: 1.038, saturation: 0.329, brightness: 0.33)),
itemInputField: PresentationInputFieldTheme(backgroundColor: mainBackgroundColor, strokeColor: mainBackgroundColor, placeholderColor: mainSecondaryColor, primaryColor: UIColor(rgb: 0xffffff), controlColor: mainSecondaryColor) itemInputField: PresentationInputFieldTheme(backgroundColor: mainInputColor, strokeColor: mainInputColor, placeholderColor: mainSecondaryColor, primaryColor: UIColor(rgb: 0xffffff), controlColor: mainSecondaryColor)
) )
let chatList = PresentationThemeChatList( let chatList = PresentationThemeChatList(

View File

@ -2767,10 +2767,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let peer = peerViewMainPeer(peerView) { if let peer = peerViewMainPeer(peerView) {
if let selectionState = presentationInterfaceState.interfaceState.selectionState { if let selectionState = presentationInterfaceState.interfaceState.selectionState {
if selectionState.selectedIds.count > 0 { if selectionState.selectedIds.count > 0 {
strongSelf.chatTitleView?.titleContent = .custom(strongSelf.presentationData.strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count ?? 1)), nil, false) strongSelf.chatTitleView?.titleContent = .custom(strongSelf.presentationData.strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count)), nil, false)
} else { } else {
if let reportReason = presentationInterfaceState.reportReason { if let reportReason = presentationInterfaceState.reportReason {
strongSelf.chatTitleView?.titleContent = .custom("Report Smth", strongSelf.presentationInterfaceState.strings.Conversation_SelectMessages, false) let title: String
switch reportReason {
case .spam:
title = strongSelf.presentationData.strings.ReportPeer_ReasonSpam
case .fake:
title = strongSelf.presentationData.strings.ReportPeer_ReasonFake
case .violence:
title = strongSelf.presentationData.strings.ReportPeer_ReasonViolence
case .porno:
title = strongSelf.presentationData.strings.ReportPeer_ReasonPornography
case .childAbuse:
title = strongSelf.presentationData.strings.ReportPeer_ReasonChildAbuse
case .copyright:
title = strongSelf.presentationData.strings.ReportPeer_ReasonCopyright
case .custom:
title = strongSelf.presentationData.strings.ReportPeer_ReasonOther
case .irrelevantLocation:
title = ""
}
strongSelf.chatTitleView?.titleContent = .custom(title, strongSelf.presentationInterfaceState.strings.Conversation_SelectMessages, false)
} else { } else {
strongSelf.chatTitleView?.titleContent = .custom(strongSelf.presentationInterfaceState.strings.Conversation_SelectMessages, nil, false) strongSelf.chatTitleView?.titleContent = .custom(strongSelf.presentationInterfaceState.strings.Conversation_SelectMessages, nil, false)
} }
@ -4753,13 +4772,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.view.window?.endEditing(true) self?.view.window?.endEditing(true)
controller?.dismissAnimated() controller?.dismissAnimated()
} }
var message = ""
var items: [ActionSheetItem] = [] var items: [ActionSheetItem] = []
items.append(ReportPeerHeaderActionSheetItem(context: strongSelf.context, text: presentationData.strings.Report_AdditionalDetailsText)) items.append(ReportPeerHeaderActionSheetItem(context: strongSelf.context, text: presentationData.strings.Report_AdditionalDetailsText))
items.append(ReportPeerDetailsActionSheetItem(context: strongSelf.context, placeholderText: presentationData.strings.Report_AdditionalDetailsPlaceholder)) items.append(ReportPeerDetailsActionSheetItem(context: strongSelf.context, placeholderText: presentationData.strings.Report_AdditionalDetailsPlaceholder, textUpdated: { text in
message = text
}))
items.append(ActionSheetButtonItem(title: presentationData.strings.Report_Report, color: .accent, font: .bold, enabled: true, action: { items.append(ActionSheetButtonItem(title: presentationData.strings.Report_Report, color: .accent, font: .bold, enabled: true, action: {
dismissAction() dismissAction()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }, completion: { _ in strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }, completion: { _ in
let _ = (reportPeerMessages(account: strongSelf.context.account, messageIds: Array(messageIds), reason: reportReason) let _ = (reportPeerMessages(account: strongSelf.context.account, messageIds: Array(messageIds), reason: reportReason, message: message)
|> deliverOnMainQueue).start(completed: { [weak self] in |> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self, let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") { if let strongSelf = self, let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current) strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
@ -6211,7 +6233,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.reportIrrelvantGeoDisposable = (TelegramCore.reportPeer(account: strongSelf.context.account, peerId: peerId, reason: .irrelevantLocation) strongSelf.reportIrrelvantGeoDisposable = (TelegramCore.reportPeer(account: strongSelf.context.account, peerId: peerId, reason: .irrelevantLocation, message: "")
|> deliverOnMainQueue).start(completed: { [weak self] in |> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.reportIrrelvantGeoNoticePromise.set(.single(true)) strongSelf.reportIrrelvantGeoNoticePromise.set(.single(true))
@ -10676,7 +10698,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = removePeerChat(account: strongSelf.context.account, peerId: chatPeer.id, reportChatSpam: reportSpam).start() let _ = removePeerChat(account: strongSelf.context.account, peerId: chatPeer.id, reportChatSpam: reportSpam).start()
strongSelf.effectiveNavigationController?.filterController(strongSelf, animated: true) strongSelf.effectiveNavigationController?.filterController(strongSelf, animated: true)
} else if reportSpam { } else if reportSpam {
let _ = TelegramCore.reportPeer(account: strongSelf.context.account, peerId: peer.id, reason: .spam).start() let _ = TelegramCore.reportPeer(account: strongSelf.context.account, peerId: peer.id, reason: .spam, message: "").start()
} }
}) })
] as [ActionSheetItem]) ] as [ActionSheetItem])

View File

@ -716,7 +716,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
(.canPostMessages, self.presentationData.strings.Channel_AdminLog_CanSendMessages), (.canPostMessages, self.presentationData.strings.Channel_AdminLog_CanSendMessages),
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessagesOfOthers), (.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessagesOfOthers),
(.canEditMessages, self.presentationData.strings.Channel_AdminLog_CanEditMessages), (.canEditMessages, self.presentationData.strings.Channel_AdminLog_CanEditMessages),
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers), (.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsersViaLink),
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages), (.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins), (.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins),
(.canManageCalls, self.presentationData.strings.Channel_AdminLog_CanManageCalls) (.canManageCalls, self.presentationData.strings.Channel_AdminLog_CanManageCalls)
@ -726,7 +726,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
(.canChangeInfo, self.presentationData.strings.Channel_AdminLog_CanChangeInfo), (.canChangeInfo, self.presentationData.strings.Channel_AdminLog_CanChangeInfo),
(.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessages), (.canDeleteMessages, self.presentationData.strings.Channel_AdminLog_CanDeleteMessages),
(.canBanUsers, self.presentationData.strings.Channel_AdminLog_CanBanUsers), (.canBanUsers, self.presentationData.strings.Channel_AdminLog_CanBanUsers),
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers), (.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsersViaLink),
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages), (.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
(.canBeAnonymous, self.presentationData.strings.Channel_AdminLog_CanBeAnonymous), (.canBeAnonymous, self.presentationData.strings.Channel_AdminLog_CanBeAnonymous),
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins), (.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins),
@ -1192,6 +1192,8 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
appendAttributedText(text: rawText, generateEntities: { index in appendAttributedText(text: rawText, generateEntities: { index in
if index == 0, let author = author { if index == 0, let author = author {
return [.TextMention(peerId: author.id)] return [.TextMention(peerId: author.id)]
} else if index == 1 {
return [.Bold]
} }
return [] return []
}, to: &text, entities: &entities) }, to: &text, entities: &entities)
@ -1211,11 +1213,13 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
var text: String = "" var text: String = ""
var entities: [MessageTextEntity] = [] var entities: [MessageTextEntity] = []
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_DeletedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link) let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_DeletedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link.replacingOccurrences(of: "https://", with: ""))
appendAttributedText(text: rawText, generateEntities: { index in appendAttributedText(text: rawText, generateEntities: { index in
if index == 0, let author = author { if index == 0, let author = author {
return [.TextMention(peerId: author.id)] return [.TextMention(peerId: author.id)]
} else if index == 1 {
return [.Bold]
} }
return [] return []
}, to: &text, entities: &entities) }, to: &text, entities: &entities)
@ -1235,11 +1239,13 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
var text: String = "" var text: String = ""
var entities: [MessageTextEntity] = [] var entities: [MessageTextEntity] = []
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_RevokedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link) let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_RevokedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link.replacingOccurrences(of: "https://", with: ""))
appendAttributedText(text: rawText, generateEntities: { index in appendAttributedText(text: rawText, generateEntities: { index in
if index == 0, let author = author { if index == 0, let author = author {
return [.TextMention(peerId: author.id)] return [.TextMention(peerId: author.id)]
} else if index == 1 {
return [.Bold]
} }
return [] return []
}, to: &text, entities: &entities) }, to: &text, entities: &entities)
@ -1259,11 +1265,39 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
var text: String = "" var text: String = ""
var entities: [MessageTextEntity] = [] var entities: [MessageTextEntity] = []
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_EditedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", updatedInvite.link) let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_EditedInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", updatedInvite.link.replacingOccurrences(of: "https://", with: ""))
appendAttributedText(text: rawText, generateEntities: { index in appendAttributedText(text: rawText, generateEntities: { index in
if index == 0, let author = author { if index == 0, let author = author {
return [.TextMention(peerId: author.id)] return [.TextMention(peerId: author.id)]
} else if index == 1 {
return [.Bold]
}
return []
}, to: &text, entities: &entities)
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()))
case let .participantJoinedViaInvite(invite):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
if let peer = self.entry.peers[self.entry.event.peerId] {
author = peer
peers[peer.id] = peer
}
var text: String = ""
var entities: [MessageTextEntity] = []
let rawText: (String, [(Int, NSRange)]) = self.presentationData.strings.Channel_AdminLog_JoinedViaInviteLink(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link.replacingOccurrences(of: "https://", with: ""))
appendAttributedText(text: rawText, generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
} else if index == 1 {
return [.Bold]
} }
return [] return []
}, to: &text, entities: &entities) }, to: &text, entities: &entities)

View File

@ -87,7 +87,7 @@ private func peerButtons(_ state: ChatPresentationInterfaceState) -> [ChatReport
buttons.append(.shareMyPhoneNumber) buttons.append(.shareMyPhoneNumber)
} }
} }
} else if let _ = state.renderedPeer?.chatMainPeer { } else if let _ = state.renderedPeer?.chatMainPeer, case .peer = state.chatLocation {
if let contactStatus = state.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.suggestAddMembers) { if let contactStatus = state.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.suggestAddMembers) {
buttons.append(.addMembers) buttons.append(.addMembers)
} else if let contactStatus = state.contactStatus, contactStatus.canReportIrrelevantLocation, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { } else if let contactStatus = state.contactStatus, contactStatus.canReportIrrelevantLocation, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {

View File

@ -656,7 +656,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
if currentInvitationsContext == nil { if currentInvitationsContext == nil {
var canManageInvitations = false var canManageInvitations = false
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) { if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
canManageInvitations = true canManageInvitations = true
} }
if canManageInvitations { if canManageInvitations {
@ -813,9 +813,13 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
if currentInvitationsContext == nil { if currentInvitationsContext == nil {
var canManageInvitations = false var canManageInvitations = false
if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role { if let group = peerViewMainPeer(peerView) as? TelegramGroup {
canManageInvitations = true if case .creator = group.role {
} else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)) { canManageInvitations = true
} else if case let .admin(rights, _) = group.role, rights.flags.contains(.canInviteUsers) {
canManageInvitations = true
}
} else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
canManageInvitations = true canManageInvitations = true
} }
if canManageInvitations { if canManageInvitations {
@ -1081,14 +1085,33 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
return result return result
} }
func peerInfoCanEdit(peer: Peer?, cachedData: CachedPeerData?) -> Bool { func peerInfoCanEdit(peer: Peer?, cachedData: CachedPeerData?, isContact: Bool?) -> Bool {
if let user = peer as? TelegramUser { if let user = peer as? TelegramUser {
if user.isDeleted { if user.isDeleted {
return false return false
} }
if let isContact = isContact, !isContact {
return false
}
return true return true
} else if peer is TelegramChannel || peer is TelegramGroup { } else if let peer = peer as? TelegramChannel {
return true if peer.flags.contains(.isCreator) {
return true
} else if let adminRights = peer.adminRights, adminRights.flags.contains(.canAddAdmins) || adminRights.flags.contains(.canBanUsers) || adminRights.flags.contains(.canChangeInfo) || adminRights.flags.contains(.canInviteUsers) {
return true
}
return false
} else if let peer = peer as? TelegramGroup {
if case .creator = peer.role {
return true
} else if case let .admin(rights, _) = peer.role {
if rights.flags.contains(.canAddAdmins) || rights.flags.contains(.canBanUsers) || rights.flags.contains(.canChangeInfo) || rights.flags.contains(.canInviteUsers) {
return true
}
return false
} else {
return false
}
} }
return false return false
} }

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 let invitesText: String
if let count = data.invitations?.count, count > 0 { if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)" invitesText = "\(count)"
} else { } else {
invitesText = "" invitesText = ""
} }
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
interaction.editingOpenInviteLinksSetup() interaction.editingOpenInviteLinksSetup()
})) }))
}
if channel.flags.contains(.isCreator) || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) {
let discussionGroupTitle: String let discussionGroupTitle: String
if let _ = data.cachedData as? CachedChannelData { if let _ = data.cachedData as? CachedChannelData {
if let peer = data.linkedDiscussionPeer { if let peer = data.linkedDiscussionPeer {
@ -1336,39 +1337,22 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
interaction.editingOpenPreHistorySetup() interaction.editingOpenPreHistorySetup()
})) }))
} }
if channel.hasPermission(.inviteMembers) {
let invitesText: String
if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)"
} else {
invitesText = ""
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
interaction.editingOpenInviteLinksSetup()
}))
}
/*if channel.hasPermission(.changeInfo) {
let timeoutString: String
if case let .known(value) = cachedData.autoremoveTimeout {
if let value = value?.effectiveValue {
timeoutString = timeIntervalString(strings: presentationData.strings, value: value)
} else {
timeoutString = presentationData.strings.PeerInfo_AutoremoveMessagesDisabled
}
} else {
timeoutString = ""
}
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAutoremove, label: .text(timeoutString), text: presentationData.strings.PeerInfo_AutoremoveMessages, action: {
interaction.editingOpenAutoremoveMesages()
}))
}*/
} }
} }
if isCreator || (channel.hasPermission(.inviteMembers)) {
let invitesText: String
if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)"
} else {
invitesText = ""
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
interaction.editingOpenInviteLinksSetup()
}))
}
if cachedData.flags.contains(.canSetStickerSet) && canEditPeerInfo(context: context, peer: channel) { if cachedData.flags.contains(.canSetStickerSet) && canEditPeerInfo(context: context, peer: channel) {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStickerPack, label: .text(cachedData.stickerPack?.title ?? presentationData.strings.GroupInfo_SharedMediaNone), text: presentationData.strings.Stickers_GroupStickers, icon: UIImage(bundleImageName: "Settings/MenuIcons/Stickers"), action: { items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStickerPack, label: .text(cachedData.stickerPack?.title ?? presentationData.strings.GroupInfo_SharedMediaNone), text: presentationData.strings.Stickers_GroupStickers, icon: UIImage(bundleImageName: "Settings/MenuIcons/Stickers"), action: {
interaction.editingOpenStickerPackSetup() interaction.editingOpenStickerPackSetup()
@ -1472,8 +1456,19 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: {
interaction.openParticipantsSection(.admins) interaction.openParticipantsSection(.admins)
})) }))
} else if case .admin = group.role { } else if case let .admin(rights, _) = group.role {
if rights.flags.contains(.canInviteUsers) {
let invitesText: String
if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)"
} else {
invitesText = ""
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: {
interaction.editingOpenInviteLinksSetup()
}))
}
} }
} }
} }
@ -3744,7 +3739,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
let _ = removePeerChat(account: strongSelf.context.account, peerId: strongSelf.peerId, reportChatSpam: reportSpam).start() let _ = removePeerChat(account: strongSelf.context.account, peerId: strongSelf.peerId, reportChatSpam: reportSpam).start()
(strongSelf.controller?.navigationController as? NavigationController)?.popToRoot(animated: true) (strongSelf.controller?.navigationController as? NavigationController)?.popToRoot(animated: true)
} else if reportSpam { } else if reportSpam {
let _ = reportPeer(account: strongSelf.context.account, peerId: strongSelf.peerId, reason: .spam).start() let _ = reportPeer(account: strongSelf.context.account, peerId: strongSelf.peerId, reason: .spam, message: "").start()
} }
deleteSendMessageIntents(peerId: strongSelf.peerId) deleteSendMessageIntents(peerId: strongSelf.peerId)
@ -3875,6 +3870,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
private func editingOpenInviteLinksSetup() { private func editingOpenInviteLinksSetup() {
self.controller?.push(inviteLinkListController(context: self.context, peerId: self.peerId, admin: nil)) self.controller?.push(inviteLinkListController(context: self.context, peerId: self.peerId, admin: nil))
} }
@ -5307,7 +5303,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if self.isSettings { if self.isSettings {
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
} else if peerInfoCanEdit(peer: self.data?.peer, cachedData: self.data?.cachedData) { } else if peerInfoCanEdit(peer: self.data?.peer, cachedData: self.data?.cachedData, isContact: self.data?.isContact) {
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
} }
if self.state.selectedMessageIds == nil { if self.state.selectedMessageIds == nil {
@ -6230,14 +6226,16 @@ func presentAddMembers(context: AccountContext, parentController: ViewController
var canCreateInviteLink = false var canCreateInviteLink = false
if let group = groupPeer as? TelegramGroup { if let group = groupPeer as? TelegramGroup {
switch group.role { switch group.role {
case .creator, .admin: case .creator:
canCreateInviteLink = true canCreateInviteLink = true
case let .admin(rights, _):
canCreateInviteLink = rights.flags.contains(.canInviteUsers)
default: default:
break break
} }
} else if let channel = groupPeer as? TelegramChannel { } else if let channel = groupPeer as? TelegramChannel {
if channel.hasPermission(.inviteMembers) { if channel.hasPermission(.inviteMembers) {
if channel.flags.contains(.isCreator) || (channel.adminRights != nil && channel.username == nil) { if channel.flags.contains(.isCreator) || (channel.hasPermission(.inviteMembers)) {
canCreateInviteLink = true canCreateInviteLink = true
} }
} }

View File

@ -13,14 +13,16 @@ import AppBundle
public final class ReportPeerDetailsActionSheetItem: ActionSheetItem { public final class ReportPeerDetailsActionSheetItem: ActionSheetItem {
let context: AccountContext let context: AccountContext
let placeholderText: String let placeholderText: String
let textUpdated: (String) -> Void
public init(context: AccountContext, placeholderText: String) { public init(context: AccountContext, placeholderText: String, textUpdated: @escaping (String) -> Void) {
self.context = context self.context = context
self.placeholderText = placeholderText self.placeholderText = placeholderText
self.textUpdated = textUpdated
} }
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
return ReportPeerDetailsActionSheetItemNode(theme: theme, context: self.context, placeholderText: self.placeholderText) return ReportPeerDetailsActionSheetItemNode(theme: theme, context: self.context, placeholderText: self.placeholderText, textUpdated: self.textUpdated)
} }
public func updateNode(_ node: ActionSheetItemNode) { public func updateNode(_ node: ActionSheetItemNode) {
@ -34,7 +36,7 @@ private final class ReportPeerDetailsActionSheetItemNode: ActionSheetItemNode {
private let accessibilityArea: AccessibilityAreaNode private let accessibilityArea: AccessibilityAreaNode
init(theme: ActionSheetControllerTheme, context: AccountContext, placeholderText: String) { init(theme: ActionSheetControllerTheme, context: AccountContext, placeholderText: String, textUpdated: @escaping (String) -> Void) {
self.theme = theme self.theme = theme
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -48,7 +50,12 @@ private final class ReportPeerDetailsActionSheetItemNode: ActionSheetItemNode {
self.addSubnode(self.inputFieldNode) self.addSubnode(self.inputFieldNode)
// self.inputFieldNode. self.inputFieldNode.updateText = { text in
textUpdated(text)
}
self.inputFieldNode.updateHeight = {
}
} }
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {

View File

@ -195,7 +195,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.attributedText = attributedText self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2 self.textNode.maximumNumberOfLines = 2
displayUndo = false displayUndo = false
self.originalRemainingSeconds = 4 self.originalRemainingSeconds = 3
case let .banned(text): case let .banned(text):
self.avatarNode = nil self.avatarNode = nil
self.iconNode = nil self.iconNode = nil