added new, delete. added force touches, added input listing, added open
This commit is contained in:
parent
36320aecc7
commit
19526c2511
@ -13,9 +13,12 @@
|
||||
E97AF45E23FC83AF00635494 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = E97AF45D23FC83AF00635494 /* KeychainAccess */; };
|
||||
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45F23FC85D600635494 /* PlaylistApi.swift */; };
|
||||
E97AF46223FC89CC00635494 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E97AF46123FC89CB00635494 /* Main.storyboard */; };
|
||||
E97AF46423FD4EEF00635494 /* LiveUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF46323FD4EEF00635494 /* LiveUser.swift */; };
|
||||
E97AF46723FD650800635494 /* AddPlaylistSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF46623FD650800635494 /* AddPlaylistSheet.swift */; };
|
||||
E97AF46923FD9E1B00635494 /* PlaylistInputList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */; };
|
||||
E97AF46C23FDA90900635494 /* LoginController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF46B23FDA90900635494 /* LoginController.swift */; };
|
||||
E98254BD23F9B7A90056D9D3 /* Playlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254BC23F9B7A90056D9D3 /* Playlist.swift */; };
|
||||
E98254C223F9FFF90056D9D3 /* PlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254C123F9FFF90056D9D3 /* PlaylistView.swift */; };
|
||||
E98254C823FA25D20056D9D3 /* PlaylistList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254C723FA25D20056D9D3 /* PlaylistList.swift */; };
|
||||
E98254CA23FA26600056D9D3 /* PlaylistRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254C923FA26600056D9D3 /* PlaylistRow.swift */; };
|
||||
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254CF23FB00B60056D9D3 /* LoginScreen.swift */; };
|
||||
E98254D923FB53780056D9D3 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = E98254D823FB53780056D9D3 /* Alamofire */; };
|
||||
@ -52,9 +55,12 @@
|
||||
E97AF45A23FC748D00635494 /* UserApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserApi.swift; sourceTree = "<group>"; };
|
||||
E97AF45F23FC85D600635494 /* PlaylistApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistApi.swift; sourceTree = "<group>"; };
|
||||
E97AF46123FC89CB00635494 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
||||
E97AF46323FD4EEF00635494 /* LiveUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveUser.swift; sourceTree = "<group>"; };
|
||||
E97AF46623FD650800635494 /* AddPlaylistSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddPlaylistSheet.swift; sourceTree = "<group>"; };
|
||||
E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistInputList.swift; sourceTree = "<group>"; };
|
||||
E97AF46B23FDA90900635494 /* LoginController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginController.swift; sourceTree = "<group>"; };
|
||||
E98254BC23F9B7A90056D9D3 /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = "<group>"; };
|
||||
E98254C123F9FFF90056D9D3 /* PlaylistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistView.swift; sourceTree = "<group>"; };
|
||||
E98254C723FA25D20056D9D3 /* PlaylistList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistList.swift; sourceTree = "<group>"; };
|
||||
E98254C923FA26600056D9D3 /* PlaylistRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistRow.swift; sourceTree = "<group>"; };
|
||||
E98254CF23FB00B60056D9D3 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = "<group>"; };
|
||||
E98254DA23FB64740056D9D3 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = "<group>"; };
|
||||
@ -102,11 +108,20 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
E97AF46A23FDA8ED00635494 /* Controller */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E97AF46B23FDA90900635494 /* LoginController.swift */,
|
||||
);
|
||||
path = Controller;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E98254BE23F9BD540056D9D3 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E98254BC23F9B7A90056D9D3 /* Playlist.swift */,
|
||||
E97AF45523FC4E7800635494 /* User.swift */,
|
||||
E97AF46323FD4EEF00635494 /* LiveUser.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@ -126,9 +141,10 @@
|
||||
children = (
|
||||
E9EA690E23F9A5430012C3E8 /* RootView.swift */,
|
||||
E98254C123F9FFF90056D9D3 /* PlaylistView.swift */,
|
||||
E98254C723FA25D20056D9D3 /* PlaylistList.swift */,
|
||||
E98254C923FA26600056D9D3 /* PlaylistRow.swift */,
|
||||
E98254CF23FB00B60056D9D3 /* LoginScreen.swift */,
|
||||
E97AF46623FD650800635494 /* AddPlaylistSheet.swift */,
|
||||
E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@ -167,6 +183,7 @@
|
||||
E9EA690923F9A5430012C3E8 /* Music Tools */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E97AF46A23FDA8ED00635494 /* Controller */,
|
||||
E98254C623FA25280056D9D3 /* Application */,
|
||||
E98254C023F9FFDD0056D9D3 /* Views */,
|
||||
E98254BF23F9BE040056D9D3 /* Network */,
|
||||
@ -350,13 +367,16 @@
|
||||
E9EA690B23F9A5430012C3E8 /* AppDelegate.swift in Sources */,
|
||||
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */,
|
||||
E98254DB23FB64740056D9D3 /* Network.swift in Sources */,
|
||||
E97AF46C23FDA90900635494 /* LoginController.swift in Sources */,
|
||||
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */,
|
||||
E98254C823FA25D20056D9D3 /* PlaylistList.swift in Sources */,
|
||||
E9EA690F23F9A5430012C3E8 /* RootView.swift in Sources */,
|
||||
E98254BD23F9B7A90056D9D3 /* Playlist.swift in Sources */,
|
||||
E97AF46723FD650800635494 /* AddPlaylistSheet.swift in Sources */,
|
||||
E98254C223F9FFF90056D9D3 /* PlaylistView.swift in Sources */,
|
||||
E97AF46423FD4EEF00635494 /* LiveUser.swift in Sources */,
|
||||
E97AF45623FC4E7800635494 /* User.swift in Sources */,
|
||||
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */,
|
||||
E97AF46923FD9E1B00635494 /* PlaylistInputList.swift in Sources */,
|
||||
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -523,7 +543,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Music Tools/Preview Content\"";
|
||||
DEVELOPMENT_ASSET_PATHS = "Music\\ Tools/Preview\\ Content";
|
||||
DEVELOPMENT_TEAM = 8UZ2659FDY;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = "Music Tools/Info.plist";
|
||||
@ -543,7 +563,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Music Tools/Preview Content\"";
|
||||
DEVELOPMENT_ASSET_PATHS = "Music\\ Tools/Preview\\ Content";
|
||||
DEVELOPMENT_TEAM = 8UZ2659FDY;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = "Music Tools/Info.plist";
|
||||
|
@ -7,14 +7,25 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyJSON
|
||||
import KeychainAccess
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
|
||||
var liveUser: LiveUser?
|
||||
|
||||
var loading = true
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
|
||||
let keychain = Keychain(service: "xyz.sarsoo.music.login")
|
||||
keychain["username"] = ""
|
||||
keychain["password"] = ""
|
||||
|
||||
liveUser = LiveUser(playlists: [])
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,8 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="Logo" translatesAutoresizingMaskIntoConstraints="NO" id="nIw-aa-ATp">
|
||||
<rect key="frame" x="-49" y="192" width="512" height="512"/>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" fixedFrame="YES" image="MusicToolsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="NIx-qI-fR9">
|
||||
<rect key="frame" x="-59" y="184" width="512" height="512"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@ -30,6 +30,6 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="Logo" width="250" height="40"/>
|
||||
<image name="MusicToolsLogo" width="512" height="512"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
@ -1,7 +1,161 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="aYE-zJ-V1n">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes/>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="a5Z-le-CZi">
|
||||
<objects>
|
||||
<viewController id="Spt-Mn-jmg" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="kc5-Ke-ZcW">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="34" translatesAutoresizingMaskIntoConstraints="NO" id="9nJ-jm-QSN">
|
||||
<rect key="frame" x="-49" y="105" width="512" height="686"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="MusicToolsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="ZdO-HY-xSA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="512"/>
|
||||
</imageView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1SI-H8-Rgg">
|
||||
<rect key="frame" x="215.5" y="546" width="81" height="53"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle0"/>
|
||||
<state key="normal" title="Login"/>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6UM-RX-7fe">
|
||||
<rect key="frame" x="196" y="633" width="120" height="53"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle0"/>
|
||||
<state key="normal" title="Register"/>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="6UM-RX-7fe" firstAttribute="top" secondItem="1SI-H8-Rgg" secondAttribute="bottom" constant="34" id="Mc6-3Y-SWR"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="9nJ-jm-QSN" firstAttribute="centerX" secondItem="kc5-Ke-ZcW" secondAttribute="centerX" id="cpW-xl-QNm"/>
|
||||
<constraint firstItem="9nJ-jm-QSN" firstAttribute="centerY" secondItem="kc5-Ke-ZcW" secondAttribute="centerY" id="kjc-76-4D7"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="AaY-2f-fg2"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="0B7-E4-M4W" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1443.4782608695652" y="-185.49107142857142"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="6At-fb-bfN">
|
||||
<objects>
|
||||
<viewController id="TND-IP-OdM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="MhD-yZ-pD8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="VLy-9d-bfF"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Dgg-BJ-VHU" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="48" y="304"/>
|
||||
</scene>
|
||||
<!--Login Controller-->
|
||||
<scene sceneID="Cbq-wX-wg3">
|
||||
<objects>
|
||||
<viewController id="aYE-zJ-V1n" customClass="LoginController" customModule="Music_Tools" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="cjE-Uz-adD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="MusicToolsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="Aa0-TI-VfP">
|
||||
<rect key="frame" x="0.0" y="94" width="414" height="433"/>
|
||||
</imageView>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="bezel" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Jsz-1T-t1A">
|
||||
<rect key="frame" x="20" y="589" width="374" height="34"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="34" id="ThQ-Jz-RB7"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" textContentType="username"/>
|
||||
</textField>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="bezel" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="c5V-bv-fGr">
|
||||
<rect key="frame" x="20" y="682" width="374" height="34"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="34" id="ynd-9a-coG"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" returnKeyType="go" textContentType="password"/>
|
||||
</textField>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6YH-OC-12B">
|
||||
<rect key="frame" x="185.5" y="766" width="43" height="53"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="53" id="udu-LZ-6uK"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle0"/>
|
||||
<state key="normal" title="Go">
|
||||
<color key="titleShadowColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="login:" destination="aYE-zJ-V1n" eventType="touchUpInside" id="G3y-eM-24U"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CNh-mk-uV0">
|
||||
<rect key="frame" x="167.5" y="560" width="79" height="21"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="21" id="oKd-GV-oVN"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Password" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mvf-wa-Hkg">
|
||||
<rect key="frame" x="170" y="653" width="74" height="21"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="21" id="glG-PD-dHg"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Jsz-1T-t1A" firstAttribute="leading" secondItem="c5V-bv-fGr" secondAttribute="leading" id="0wx-Ol-kyH"/>
|
||||
<constraint firstItem="Aa0-TI-VfP" firstAttribute="top" secondItem="JQv-rE-o2m" secondAttribute="top" constant="50" id="36L-3g-C8l"/>
|
||||
<constraint firstItem="JQv-rE-o2m" firstAttribute="bottom" secondItem="6YH-OC-12B" secondAttribute="bottom" constant="43" id="4I4-MB-XEO"/>
|
||||
<constraint firstItem="c5V-bv-fGr" firstAttribute="top" secondItem="mvf-wa-Hkg" secondAttribute="bottom" constant="8" id="57A-nQ-c7X"/>
|
||||
<constraint firstItem="Aa0-TI-VfP" firstAttribute="centerX" secondItem="CNh-mk-uV0" secondAttribute="centerX" id="CSz-Z9-0h2"/>
|
||||
<constraint firstItem="Jsz-1T-t1A" firstAttribute="trailing" secondItem="cjE-Uz-adD" secondAttribute="trailingMargin" id="JUi-Dm-bqV"/>
|
||||
<constraint firstItem="CNh-mk-uV0" firstAttribute="centerX" secondItem="Jsz-1T-t1A" secondAttribute="centerX" id="OGV-Rj-ryd"/>
|
||||
<constraint firstItem="mvf-wa-Hkg" firstAttribute="top" secondItem="Jsz-1T-t1A" secondAttribute="bottom" constant="30" id="V2C-dy-adh"/>
|
||||
<constraint firstItem="Jsz-1T-t1A" firstAttribute="top" secondItem="CNh-mk-uV0" secondAttribute="bottom" constant="8" id="WVP-qB-wVP"/>
|
||||
<constraint firstItem="6YH-OC-12B" firstAttribute="top" secondItem="c5V-bv-fGr" secondAttribute="bottom" constant="50" id="ah4-pZ-kNm"/>
|
||||
<constraint firstItem="c5V-bv-fGr" firstAttribute="centerX" secondItem="6YH-OC-12B" secondAttribute="centerX" id="b6B-2x-Zf2"/>
|
||||
<constraint firstItem="Jsz-1T-t1A" firstAttribute="trailing" secondItem="c5V-bv-fGr" secondAttribute="trailing" id="bsE-gu-f8q"/>
|
||||
<constraint firstItem="Aa0-TI-VfP" firstAttribute="leading" secondItem="JQv-rE-o2m" secondAttribute="leading" id="mrX-sL-EaE"/>
|
||||
<constraint firstItem="Jsz-1T-t1A" firstAttribute="leading" secondItem="cjE-Uz-adD" secondAttribute="leadingMargin" id="q1D-Aj-DhS"/>
|
||||
<constraint firstItem="mvf-wa-Hkg" firstAttribute="centerX" secondItem="c5V-bv-fGr" secondAttribute="centerX" id="rPo-wh-BDa"/>
|
||||
<constraint firstItem="CNh-mk-uV0" firstAttribute="top" secondItem="Aa0-TI-VfP" secondAttribute="bottom" constant="33" id="yXw-NR-ADJ"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="JQv-rE-o2m"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="goButton" destination="6YH-OC-12B" id="JFW-b1-k9S"/>
|
||||
<outlet property="passwordField" destination="c5V-bv-fGr" id="9LJ-hb-7Jx"/>
|
||||
<outlet property="usernameField" destination="Jsz-1T-t1A" id="QOC-fI-XsH"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="BJs-Wv-17o" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-68.115942028985515" y="-676.33928571428567"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="MusicToolsLogo" width="512" height="512"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
@ -8,13 +8,11 @@
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import KeychainAccess
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||
@ -23,14 +21,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
// Create the SwiftUI view that provides the window contents.
|
||||
let contentView = RootView()
|
||||
|
||||
let keychain = Keychain(service: "xyz.sarsoo.music.login")
|
||||
keychain["username"] = ""
|
||||
keychain["password"] = ""
|
||||
var liveUser: LiveUser?
|
||||
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
|
||||
liveUser = appDelegate.liveUser
|
||||
}
|
||||
|
||||
// Use a UIHostingController as window root view controller.
|
||||
if let windowScene = scene as? UIWindowScene {
|
||||
let window = UIWindow(windowScene: windowScene)
|
||||
window.rootViewController = UIHostingController(rootView: contentView)
|
||||
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(liveUser ?? LiveUser(playlists: [])))
|
||||
self.window = window
|
||||
window.makeKeyAndVisible()
|
||||
}
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
50
Music Tools/Controller/LoginController.swift
Normal file
50
Music Tools/Controller/LoginController.swift
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// LoginController.swift
|
||||
// Music Tools
|
||||
//
|
||||
// Created by Ellie McCarthy on 19/02/2020.
|
||||
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class LoginController: UIViewController, UITextFieldDelegate {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
@IBOutlet weak var usernameField: UITextField!
|
||||
@IBOutlet weak var passwordField: UITextField!
|
||||
@IBOutlet weak var goButton: UIButton!
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
@IBAction func login(_ sender: UIButton) {
|
||||
debugPrint(usernameField?.text)
|
||||
}
|
||||
|
||||
// MARK: UITextFieldDelegate
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
textField.resignFirstResponder()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
// MARK: - Navigation
|
||||
|
||||
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
// Get the new view controller using segue.destination.
|
||||
// Pass the selected object to the new view controller.
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
17
Music Tools/Model/LiveUser.swift
Normal file
17
Music Tools/Model/LiveUser.swift
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// LiveUser.swift
|
||||
// Music Tools
|
||||
//
|
||||
// Created by Andy Pack on 19/02/2020.
|
||||
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class LiveUser: ObservableObject {
|
||||
var playlists: Array<Playlist>
|
||||
|
||||
init(playlists: Array<Playlist>) {
|
||||
self.playlists = playlists
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
import SwiftyJSON
|
||||
|
||||
class Playlist: Identifiable {
|
||||
class Playlist: Identifiable, Equatable {
|
||||
|
||||
//MARK: Properties
|
||||
|
||||
@ -70,4 +70,14 @@ class Playlist: Identifiable {
|
||||
shuffle: dictionary["shuffle"].boolValue)
|
||||
}
|
||||
|
||||
var link: String {
|
||||
let uriSplit = self.uri.components(separatedBy: ":")
|
||||
return "https://open.spotify.com/playlist/\(uriSplit.last ?? "")"
|
||||
}
|
||||
|
||||
static func == (lhs: Playlist, rhs: Playlist) -> Bool {
|
||||
return lhs.name == rhs.name
|
||||
// && lhs.username == rhs.username
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,10 +10,20 @@ import Foundation
|
||||
import Alamofire
|
||||
import SwiftyJSON
|
||||
|
||||
let txTypeHeaders = ["default", "recents", "fmchart"]
|
||||
|
||||
public enum PlaylistType: Int {
|
||||
case defaultPlaylist = 0
|
||||
case recents = 1
|
||||
case fmchart = 2
|
||||
}
|
||||
|
||||
public enum PlaylistApi {
|
||||
case getPlaylists
|
||||
case runPlaylist(name: String)
|
||||
case updatePlaylist(name: String, updates: JSON)
|
||||
case deletePlaylist(name: String)
|
||||
case newPlaylist(name: String, type: PlaylistType)
|
||||
}
|
||||
|
||||
extension PlaylistApi: ApiRequest {
|
||||
@ -29,6 +39,10 @@ extension PlaylistApi: ApiRequest {
|
||||
return "api/playlist/run"
|
||||
case .updatePlaylist:
|
||||
return "api/playlist"
|
||||
case .deletePlaylist:
|
||||
return "api/playlist"
|
||||
case .newPlaylist:
|
||||
return "api/playlist"
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,6 +54,10 @@ extension PlaylistApi: ApiRequest {
|
||||
return .get
|
||||
case .updatePlaylist:
|
||||
return .post
|
||||
case .deletePlaylist:
|
||||
return .delete
|
||||
case .newPlaylist:
|
||||
return .put
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,8 +70,11 @@ extension PlaylistApi: ApiRequest {
|
||||
case .updatePlaylist(let name, let updates):
|
||||
var txUpdates = updates
|
||||
txUpdates["name"].string = name
|
||||
debugPrint(txUpdates)
|
||||
return txUpdates
|
||||
case .deletePlaylist(let name):
|
||||
return JSON(["name": name])
|
||||
case .newPlaylist(let name, let type):
|
||||
return JSON(["name": name, "type": txTypeHeaders[type.rawValue]])
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +86,10 @@ extension PlaylistApi: ApiRequest {
|
||||
return URLEncodedFormParameterEncoder()
|
||||
case .updatePlaylist:
|
||||
return JSONParameterEncoder.default
|
||||
case .deletePlaylist:
|
||||
return URLEncodedFormParameterEncoder()
|
||||
case .newPlaylist:
|
||||
return JSONParameterEncoder.default
|
||||
}
|
||||
}
|
||||
|
||||
|
90
Music Tools/Views/AddPlaylistSheet.swift
Normal file
90
Music Tools/Views/AddPlaylistSheet.swift
Normal file
@ -0,0 +1,90 @@
|
||||
//
|
||||
// AddPlaylistSheet.swift
|
||||
// Music Tools
|
||||
//
|
||||
// Created by Andy Pack on 19/02/2020.
|
||||
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftyJSON
|
||||
|
||||
struct AddPlaylistSheet: View {
|
||||
|
||||
@State private var selectedType = 0
|
||||
@State private var name = ""
|
||||
@State private var errorMessage = ""
|
||||
@State private var isLoading = false
|
||||
|
||||
@Binding var state: Bool
|
||||
@Binding var playlists: Array<Playlist>
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
Text("New Playlist")
|
||||
.font(.largeTitle)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding([.top, .leading, .trailing], 20.0)
|
||||
|
||||
}
|
||||
Picker(selection: $selectedType, label: Text("Picker")) {
|
||||
Text("Default").tag(0)
|
||||
Text("Recents").tag(1)
|
||||
Text("Last.fm Chart").tag(2)
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
.padding()
|
||||
TextField("Name", text: $name)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.padding([.bottom, .leading, .trailing], 20.0)
|
||||
|
||||
|
||||
|
||||
Button(action: create){
|
||||
Text("Add")
|
||||
.font(.title)
|
||||
}
|
||||
.disabled(isLoading)
|
||||
.padding()
|
||||
|
||||
Text(errorMessage)
|
||||
.foregroundColor(Color.red)
|
||||
.padding()
|
||||
}
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
|
||||
}
|
||||
|
||||
func create(){
|
||||
if name.count == 0 {
|
||||
errorMessage = "Enter Playlist Name"
|
||||
return
|
||||
}
|
||||
|
||||
var namePresent = false
|
||||
for playlist in playlists {
|
||||
if playlist.name == name {
|
||||
namePresent = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if namePresent == true {
|
||||
errorMessage = "Playlist already created"
|
||||
return
|
||||
}
|
||||
|
||||
isLoading = true
|
||||
let api = PlaylistApi.newPlaylist(name: self.name,
|
||||
type: PlaylistType(rawValue: selectedType) ?? .defaultPlaylist)
|
||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||
self.isLoading = false
|
||||
self.state = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddPlaylistSheet_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddPlaylistSheet(state: .constant(true), playlists: .constant([]))
|
||||
}
|
||||
}
|
49
Music Tools/Views/PlaylistInputList.swift
Normal file
49
Music Tools/Views/PlaylistInputList.swift
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// PlaylistInputList.swift
|
||||
// Music Tools
|
||||
//
|
||||
// Created by Andy Pack on 19/02/2020.
|
||||
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct Name: Identifiable {
|
||||
var id = UUID()
|
||||
var name: String
|
||||
}
|
||||
|
||||
struct PlaylistInputList: View {
|
||||
|
||||
var names: Array<Name> = []
|
||||
var nameType: String
|
||||
|
||||
init(names: Array<String>, nameType: String){
|
||||
self.nameType = nameType
|
||||
self.names = names.map { (name) -> Name in
|
||||
return Name(name: name)
|
||||
}.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
||||
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
return List(names) { name in
|
||||
Text(name.name)
|
||||
}
|
||||
.navigationBarTitle(Text(nameType))
|
||||
.navigationBarItems(trailing:
|
||||
Button(
|
||||
action: { },
|
||||
label: { Image(systemName: "plus.circle") }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaylistInputList_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PlaylistInputList(names: [
|
||||
"name"
|
||||
], nameType: "Spotify Playlists")
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
//
|
||||
// PlaylistList.swift
|
||||
// Music Tools
|
||||
//
|
||||
// Created by Andy Pack on 17/02/2020.
|
||||
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PlaylistList: View {
|
||||
var playlists: Array<Playlist>
|
||||
|
||||
var body: some View {
|
||||
List(playlists) { playlist in
|
||||
PlaylistRow(playlist: playlist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaylistList_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PlaylistList(playlists:
|
||||
[
|
||||
Playlist(name: "playlist name",
|
||||
uri: "uri",
|
||||
username: "username",
|
||||
|
||||
include_recommendations: true,
|
||||
recommendation_sample: 5,
|
||||
include_library_tracks: true,
|
||||
|
||||
parts: ["name"],
|
||||
playlist_references: ["ref name"],
|
||||
|
||||
shuffle: true)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
@ -7,15 +7,37 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftyJSON
|
||||
|
||||
struct PlaylistRow: View {
|
||||
|
||||
@EnvironmentObject var liveUser: LiveUser
|
||||
|
||||
var playlist: Playlist
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(destination: PlaylistView(playlist: playlist)){
|
||||
HStack {
|
||||
Text(playlist.name)
|
||||
Spacer()
|
||||
.contextMenu {
|
||||
Button(action: {
|
||||
let api = PlaylistApi.runPlaylist(name: self.playlist.name)
|
||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||
|
||||
}
|
||||
}) {
|
||||
Text("Refresh")
|
||||
Image(systemName: "arrow.clockwise.circle")
|
||||
}
|
||||
Button(action: {
|
||||
if let url = URL(string: self.playlist.link) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}) {
|
||||
Text("Open")
|
||||
Image(systemName: "arrowshape.turn.up.right.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,108 +10,109 @@ import SwiftUI
|
||||
import SwiftyJSON
|
||||
|
||||
struct PlaylistView: View {
|
||||
|
||||
init(playlist: Playlist) {
|
||||
self.playlist = playlist
|
||||
UITableView.appearance().tableFooterView = UIView()
|
||||
}
|
||||
|
||||
var playlist: Playlist
|
||||
@State private var recommendations: Bool = false
|
||||
@State private var recommendations: Bool = true
|
||||
@State private var library_Tracks: Bool = false
|
||||
@State private var shuffle: Bool = false
|
||||
|
||||
@State private var rec_num: Int = 0
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Text(playlist.name)
|
||||
.font(.largeTitle)
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(.leading)
|
||||
Text(playlist.username)
|
||||
.foregroundColor(Color.gray)
|
||||
.padding(.leading)
|
||||
Image("PlaylistCoverImage")
|
||||
.resizable()
|
||||
.frame(width: 200.0, height: 200.0, alignment: .trailing)
|
||||
.cornerRadius(18)
|
||||
.padding(.all, 20)
|
||||
|
||||
List {
|
||||
Section(header: Text("Options")){
|
||||
Toggle(isOn: $recommendations) {
|
||||
Text("Spotify Recommendations")
|
||||
}
|
||||
.padding()
|
||||
|
||||
if recommendations {
|
||||
// if recommendations {
|
||||
Stepper(onIncrement: {
|
||||
self.$rec_num.wrappedValue += 1
|
||||
self.updatePlaylist(updates: JSON(["recommendation_sample": self.$rec_num.wrappedValue]))
|
||||
},
|
||||
onDecrement: {
|
||||
self.$rec_num.wrappedValue -= 1
|
||||
self.updatePlaylist(updates: JSON(["recommendation_sample": self.$rec_num.wrappedValue]))
|
||||
self.updatePlaylist(updates: JSON(["recommendation_sample": self.$rec_num.wrappedValue]))
|
||||
}){
|
||||
Text("#:")
|
||||
.foregroundColor(Color.gray)
|
||||
.multilineTextAlignment(.trailing)
|
||||
.padding(.leading, 20)
|
||||
Text("\(rec_num)")
|
||||
.multilineTextAlignment(.trailing)
|
||||
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
Toggle(isOn: $library_Tracks) {
|
||||
Text("Library Tracks")
|
||||
}.padding()
|
||||
}
|
||||
|
||||
Toggle(isOn: $shuffle) {
|
||||
Text("Shuffle")
|
||||
}.padding()
|
||||
|
||||
HStack {
|
||||
Button(action: { self.runPlaylist() }) {
|
||||
Text("Update")
|
||||
}.padding().multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
Section(header: Text("Inputs")){
|
||||
NavigationLink(destination: PlaylistInputList(names: self.playlist.playlist_references, nameType: "Managed Playlists")) {
|
||||
HStack {
|
||||
Text("Managed Playlists")
|
||||
Spacer()
|
||||
Text("\(self.playlist.playlist_references.count)")
|
||||
.foregroundColor(Color.gray)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
NavigationLink(destination: PlaylistInputList(names: self.playlist.parts, nameType: "Spotify Playlists")) {
|
||||
HStack {
|
||||
Text("Spotify Playlists")
|
||||
Spacer()
|
||||
Text("\(self.playlist.parts.count)")
|
||||
.foregroundColor(Color.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
|
||||
.onAppear {
|
||||
self.$recommendations.wrappedValue = self.playlist.include_recommendations
|
||||
self.$library_Tracks.wrappedValue = self.playlist.include_library_tracks
|
||||
self.$shuffle.wrappedValue = self.playlist.shuffle
|
||||
Section(header: Text("Actions")){
|
||||
Button(action: { self.runPlaylist() }) {
|
||||
Text("Update")
|
||||
}
|
||||
|
||||
self.$rec_num.wrappedValue = self.playlist.recommendation_sample
|
||||
Button(action: { self.openPlaylist() }) {
|
||||
Text("Open")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitle(Text(playlist.name))
|
||||
.onAppear {
|
||||
self.$recommendations.wrappedValue = self.playlist.include_recommendations
|
||||
self.$library_Tracks.wrappedValue = self.playlist.include_library_tracks
|
||||
self.$shuffle.wrappedValue = self.playlist.shuffle
|
||||
|
||||
self.$rec_num.wrappedValue = self.playlist.recommendation_sample
|
||||
}
|
||||
}
|
||||
|
||||
func runPlaylist() {
|
||||
let api = PlaylistApi.runPlaylist(name: playlist.name)
|
||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||
|
||||
guard let data = response.data else {
|
||||
fatalError("error getting playlists")
|
||||
}
|
||||
|
||||
guard let json = try? JSON(data: data) else {
|
||||
fatalError("error parsing reponse")
|
||||
}
|
||||
}
|
||||
//TODO: do better error checking
|
||||
}
|
||||
|
||||
func openPlaylist() {
|
||||
if let url = URL(string: self.playlist.link) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePlaylist(updates: JSON) {
|
||||
let api = PlaylistApi.updatePlaylist(name: playlist.name, updates: updates)
|
||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||
|
||||
guard let data = response.data else {
|
||||
fatalError("error getting playlists")
|
||||
}
|
||||
|
||||
guard let json = try? JSON(data: data) else {
|
||||
fatalError("error parsing reponse")
|
||||
}
|
||||
}
|
||||
//TODO: do better error checking
|
||||
}
|
||||
|
@ -11,16 +11,56 @@ import Alamofire
|
||||
import SwiftyJSON
|
||||
|
||||
struct RootView: View {
|
||||
|
||||
@EnvironmentObject var liveUser: LiveUser
|
||||
|
||||
@State private var selection = 0
|
||||
@State private var playlists: Array<Playlist> = []
|
||||
|
||||
@State private var isLoading = true
|
||||
@State private var showAdd = false
|
||||
|
||||
@State private var onClose = onSheetClose
|
||||
|
||||
@State private var justDeleted: Array<Playlist> = []
|
||||
|
||||
func onSheetClose() {
|
||||
self.fetch()
|
||||
return
|
||||
}
|
||||
|
||||
let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $selection){
|
||||
TabView {
|
||||
NavigationView {
|
||||
List(playlists) { playlist in
|
||||
PlaylistRow(playlist: playlist)
|
||||
List{
|
||||
ForEach(playlists) { playlist in
|
||||
PlaylistRow(playlist: playlist)
|
||||
}
|
||||
.onDelete { indexSet in
|
||||
|
||||
indexSet.forEach { index in
|
||||
self.justDeleted.append(self.playlists[index])
|
||||
|
||||
let api = PlaylistApi.deletePlaylist(name: self.playlists[index].name)
|
||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
self.playlists.remove(atOffsets: indexSet)
|
||||
}
|
||||
}
|
||||
.navigationBarTitle(Text("Playlists").font(.title))
|
||||
.navigationBarItems(trailing:
|
||||
Button(
|
||||
action: { self.showAdd = true },
|
||||
label: { Text("Add") }
|
||||
).sheet(isPresented: $showAdd) {
|
||||
AddPlaylistSheet(state: self.$showAdd, playlists: self.$playlists)
|
||||
}
|
||||
)
|
||||
}
|
||||
.tabItem {
|
||||
VStack {
|
||||
@ -57,6 +97,9 @@ struct RootView: View {
|
||||
}
|
||||
}
|
||||
.tag(2)
|
||||
.onReceive(timer) { _ in
|
||||
self.fetch()
|
||||
}
|
||||
}.onAppear {
|
||||
self.fetch()
|
||||
}
|
||||
@ -74,9 +117,23 @@ struct RootView: View {
|
||||
fatalError("error parsing reponse")
|
||||
}
|
||||
|
||||
self.playlists = json["playlists"].arrayValue.map({ dict in
|
||||
let playlists = json["playlists"].arrayValue.map({ dict in
|
||||
Playlist.fromDict(dictionary: dict)
|
||||
}).sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
||||
.filter { (rxPlaylist) -> Bool in
|
||||
|
||||
var deleted = false
|
||||
for playlist in self.justDeleted {
|
||||
if playlist == rxPlaylist {
|
||||
deleted = true
|
||||
}
|
||||
}
|
||||
return !deleted
|
||||
}
|
||||
self.justDeleted = []
|
||||
|
||||
self.liveUser.playlists = playlists
|
||||
self.playlists = self.liveUser.playlists
|
||||
}
|
||||
//TODO: do better error checking
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user