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 */; };
|
E97AF45E23FC83AF00635494 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = E97AF45D23FC83AF00635494 /* KeychainAccess */; };
|
||||||
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45F23FC85D600635494 /* PlaylistApi.swift */; };
|
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45F23FC85D600635494 /* PlaylistApi.swift */; };
|
||||||
E97AF46223FC89CC00635494 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E97AF46123FC89CB00635494 /* Main.storyboard */; };
|
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 */; };
|
E98254BD23F9B7A90056D9D3 /* Playlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254BC23F9B7A90056D9D3 /* Playlist.swift */; };
|
||||||
E98254C223F9FFF90056D9D3 /* PlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254C123F9FFF90056D9D3 /* PlaylistView.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 */; };
|
E98254CA23FA26600056D9D3 /* PlaylistRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254C923FA26600056D9D3 /* PlaylistRow.swift */; };
|
||||||
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254CF23FB00B60056D9D3 /* LoginScreen.swift */; };
|
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254CF23FB00B60056D9D3 /* LoginScreen.swift */; };
|
||||||
E98254D923FB53780056D9D3 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = E98254D823FB53780056D9D3 /* Alamofire */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
E98254DA23FB64740056D9D3 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = "<group>"; };
|
||||||
@ -102,11 +108,20 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
E97AF46A23FDA8ED00635494 /* Controller */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E97AF46B23FDA90900635494 /* LoginController.swift */,
|
||||||
|
);
|
||||||
|
path = Controller;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E98254BE23F9BD540056D9D3 /* Model */ = {
|
E98254BE23F9BD540056D9D3 /* Model */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E98254BC23F9B7A90056D9D3 /* Playlist.swift */,
|
E98254BC23F9B7A90056D9D3 /* Playlist.swift */,
|
||||||
E97AF45523FC4E7800635494 /* User.swift */,
|
E97AF45523FC4E7800635494 /* User.swift */,
|
||||||
|
E97AF46323FD4EEF00635494 /* LiveUser.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -126,9 +141,10 @@
|
|||||||
children = (
|
children = (
|
||||||
E9EA690E23F9A5430012C3E8 /* RootView.swift */,
|
E9EA690E23F9A5430012C3E8 /* RootView.swift */,
|
||||||
E98254C123F9FFF90056D9D3 /* PlaylistView.swift */,
|
E98254C123F9FFF90056D9D3 /* PlaylistView.swift */,
|
||||||
E98254C723FA25D20056D9D3 /* PlaylistList.swift */,
|
|
||||||
E98254C923FA26600056D9D3 /* PlaylistRow.swift */,
|
E98254C923FA26600056D9D3 /* PlaylistRow.swift */,
|
||||||
E98254CF23FB00B60056D9D3 /* LoginScreen.swift */,
|
E98254CF23FB00B60056D9D3 /* LoginScreen.swift */,
|
||||||
|
E97AF46623FD650800635494 /* AddPlaylistSheet.swift */,
|
||||||
|
E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -167,6 +183,7 @@
|
|||||||
E9EA690923F9A5430012C3E8 /* Music Tools */ = {
|
E9EA690923F9A5430012C3E8 /* Music Tools */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E97AF46A23FDA8ED00635494 /* Controller */,
|
||||||
E98254C623FA25280056D9D3 /* Application */,
|
E98254C623FA25280056D9D3 /* Application */,
|
||||||
E98254C023F9FFDD0056D9D3 /* Views */,
|
E98254C023F9FFDD0056D9D3 /* Views */,
|
||||||
E98254BF23F9BE040056D9D3 /* Network */,
|
E98254BF23F9BE040056D9D3 /* Network */,
|
||||||
@ -350,13 +367,16 @@
|
|||||||
E9EA690B23F9A5430012C3E8 /* AppDelegate.swift in Sources */,
|
E9EA690B23F9A5430012C3E8 /* AppDelegate.swift in Sources */,
|
||||||
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */,
|
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */,
|
||||||
E98254DB23FB64740056D9D3 /* Network.swift in Sources */,
|
E98254DB23FB64740056D9D3 /* Network.swift in Sources */,
|
||||||
|
E97AF46C23FDA90900635494 /* LoginController.swift in Sources */,
|
||||||
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */,
|
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */,
|
||||||
E98254C823FA25D20056D9D3 /* PlaylistList.swift in Sources */,
|
|
||||||
E9EA690F23F9A5430012C3E8 /* RootView.swift in Sources */,
|
E9EA690F23F9A5430012C3E8 /* RootView.swift in Sources */,
|
||||||
E98254BD23F9B7A90056D9D3 /* Playlist.swift in Sources */,
|
E98254BD23F9B7A90056D9D3 /* Playlist.swift in Sources */,
|
||||||
|
E97AF46723FD650800635494 /* AddPlaylistSheet.swift in Sources */,
|
||||||
E98254C223F9FFF90056D9D3 /* PlaylistView.swift in Sources */,
|
E98254C223F9FFF90056D9D3 /* PlaylistView.swift in Sources */,
|
||||||
|
E97AF46423FD4EEF00635494 /* LiveUser.swift in Sources */,
|
||||||
E97AF45623FC4E7800635494 /* User.swift in Sources */,
|
E97AF45623FC4E7800635494 /* User.swift in Sources */,
|
||||||
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */,
|
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */,
|
||||||
|
E97AF46923FD9E1B00635494 /* PlaylistInputList.swift in Sources */,
|
||||||
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */,
|
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -523,7 +543,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Music Tools/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "Music\\ Tools/Preview\\ Content";
|
||||||
DEVELOPMENT_TEAM = 8UZ2659FDY;
|
DEVELOPMENT_TEAM = 8UZ2659FDY;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = "Music Tools/Info.plist";
|
INFOPLIST_FILE = "Music Tools/Info.plist";
|
||||||
@ -543,7 +563,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Music Tools/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "Music\\ Tools/Preview\\ Content";
|
||||||
DEVELOPMENT_TEAM = 8UZ2659FDY;
|
DEVELOPMENT_TEAM = 8UZ2659FDY;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = "Music Tools/Info.plist";
|
INFOPLIST_FILE = "Music Tools/Info.plist";
|
||||||
|
@ -7,14 +7,25 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SwiftyJSON
|
||||||
|
import KeychainAccess
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
|
var liveUser: LiveUser?
|
||||||
|
|
||||||
|
var loading = true
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
// Override point for customization after application launch.
|
// Override point for customization after application launch.
|
||||||
|
|
||||||
|
let keychain = Keychain(service: "xyz.sarsoo.music.login")
|
||||||
|
keychain["username"] = ""
|
||||||
|
keychain["password"] = ""
|
||||||
|
|
||||||
|
liveUser = LiveUser(playlists: [])
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="Logo" translatesAutoresizingMaskIntoConstraints="NO" id="nIw-aa-ATp">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" fixedFrame="YES" image="MusicToolsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="NIx-qI-fR9">
|
||||||
<rect key="frame" x="-49" y="192" width="512" height="512"/>
|
<rect key="frame" x="-59" y="184" width="512" height="512"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@ -30,6 +30,6 @@
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="Logo" width="250" height="40"/>
|
<image name="MusicToolsLogo" width="512" height="512"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
@ -1,7 +1,161 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<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">
|
<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>
|
<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>
|
</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>
|
</document>
|
||||||
|
@ -8,13 +8,11 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import KeychainAccess
|
|
||||||
|
|
||||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
|
|
||||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
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`.
|
// 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.
|
// 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.
|
// Create the SwiftUI view that provides the window contents.
|
||||||
let contentView = RootView()
|
let contentView = RootView()
|
||||||
|
|
||||||
let keychain = Keychain(service: "xyz.sarsoo.music.login")
|
var liveUser: LiveUser?
|
||||||
keychain["username"] = ""
|
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
|
||||||
keychain["password"] = ""
|
liveUser = appDelegate.liveUser
|
||||||
|
}
|
||||||
|
|
||||||
// Use a UIHostingController as window root view controller.
|
// Use a UIHostingController as window root view controller.
|
||||||
if let windowScene = scene as? UIWindowScene {
|
if let windowScene = scene as? UIWindowScene {
|
||||||
let window = UIWindow(windowScene: windowScene)
|
let window = UIWindow(windowScene: windowScene)
|
||||||
window.rootViewController = UIHostingController(rootView: contentView)
|
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(liveUser ?? LiveUser(playlists: [])))
|
||||||
self.window = window
|
self.window = window
|
||||||
window.makeKeyAndVisible()
|
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 UIKit
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
class Playlist: Identifiable {
|
class Playlist: Identifiable, Equatable {
|
||||||
|
|
||||||
//MARK: Properties
|
//MARK: Properties
|
||||||
|
|
||||||
@ -70,4 +70,14 @@ class Playlist: Identifiable {
|
|||||||
shuffle: dictionary["shuffle"].boolValue)
|
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 Alamofire
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
|
let txTypeHeaders = ["default", "recents", "fmchart"]
|
||||||
|
|
||||||
|
public enum PlaylistType: Int {
|
||||||
|
case defaultPlaylist = 0
|
||||||
|
case recents = 1
|
||||||
|
case fmchart = 2
|
||||||
|
}
|
||||||
|
|
||||||
public enum PlaylistApi {
|
public enum PlaylistApi {
|
||||||
case getPlaylists
|
case getPlaylists
|
||||||
case runPlaylist(name: String)
|
case runPlaylist(name: String)
|
||||||
case updatePlaylist(name: String, updates: JSON)
|
case updatePlaylist(name: String, updates: JSON)
|
||||||
|
case deletePlaylist(name: String)
|
||||||
|
case newPlaylist(name: String, type: PlaylistType)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PlaylistApi: ApiRequest {
|
extension PlaylistApi: ApiRequest {
|
||||||
@ -29,6 +39,10 @@ extension PlaylistApi: ApiRequest {
|
|||||||
return "api/playlist/run"
|
return "api/playlist/run"
|
||||||
case .updatePlaylist:
|
case .updatePlaylist:
|
||||||
return "api/playlist"
|
return "api/playlist"
|
||||||
|
case .deletePlaylist:
|
||||||
|
return "api/playlist"
|
||||||
|
case .newPlaylist:
|
||||||
|
return "api/playlist"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +54,10 @@ extension PlaylistApi: ApiRequest {
|
|||||||
return .get
|
return .get
|
||||||
case .updatePlaylist:
|
case .updatePlaylist:
|
||||||
return .post
|
return .post
|
||||||
|
case .deletePlaylist:
|
||||||
|
return .delete
|
||||||
|
case .newPlaylist:
|
||||||
|
return .put
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,8 +70,11 @@ extension PlaylistApi: ApiRequest {
|
|||||||
case .updatePlaylist(let name, let updates):
|
case .updatePlaylist(let name, let updates):
|
||||||
var txUpdates = updates
|
var txUpdates = updates
|
||||||
txUpdates["name"].string = name
|
txUpdates["name"].string = name
|
||||||
debugPrint(txUpdates)
|
|
||||||
return 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()
|
return URLEncodedFormParameterEncoder()
|
||||||
case .updatePlaylist:
|
case .updatePlaylist:
|
||||||
return JSONParameterEncoder.default
|
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 SwiftUI
|
||||||
|
import SwiftyJSON
|
||||||
|
|
||||||
struct PlaylistRow: View {
|
struct PlaylistRow: View {
|
||||||
|
|
||||||
|
@EnvironmentObject var liveUser: LiveUser
|
||||||
|
|
||||||
var playlist: Playlist
|
var playlist: Playlist
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationLink(destination: PlaylistView(playlist: playlist)){
|
NavigationLink(destination: PlaylistView(playlist: playlist)){
|
||||||
HStack {
|
HStack {
|
||||||
Text(playlist.name)
|
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
|
import SwiftyJSON
|
||||||
|
|
||||||
struct PlaylistView: View {
|
struct PlaylistView: View {
|
||||||
|
|
||||||
|
init(playlist: Playlist) {
|
||||||
|
self.playlist = playlist
|
||||||
|
UITableView.appearance().tableFooterView = UIView()
|
||||||
|
}
|
||||||
|
|
||||||
var playlist: Playlist
|
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 library_Tracks: Bool = false
|
||||||
@State private var shuffle: Bool = false
|
@State private var shuffle: Bool = false
|
||||||
|
|
||||||
@State private var rec_num: Int = 0
|
@State private var rec_num: Int = 0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
List {
|
||||||
VStack(alignment: .leading) {
|
Section(header: Text("Options")){
|
||||||
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)
|
|
||||||
|
|
||||||
Toggle(isOn: $recommendations) {
|
Toggle(isOn: $recommendations) {
|
||||||
Text("Spotify Recommendations")
|
Text("Spotify Recommendations")
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
|
|
||||||
if recommendations {
|
// if recommendations {
|
||||||
Stepper(onIncrement: {
|
Stepper(onIncrement: {
|
||||||
self.$rec_num.wrappedValue += 1
|
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]))
|
||||||
},
|
},
|
||||||
onDecrement: {
|
onDecrement: {
|
||||||
self.$rec_num.wrappedValue -= 1
|
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("#:")
|
Text("#:")
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
.multilineTextAlignment(.trailing)
|
.multilineTextAlignment(.trailing)
|
||||||
.padding(.leading, 20)
|
|
||||||
Text("\(rec_num)")
|
Text("\(rec_num)")
|
||||||
.multilineTextAlignment(.trailing)
|
.multilineTextAlignment(.trailing)
|
||||||
|
|
||||||
}.padding()
|
}
|
||||||
}
|
// }
|
||||||
|
|
||||||
Toggle(isOn: $library_Tracks) {
|
Toggle(isOn: $library_Tracks) {
|
||||||
Text("Library Tracks")
|
Text("Library Tracks")
|
||||||
}.padding()
|
}
|
||||||
|
|
||||||
Toggle(isOn: $shuffle) {
|
Toggle(isOn: $shuffle) {
|
||||||
Text("Shuffle")
|
Text("Shuffle")
|
||||||
}.padding()
|
}
|
||||||
|
}
|
||||||
HStack {
|
Section(header: Text("Inputs")){
|
||||||
Button(action: { self.runPlaylist() }) {
|
NavigationLink(destination: PlaylistInputList(names: self.playlist.playlist_references, nameType: "Managed Playlists")) {
|
||||||
Text("Update")
|
HStack {
|
||||||
}.padding().multilineTextAlignment(.center)
|
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)
|
Section(header: Text("Actions")){
|
||||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
|
Button(action: { self.runPlaylist() }) {
|
||||||
.onAppear {
|
Text("Update")
|
||||||
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
|
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() {
|
func runPlaylist() {
|
||||||
let api = PlaylistApi.runPlaylist(name: playlist.name)
|
let api = PlaylistApi.runPlaylist(name: playlist.name)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
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
|
//TODO: do better error checking
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openPlaylist() {
|
||||||
|
if let url = URL(string: self.playlist.link) {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updatePlaylist(updates: JSON) {
|
func updatePlaylist(updates: JSON) {
|
||||||
let api = PlaylistApi.updatePlaylist(name: playlist.name, updates: updates)
|
let api = PlaylistApi.updatePlaylist(name: playlist.name, updates: updates)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
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
|
//TODO: do better error checking
|
||||||
}
|
}
|
||||||
|
@ -11,16 +11,56 @@ import Alamofire
|
|||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
struct RootView: View {
|
struct RootView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject var liveUser: LiveUser
|
||||||
|
|
||||||
@State private var selection = 0
|
@State private var selection = 0
|
||||||
@State private var playlists: Array<Playlist> = []
|
@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 {
|
var body: some View {
|
||||||
TabView(selection: $selection){
|
TabView {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List(playlists) { playlist in
|
List{
|
||||||
PlaylistRow(playlist: playlist)
|
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))
|
.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 {
|
.tabItem {
|
||||||
VStack {
|
VStack {
|
||||||
@ -57,6 +97,9 @@ struct RootView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tag(2)
|
.tag(2)
|
||||||
|
.onReceive(timer) { _ in
|
||||||
|
self.fetch()
|
||||||
|
}
|
||||||
}.onAppear {
|
}.onAppear {
|
||||||
self.fetch()
|
self.fetch()
|
||||||
}
|
}
|
||||||
@ -74,9 +117,23 @@ struct RootView: View {
|
|||||||
fatalError("error parsing reponse")
|
fatalError("error parsing reponse")
|
||||||
}
|
}
|
||||||
|
|
||||||
self.playlists = json["playlists"].arrayValue.map({ dict in
|
let playlists = json["playlists"].arrayValue.map({ dict in
|
||||||
Playlist.fromDict(dictionary: dict)
|
Playlist.fromDict(dictionary: dict)
|
||||||
}).sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
}).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
|
//TODO: do better error checking
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user