An experimental share extension to send files and too much refactoring

closes #4
This commit is contained in:
Grishka
2023-09-26 03:22:23 +03:00
parent 22a2fff54b
commit 8f0eccd7b6
39 changed files with 2158 additions and 235 deletions

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "16.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "32.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NFiles</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@files@</string>
<key>files</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d file</string>
<key>other</key>
<string>%d files</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,264 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ShareViewController" customModule="ShareExtension" customModuleProvider="target">
<connections>
<outlet property="contentWrap" destination="ACO-U2-AZj" id="PAE-Pw-Vyx"/>
<outlet property="filesIcon" destination="afM-Om-nLQ" id="I58-a3-SBZ"/>
<outlet property="filesLabel" destination="OEz-QK-nem" id="his-3f-6kx"/>
<outlet property="largeProgress" destination="fCj-wF-rEj" id="jEg-Uc-Ur7"/>
<outlet property="listView" destination="gZI-Zb-Blx" id="VOC-3j-Pwy"/>
<outlet property="listViewWrapper" destination="A4d-Qt-PsT" id="8g5-VW-MIb"/>
<outlet property="loadingOverlay" destination="fLZ-IA-bPS" id="84g-Ef-GzW"/>
<outlet property="progressDeviceIcon" destination="84U-lB-SRB" id="LEL-BY-2FY"/>
<outlet property="progressDeviceIconWrap" destination="jBg-Ih-Gac" id="69x-6y-z0c"/>
<outlet property="progressDeviceName" destination="dJc-gw-4ux" id="ZE4-Kx-5hv"/>
<outlet property="progressDeviceSecondaryIcon" destination="BEO-kU-a2Q" id="iks-QR-2Od"/>
<outlet property="progressProgressBar" destination="J5x-hu-Kn5" id="vYH-DN-03b"/>
<outlet property="progressState" destination="y8I-D3-scQ" id="Tj3-xa-8VG"/>
<outlet property="progressView" destination="Q9K-dc-THx" id="LlE-4d-mI5"/>
<outlet property="view" destination="1" id="2"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="1">
<rect key="frame" x="0.0" y="0.0" width="404" height="248"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NVE-vN-dkz">
<rect key="frame" x="325" y="3" width="76" height="32"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="60" id="cP1-hK-9ZX"/>
</constraints>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6Up-t3-mwm">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancel:" target="-2" id="Qav-AK-DGt"/>
</connections>
</button>
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1xF-vr-5sH">
<rect key="frame" x="160" y="222" width="84" height="16"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="4M6-D5-WIf">
<rect key="frame" x="0.0" y="0.0" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="BOe-aZ-Njc"/>
<constraint firstAttribute="height" constant="16" id="zLg-1a-wlZ"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NearDropIcon" id="q3u-Am-ZIA"/>
</imageView>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aNc-0i-CWK">
<rect key="frame" x="19" y="0.0" width="67" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="NearDrop" allowsEditingTextAttributes="YES" id="0xp-rC-2gr">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="afM-Om-nLQ">
<rect key="frame" x="10" y="196" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="3NE-mW-Jk0"/>
<constraint firstAttribute="height" constant="16" id="koV-ob-gSn"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="jOq-9r-V1W"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OEz-QK-nem">
<rect key="frame" x="29" y="196" width="367" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingMiddle" truncatesLastVisibleLine="YES" id="opy-Bj-x6u">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="ACO-U2-AZj">
<rect key="frame" x="0.0" y="40" width="404" height="146"/>
</customView>
</subviews>
<constraints>
<constraint firstItem="OEz-QK-nem" firstAttribute="top" secondItem="1xF-vr-5sH" secondAttribute="bottom" constant="10" id="852-oh-vM0"/>
<constraint firstItem="OEz-QK-nem" firstAttribute="leading" secondItem="afM-Om-nLQ" secondAttribute="trailing" constant="5" id="CIj-Dk-Md0"/>
<constraint firstAttribute="trailing" secondItem="ACO-U2-AZj" secondAttribute="trailing" id="HIg-sX-IK0"/>
<constraint firstItem="ACO-U2-AZj" firstAttribute="leading" secondItem="1" secondAttribute="leading" id="IGL-2g-wpe"/>
<constraint firstItem="afM-Om-nLQ" firstAttribute="leading" secondItem="1" secondAttribute="leading" constant="10" id="LK2-cg-qcY"/>
<constraint firstItem="NVE-vN-dkz" firstAttribute="top" secondItem="ACO-U2-AZj" secondAttribute="bottom" constant="10" id="RSD-PX-W0H"/>
<constraint firstAttribute="bottom" secondItem="NVE-vN-dkz" secondAttribute="bottom" constant="10" id="USG-Gg-of3"/>
<constraint firstAttribute="trailing" secondItem="NVE-vN-dkz" secondAttribute="trailing" constant="10" id="Z6U-4n-4tO"/>
<constraint firstItem="afM-Om-nLQ" firstAttribute="top" secondItem="1" secondAttribute="top" constant="36" id="fUG-kT-wb0"/>
<constraint firstItem="1xF-vr-5sH" firstAttribute="centerX" secondItem="1" secondAttribute="centerX" id="sCA-yf-S9L"/>
<constraint firstItem="1xF-vr-5sH" firstAttribute="top" secondItem="1" secondAttribute="top" constant="10" id="tFn-vk-9Cq"/>
<constraint firstAttribute="trailing" secondItem="OEz-QK-nem" secondAttribute="trailing" constant="10" id="vyd-BI-oDH"/>
<constraint firstItem="ACO-U2-AZj" firstAttribute="top" secondItem="OEz-QK-nem" secondAttribute="bottom" constant="10" id="x5r-wc-s4x"/>
</constraints>
<point key="canvasLocation" x="140" y="-36"/>
</customView>
<stackView wantsLayer="YES" distribution="equalSpacing" orientation="vertical" alignment="centerX" spacing="0.0" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" id="fLZ-IA-bPS">
<rect key="frame" x="0.0" y="0.0" width="521" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<progressIndicator maxValue="100" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="fCj-wF-rEj">
<rect key="frame" x="245" y="68" width="32" height="32"/>
</progressIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="27P-yf-gUd">
<rect key="frame" x="189" y="42" width="144" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Looking for devices..." id="NaJ-Wx-Pim">
<font key="font" textStyle="headline" name=".SFNS-Bold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="V5W-Rm-YVM">
<rect key="frame" x="-2" y="0.0" width="525" height="32"/>
<textFieldCell key="cell" alignment="center" title="If you don't see your device, open &quot;Files by Google&quot; app and tap &quot;Receive&quot; on the Nearby Share tab." id="vla-gF-eJo">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="27P-yf-gUd" firstAttribute="top" secondItem="fCj-wF-rEj" secondAttribute="bottom" constant="10" id="87W-ec-fMh"/>
<constraint firstItem="V5W-Rm-YVM" firstAttribute="top" secondItem="27P-yf-gUd" secondAttribute="bottom" constant="10" id="OSI-wI-LcY"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
<point key="canvasLocation" x="660.5" y="-1.5"/>
</stackView>
<stackView wantsLayer="YES" distribution="equalSpacing" orientation="vertical" alignment="centerX" spacing="1" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" id="Q9K-dc-THx">
<rect key="frame" x="0.0" y="0.0" width="384" height="130"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="jBg-Ih-Gac">
<rect key="frame" x="168" y="82" width="48" height="48"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="84U-lB-SRB">
<rect key="frame" x="0.0" y="0.0" width="48" height="48"/>
<constraints>
<constraint firstAttribute="width" constant="48" id="9ld-k4-EkL"/>
<constraint firstAttribute="height" constant="48" id="XA4-2k-hqR"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="rb3-78-PXe"/>
</imageView>
<imageView hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="BEO-kU-a2Q">
<rect key="frame" x="24" y="-8" width="32" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="05I-Ef-84Z"/>
<constraint firstAttribute="width" constant="32" id="V0g-Fz-HWH"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSCaution" id="k1c-Xl-fTQ"/>
</imageView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="BEO-kU-a2Q" secondAttribute="trailing" constant="-8" id="59U-Zi-ESa"/>
<constraint firstAttribute="bottom" secondItem="BEO-kU-a2Q" secondAttribute="bottom" constant="-8" id="8ty-pq-miR"/>
<constraint firstItem="84U-lB-SRB" firstAttribute="top" secondItem="jBg-Ih-Gac" secondAttribute="top" id="MB9-UI-2mb"/>
<constraint firstItem="84U-lB-SRB" firstAttribute="leading" secondItem="jBg-Ih-Gac" secondAttribute="leading" id="b23-hJ-aWT"/>
<constraint firstAttribute="trailing" secondItem="84U-lB-SRB" secondAttribute="trailing" id="eKl-3V-K49"/>
<constraint firstAttribute="bottom" secondItem="84U-lB-SRB" secondAttribute="bottom" id="uh8-T1-Vd8"/>
</constraints>
</customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dJc-gw-4ux">
<rect key="frame" x="-2" y="56" width="388" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" id="QzI-qQ-OBb">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<progressIndicator maxValue="100" doubleValue="50" indeterminate="YES" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="J5x-hu-Kn5">
<rect key="frame" x="92" y="25" width="200" height="22"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="bM2-Ih-2UC"/>
</constraints>
</progressIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="y8I-D3-scQ">
<rect key="frame" x="165" y="0.0" width="54" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" id="Iin-UY-IE5">
<font key="font" metaFont="system"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="y8I-D3-scQ" firstAttribute="top" secondItem="J5x-hu-Kn5" secondAttribute="bottom" constant="10" id="4EB-xc-Qni"/>
<constraint firstItem="dJc-gw-4ux" firstAttribute="leading" secondItem="Q9K-dc-THx" secondAttribute="leading" id="IXP-am-A8h"/>
<constraint firstItem="J5x-hu-Kn5" firstAttribute="top" secondItem="dJc-gw-4ux" secondAttribute="bottom" constant="10" id="Zav-Ho-QEJ"/>
<constraint firstItem="dJc-gw-4ux" firstAttribute="top" secondItem="jBg-Ih-Gac" secondAttribute="bottom" constant="10" id="bIE-Es-ZbK"/>
<constraint firstAttribute="trailing" secondItem="dJc-gw-4ux" secondAttribute="trailing" id="bKd-Vz-YA1"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
<point key="canvasLocation" x="4" y="-288"/>
</stackView>
<scrollView wantsLayer="YES" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="A4d-Qt-PsT">
<rect key="frame" x="0.0" y="0.0" width="271" height="172"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" id="cCr-8P-Lya">
<rect key="frame" x="1" y="1" width="269" height="170"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView id="gZI-Zb-Blx">
<rect key="frame" x="0.0" y="0.0" width="269" height="170"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumInteritemSpacing="10" minimumLineSpacing="10" id="ATS-Jd-odC">
<size key="itemSize" width="50" height="50"/>
</collectionViewFlowLayout>
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</collectionView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="3j2-z3-G1e">
<rect key="frame" x="-100" y="-100" width="402" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="wty-KV-JAg">
<rect key="frame" x="380" y="1" width="16" height="142"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<point key="canvasLocation" x="-397.5" y="30"/>
</scrollView>
</objects>
<resources>
<image name="NSCaution" width="32" height="32"/>
<image name="NearDropIcon" width="16" height="16"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
//
// DeviceListCell.swift
// ShareExtension
//
// Created by Grishka on 20.09.2023.
//
import Cocoa
class DeviceListCell:NSCollectionViewItem {
public var clickHandler:(()->Void)?
override func viewDidLoad() {
super.viewDidLoad()
let btn:NSButton=view as! NSButton
btn.isEnabled=true
btn.setButtonType(.momentaryPushIn)
btn.action=#selector(onClick)
btn.target=self
}
@IBAction func onClick(_ sender:Any?){
guard let handler=clickHandler else {return}
handler()
}
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="DeviceListCell" customModule="ShareExtension" customModuleProvider="target">
<connections>
<outlet property="view" destination="Hz6-mo-xeY" id="gtq-ca-SBh"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView misplaced="YES" id="Hz6-mo-xeY" customClass="NSButton">
<rect key="frame" x="0.0" y="0.0" width="206" height="82"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QPM-sE-maP">
<rect key="frame" x="-2" y="0.0" width="210" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="TBS-IE-2DT"/>
</constraints>
<textFieldCell key="cell" truncatesLastVisibleLine="YES" alignment="center" title="Label" id="cXi-mf-qP0">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="O1J-am-CoK">
<rect key="frame" x="79" y="42" width="48" height="48"/>
<constraints>
<constraint firstAttribute="width" constant="48" id="KYr-2E-wCO"/>
<constraint firstAttribute="height" constant="48" id="nik-I6-YgB"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="QGU-rR-rJQ"/>
</imageView>
</subviews>
<constraints>
<constraint firstItem="O1J-am-CoK" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="R76-2L-pXb"/>
<constraint firstAttribute="bottom" secondItem="QPM-sE-maP" secondAttribute="bottom" id="Ryn-nY-yZp"/>
<constraint firstItem="O1J-am-CoK" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="ZFA-S9-fpT"/>
<constraint firstAttribute="trailing" secondItem="QPM-sE-maP" secondAttribute="trailing" id="eOl-Va-HXq"/>
<constraint firstItem="QPM-sE-maP" firstAttribute="top" secondItem="O1J-am-CoK" secondAttribute="bottom" constant="10" id="leg-nD-mHB"/>
<constraint firstItem="QPM-sE-maP" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="vvH-Hq-iUk"/>
</constraints>
<connections>
<action selector="onClick:" target="-2" id="mve-gp-q4i"/>
</connections>
<point key="canvasLocation" x="-60" y="150"/>
</customView>
<collectionViewItem id="1Lb-iJ-Nar" customClass="DeviceListCell" customModule="ShareExtension" customModuleProvider="target">
<connections>
<outlet property="imageView" destination="O1J-am-CoK" id="zQn-mV-bRy"/>
<outlet property="textField" destination="QPM-sE-maP" id="s0j-Qq-7iA"/>
<outlet property="view" destination="Hz6-mo-xeY" id="gZn-20-o77"/>
</connections>
</collectionViewItem>
</objects>
</document>

27
ShareExtension/Info.plist Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>SUBQUERY (
extensionItems,
$extensionItem,
SUBQUERY (
$extensionItem.attachments,
$attachment,
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url"
AND NOT (ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.folder")
).@count == $extensionItem.attachments.@count
).@count &gt; 0</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,271 @@
//
// ShareViewController.swift
// ShareExtension
//
// Created by Grishka on 12.09.2023.
//
import Foundation
import Cocoa
import NearbyShare
class ShareViewController: NSViewController, ShareExtensionDelegate{
private var urls:[URL]=[]
private var foundDevices:[RemoteDeviceInfo]=[]
private var chosenDevice:RemoteDeviceInfo?
private var lastError:Error?
@IBOutlet var filesIcon:NSImageView?
@IBOutlet var filesLabel:NSTextField?
@IBOutlet var loadingOverlay:NSStackView?
@IBOutlet var largeProgress:NSProgressIndicator?
@IBOutlet var listView:NSCollectionView?
@IBOutlet var listViewWrapper:NSView?
@IBOutlet var contentWrap:NSView?
@IBOutlet var progressView:NSView?
@IBOutlet var progressDeviceIcon:NSImageView?
@IBOutlet var progressDeviceName:NSTextField?
@IBOutlet var progressProgressBar:NSProgressIndicator?
@IBOutlet var progressState:NSTextField?
@IBOutlet var progressDeviceIconWrap:NSView?
@IBOutlet var progressDeviceSecondaryIcon:NSImageView?
override var nibName: NSNib.Name? {
return NSNib.Name("ShareViewController")
}
override func loadView() {
super.loadView()
// Insert code here to customize the view
let item = self.extensionContext!.inputItems[0] as! NSExtensionItem
if let attachments = item.attachments {
for attachment in attachments as NSArray{
let provider=attachment as! NSItemProvider
provider.loadItem(forTypeIdentifier: kUTTypeURL as String) { data, err in
if let url=URL(dataRepresentation: data as! Data, relativeTo: nil, isAbsolute: false){
self.urls.append(url)
if self.urls.count==attachments.count{
DispatchQueue.main.async {
self.urlsReady()
}
}
}
}
}
} else {
let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
self.extensionContext!.cancelRequest(withError: cancelError)
return
}
contentWrap!.addSubview(listViewWrapper!)
contentWrap!.addSubview(loadingOverlay!)
contentWrap!.addSubview(progressView!)
progressView!.isHidden=true
listViewWrapper!.translatesAutoresizingMaskIntoConstraints=false
loadingOverlay!.translatesAutoresizingMaskIntoConstraints=false
progressView!.translatesAutoresizingMaskIntoConstraints=false
NSLayoutConstraint.activate([
NSLayoutConstraint(item: listViewWrapper!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0),
NSLayoutConstraint(item: listViewWrapper!, attribute: .height, relatedBy: .equal, toItem: contentWrap, attribute: .height, multiplier: 1, constant: 0),
NSLayoutConstraint(item: loadingOverlay!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0),
NSLayoutConstraint(item: loadingOverlay!, attribute: .centerY, relatedBy: .equal, toItem: contentWrap, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: progressView!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0),
NSLayoutConstraint(item: progressView!, attribute: .centerY, relatedBy: .equal, toItem: contentWrap, attribute: .centerY, multiplier: 1, constant: 0)
])
largeProgress!.startAnimation(nil)
let flowLayout=NSCollectionViewFlowLayout()
flowLayout.itemSize=NSSize(width: 75, height: 90)
flowLayout.sectionInset=NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
flowLayout.minimumInteritemSpacing=10
flowLayout.minimumLineSpacing=10
listView!.collectionViewLayout=flowLayout
listView!.dataSource=self
progressDeviceIconWrap!.wantsLayer=true
progressDeviceIconWrap!.layer!.masksToBounds=false
}
override func viewDidLoad(){
super.viewDidLoad()
NearbyConnectionManager.shared.startDeviceDiscovery()
NearbyConnectionManager.shared.addShareExtensionDelegate(self)
}
override func viewWillDisappear() {
if chosenDevice==nil{
NearbyConnectionManager.shared.stopDeviceDiscovery()
}
NearbyConnectionManager.shared.removeShareExtensionDelegate(self)
}
@IBAction func cancel(_ sender: AnyObject?) {
if let device=chosenDevice{
NearbyConnectionManager.shared.cancelOutgoingTransfer(id: device.id!)
}
let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
self.extensionContext!.cancelRequest(withError: cancelError)
}
private func urlsReady(){
for url in urls{
if url.isFileURL{
var isDirectory=UnsafeMutablePointer<ObjCBool>.allocate(capacity: 1)
if FileManager.default.fileExists(atPath: url.path, isDirectory: isDirectory) && isDirectory.pointee.boolValue{
print("Canceling share request because URL \(url) is a directory")
let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
self.extensionContext!.cancelRequest(withError: cancelError)
return
}
}
}
if urls.count==1{
if urls[0].isFileURL{
filesLabel!.stringValue=urls[0].lastPathComponent
filesIcon!.image=NSWorkspace.shared.icon(forFile: urls[0].path)
}else if urls[0].scheme=="http" || urls[0].scheme=="https"{
filesLabel!.stringValue=urls[0].absoluteString
filesIcon!.image=NSImage(named: NSImage.networkName)
}
}else{
filesLabel!.stringValue=String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), urls.count)
filesIcon!.image=NSImage(named: NSImage.multipleDocumentsName)
}
}
func addDevice(device: RemoteDeviceInfo) {
if foundDevices.isEmpty{
loadingOverlay?.animator().isHidden=true
}
foundDevices.append(device)
listView?.animator().insertItems(at: [[0, foundDevices.count-1]])
}
func removeDevice(id: String){
if chosenDevice != nil{
return
}
for i in foundDevices.indices{
if foundDevices[i].id==id{
foundDevices.remove(at: i)
listView?.animator().deleteItems(at: [[0, i]])
break
}
}
if foundDevices.isEmpty{
loadingOverlay?.animator().isHidden=false
}
}
func connectionWasEstablished(pinCode: String) {
progressState?.stringValue=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [pinCode])
progressProgressBar?.isIndeterminate=false
progressProgressBar?.maxValue=1000
progressProgressBar?.doubleValue=0
}
func connectionFailed(with error: Error) {
progressProgressBar?.isIndeterminate=false
progressProgressBar?.maxValue=1000
progressProgressBar?.doubleValue=0
lastError=error
if let ne=(error as? NearbyError), case let .canceled(reason)=ne{
switch reason{
case .userRejected:
progressState?.stringValue=NSLocalizedString("TransferDeclined", value: "Declined", comment: "")
case .userCanceled:
progressState?.stringValue=NSLocalizedString("TransferCanceled", value: "Canceled", comment: "")
case .notEnoughSpace:
progressState?.stringValue=NSLocalizedString("NotEnoughSpace", value: "Not enough disk space", comment: "")
case .unsupportedType:
progressState?.stringValue=NSLocalizedString("UnsupportedType", value: "Attachment type not supported", comment: "")
case .timedOut:
progressState?.stringValue=NSLocalizedString("TransferTimedOut", value: "Timed out", comment: "")
}
progressDeviceSecondaryIcon?.isHidden=false
dismissDelayed()
}else{
let alert=NSAlert(error: error)
alert.beginSheetModal(for: view.window!) { resp in
self.extensionContext!.cancelRequest(withError: error)
}
}
}
func transferAccepted() {
progressState?.stringValue=NSLocalizedString("Sending", value: "Sending...", comment: "")
}
func transferProgress(progress: Double) {
progressProgressBar!.doubleValue=progress*progressProgressBar!.maxValue
}
func transferFinished() {
progressState?.stringValue=NSLocalizedString("TransferFinished", value: "Transfer finished", comment: "")
dismissDelayed()
}
func selectDevice(device:RemoteDeviceInfo){
NearbyConnectionManager.shared.stopDeviceDiscovery()
listViewWrapper?.animator().isHidden=true
progressView?.animator().isHidden=false
progressDeviceName?.stringValue=device.name
progressDeviceIcon?.image=imageForDeviceType(type: device.type)
progressProgressBar?.startAnimation(nil)
progressState?.stringValue=NSLocalizedString("Connecting", value: "Connecting...", comment: "")
chosenDevice=device
NearbyConnectionManager.shared.startOutgoingTransfer(deviceID: device.id!, delegate: self, urls: urls)
}
private func dismissDelayed(){
DispatchQueue.main.asyncAfter(deadline: .now()+2.0){
if let error=self.lastError{
self.extensionContext!.cancelRequest(withError: error)
}else{
self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
}
}
}
}
fileprivate func imageForDeviceType(type:RemoteDeviceInfo.DeviceType)->NSImage{
let imageName:String
switch type{
case .tablet:
imageName="com.apple.ipad"
case .computer:
imageName="com.apple.macbookpro-13-unibody"
default: // also .phone
imageName="com.apple.iphone"
}
return NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/\(imageName).icns")!
}
extension ShareViewController:NSCollectionViewDataSource{
func numberOfSections(in collectionView: NSCollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return foundDevices.count
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item=collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DeviceListCell"), for: indexPath)
guard let collectionViewItem = item as? DeviceListCell else {return item}
let device=foundDevices[indexPath[1]]
collectionViewItem.textField?.stringValue=device.name
collectionViewItem.imageView?.image=imageForDeviceType(type: device.type)
// TODO maybe there's a better way to handle clicks on collection view items? I'm still new to Apple's way of doing UIs so I may do dumb shit occasionally
collectionViewItem.clickHandler={
self.selectDevice(device: device)
}
return collectionViewItem
}
}

Binary file not shown.

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NFiles</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@files@</string>
<key>files</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d файл</string>
<key>few</key>
<string>%d файла</string>
<key>many</key>
<string>%d файлов</string>
<key>other</key>
<string>%d файлов</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,12 @@
/* Class = "NSTextFieldCell"; title = "NearDrop"; ObjectID = "0xp-rC-2gr"; */
"0xp-rC-2gr.title" = "NearDrop";
/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "6Up-t3-mwm"; */
"6Up-t3-mwm.title" = "Отменить";
/* Class = "NSTextFieldCell"; title = "Looking for devices..."; ObjectID = "NaJ-Wx-Pim"; */
"NaJ-Wx-Pim.title" = "Ищу устройства...";
/* Class = "NSTextFieldCell"; title = "If you don't see your device, open \"Files by Google\" app and tap \"Receive\" on the Nearby Share tab."; ObjectID = "vla-gF-eJo"; */
"vla-gF-eJo.title" = "Если Вы не видите своё устройство, откройте приложение \"Google Files\" и нажмите \"Получить\" на вкладке Обмен.";