added pull to refresh, moved playlist and tag reference to environment object, added get playlist

This commit is contained in:
aj 2020-02-22 13:28:15 +00:00
parent 54a3806908
commit fea9e3f84b
7 changed files with 147 additions and 29 deletions

View File

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
E92F94822401412100B6B721 /* SwiftUIRefresh in Frameworks */ = {isa = PBXBuildFile; productRef = E92F94812401412100B6B721 /* SwiftUIRefresh */; };
E97AF45623FC4E7800635494 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45523FC4E7800635494 /* User.swift */; }; E97AF45623FC4E7800635494 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45523FC4E7800635494 /* User.swift */; };
E97AF45923FC50EC00635494 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = E97AF45823FC50EC00635494 /* SwiftyJSON */; }; E97AF45923FC50EC00635494 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = E97AF45823FC50EC00635494 /* SwiftyJSON */; };
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45A23FC748D00635494 /* UserApi.swift */; }; E97AF45B23FC748D00635494 /* UserApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45A23FC748D00635494 /* UserApi.swift */; };
@ -97,6 +98,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E92F94822401412100B6B721 /* SwiftUIRefresh in Frameworks */,
E97AF45923FC50EC00635494 /* SwiftyJSON in Frameworks */, E97AF45923FC50EC00635494 /* SwiftyJSON in Frameworks */,
E98254D923FB53780056D9D3 /* Alamofire in Frameworks */, E98254D923FB53780056D9D3 /* Alamofire in Frameworks */,
E97AF45E23FC83AF00635494 /* KeychainAccess in Frameworks */, E97AF45E23FC83AF00635494 /* KeychainAccess in Frameworks */,
@ -225,14 +227,14 @@
E9EA690923F9A5430012C3E8 /* Music Tools */ = { E9EA690923F9A5430012C3E8 /* Music Tools */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E97AF46A23FDA8ED00635494 /* Controller */,
E98254C623FA25280056D9D3 /* Application */, E98254C623FA25280056D9D3 /* Application */,
E98254C023F9FFDD0056D9D3 /* Views */,
E98254BF23F9BE040056D9D3 /* Network */,
E98254BE23F9BD540056D9D3 /* Model */,
E9EA691023F9A54A0012C3E8 /* Assets.xcassets */, E9EA691023F9A54A0012C3E8 /* Assets.xcassets */,
E97AF46A23FDA8ED00635494 /* Controller */,
E9EA691823F9A54B0012C3E8 /* Info.plist */, E9EA691823F9A54B0012C3E8 /* Info.plist */,
E98254BE23F9BD540056D9D3 /* Model */,
E98254BF23F9BE040056D9D3 /* Network */,
E9EA691223F9A54B0012C3E8 /* Preview Content */, E9EA691223F9A54B0012C3E8 /* Preview Content */,
E98254C023F9FFDD0056D9D3 /* Views */,
); );
path = "Music Tools"; path = "Music Tools";
sourceTree = "<group>"; sourceTree = "<group>";
@ -283,6 +285,7 @@
E98254D823FB53780056D9D3 /* Alamofire */, E98254D823FB53780056D9D3 /* Alamofire */,
E97AF45823FC50EC00635494 /* SwiftyJSON */, E97AF45823FC50EC00635494 /* SwiftyJSON */,
E97AF45D23FC83AF00635494 /* KeychainAccess */, E97AF45D23FC83AF00635494 /* KeychainAccess */,
E92F94812401412100B6B721 /* SwiftUIRefresh */,
); );
productName = "Music Tools"; productName = "Music Tools";
productReference = E9EA690723F9A5430012C3E8 /* Music Tools.app */; productReference = E9EA690723F9A5430012C3E8 /* Music Tools.app */;
@ -360,6 +363,7 @@
E98254D723FB53770056D9D3 /* XCRemoteSwiftPackageReference "alamofire" */, E98254D723FB53770056D9D3 /* XCRemoteSwiftPackageReference "alamofire" */,
E97AF45723FC50EC00635494 /* XCRemoteSwiftPackageReference "swiftyjson" */, E97AF45723FC50EC00635494 /* XCRemoteSwiftPackageReference "swiftyjson" */,
E97AF45C23FC83AF00635494 /* XCRemoteSwiftPackageReference "keychainaccess" */, E97AF45C23FC83AF00635494 /* XCRemoteSwiftPackageReference "keychainaccess" */,
E92F94802401412100B6B721 /* XCRemoteSwiftPackageReference "swiftuirefresh" */,
); );
productRefGroup = E9EA690823F9A5430012C3E8 /* Products */; productRefGroup = E9EA690823F9A5430012C3E8 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -752,6 +756,14 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */
E92F94802401412100B6B721 /* XCRemoteSwiftPackageReference "swiftuirefresh" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/siteline/swiftuirefresh";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.0.2;
};
};
E97AF45723FC50EC00635494 /* XCRemoteSwiftPackageReference "swiftyjson" */ = { E97AF45723FC50EC00635494 /* XCRemoteSwiftPackageReference "swiftyjson" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/swiftyjson/swiftyjson"; repositoryURL = "https://github.com/swiftyjson/swiftyjson";
@ -779,6 +791,11 @@
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
E92F94812401412100B6B721 /* SwiftUIRefresh */ = {
isa = XCSwiftPackageProductDependency;
package = E92F94802401412100B6B721 /* XCRemoteSwiftPackageReference "swiftuirefresh" */;
productName = SwiftUIRefresh;
};
E97AF45823FC50EC00635494 /* SwiftyJSON */ = { E97AF45823FC50EC00635494 /* SwiftyJSON */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E97AF45723FC50EC00635494 /* XCRemoteSwiftPackageReference "swiftyjson" */; package = E97AF45723FC50EC00635494 /* XCRemoteSwiftPackageReference "swiftyjson" */;

View File

@ -19,6 +19,24 @@
"version": "4.1.0" "version": "4.1.0"
} }
}, },
{
"package": "Introspect",
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git",
"state": {
"branch": null,
"revision": "de5c32c15ae169cfcb27397ffb2734dcd0e1e6d5",
"version": "0.1.0"
}
},
{
"package": "SwiftUIRefresh",
"repositoryURL": "https://github.com/siteline/swiftuirefresh",
"state": {
"branch": null,
"revision": "6939ec13efa866eb9f556f5de7f0e06f5d7f2761",
"version": "0.0.2"
}
},
{ {
"package": "SwiftyJSON", "package": "SwiftyJSON",
"repositoryURL": "https://github.com/swiftyjson/swiftyjson", "repositoryURL": "https://github.com/swiftyjson/swiftyjson",

View File

@ -33,6 +33,9 @@
<rect key="frame" x="196" y="633" width="120" height="53"/> <rect key="frame" x="196" y="633" width="120" height="53"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle0"/> <fontDescription key="fontDescription" style="UICTFontTextStyleTitle0"/>
<state key="normal" title="Register"/> <state key="normal" title="Register"/>
<connections>
<segue destination="TND-IP-OdM" kind="show" id="96d-mV-baq"/>
</connections>
</button> </button>
</subviews> </subviews>
<constraints> <constraints>
@ -57,11 +60,12 @@
<objects> <objects>
<viewController id="TND-IP-OdM" sceneMemberID="viewController"> <viewController id="TND-IP-OdM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="MhD-yZ-pD8"> <view key="view" contentMode="scaleToFill" id="MhD-yZ-pD8">
<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="842"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="VLy-9d-bfF"/> <viewLayoutGuide key="safeArea" id="VLy-9d-bfF"/>
</view> </view>
<navigationItem key="navigationItem" id="ols-n0-cLe"/>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Dgg-BJ-VHU" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="Dgg-BJ-VHU" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
@ -102,7 +106,7 @@
<constraint firstAttribute="height" constant="34" id="ynd-9a-coG"/> <constraint firstAttribute="height" constant="34" id="ynd-9a-coG"/>
</constraints> </constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" returnKeyType="go" textContentType="password"/> <textInputTraits key="textInputTraits" returnKeyType="go" secureTextEntry="YES" textContentType="password"/>
</textField> </textField>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6YH-OC-12B"> <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="712" width="43" height="53"/> <rect key="frame" x="185.5" y="712" width="43" height="53"/>

View File

@ -9,11 +9,19 @@
import Foundation import Foundation
class LiveUser: ObservableObject { class LiveUser: ObservableObject {
var playlists: [Playlist]
var tags: [Tag] @Published var playlists: [Playlist]
@Published var tags: [Tag]
init(playlists: [Playlist], tags: [Tag]) { init(playlists: [Playlist], tags: [Tag]) {
self.playlists = playlists self.playlists = playlists
self.tags = tags self.tags = tags
} }
func updatePlaylist(playlistIn: Playlist) {
guard let index = self.playlists.firstIndex(of: playlistIn) else {
fatalError("\(playlistIn) not found")
}
self.playlists[index] = playlistIn
}
} }

View File

@ -24,6 +24,7 @@ public enum PlaylistApi {
case updatePlaylist(name: String, updates: JSON) case updatePlaylist(name: String, updates: JSON)
case deletePlaylist(name: String) case deletePlaylist(name: String)
case newPlaylist(name: String, type: PlaylistType) case newPlaylist(name: String, type: PlaylistType)
case getPlaylist(name: String)
} }
extension PlaylistApi: ApiRequest { extension PlaylistApi: ApiRequest {
@ -43,6 +44,8 @@ extension PlaylistApi: ApiRequest {
return "api/playlist" return "api/playlist"
case .newPlaylist: case .newPlaylist:
return "api/playlist" return "api/playlist"
case .getPlaylist:
return "api/playlist"
} }
} }
@ -58,6 +61,8 @@ extension PlaylistApi: ApiRequest {
return .delete return .delete
case .newPlaylist: case .newPlaylist:
return .put return .put
case .getPlaylist:
return .get
} }
} }
@ -75,6 +80,8 @@ extension PlaylistApi: ApiRequest {
return JSON(["name": name]) return JSON(["name": name])
case .newPlaylist(let name, let type): case .newPlaylist(let name, let type):
return JSON(["name": name, "type": txTypeHeaders[type.rawValue]]) return JSON(["name": name, "type": txTypeHeaders[type.rawValue]])
case .getPlaylist(let name):
return JSON(["name": name])
} }
} }
@ -90,6 +97,8 @@ extension PlaylistApi: ApiRequest {
return URLEncodedFormParameterEncoder() return URLEncodedFormParameterEncoder()
case .newPlaylist: case .newPlaylist:
return JSONParameterEncoder.default return JSONParameterEncoder.default
case .getPlaylist:
return URLEncodedFormParameterEncoder()
} }
} }

View File

@ -7,10 +7,28 @@
// //
import SwiftUI import SwiftUI
//import SwiftUIRefresh
import SwiftyJSON import SwiftyJSON
final class ChangeableBool: ObservableObject {
var onClick: () -> ()
init(onClick: @escaping () -> ()) {
self.onClick = onClick
}
@Published var state: Bool = false {
didSet {
self.onClick()
}
}
}
struct PlaylistView: View { struct PlaylistView: View {
@EnvironmentObject var liveUser: LiveUser
init(playlist: Playlist) { init(playlist: Playlist) {
self.playlist = playlist self.playlist = playlist
@ -25,6 +43,8 @@ struct PlaylistView: View {
@State private var rec_num: Int = 0 @State private var rec_num: Int = 0
@State private var isRefreshing = false
var body: some View { var body: some View {
List { List {
Section(header: Text("Options")){ Section(header: Text("Options")){
@ -87,6 +107,9 @@ struct PlaylistView: View {
} }
} }
} }
// .pullToRefresh(isShowing: $isRefreshing) {
// self.refreshPlaylist()
// }
.navigationBarTitle(Text(playlist.name)) .navigationBarTitle(Text(playlist.name))
.onAppear { .onAppear {
self.$recommendations.wrappedValue = self.playlist.include_recommendations self.$recommendations.wrappedValue = self.playlist.include_recommendations
@ -114,7 +137,31 @@ struct PlaylistView: View {
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
switch response.result {
case .success:
debugPrint("success")
case .failure:
debugPrint("error")
}
}
//TODO: do better error checking
}
func refreshPlaylist(updates: JSON) {
let api = PlaylistApi.getPlaylist(name: self.playlist.name)
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
guard let data = response.data else {
fatalError("error getting playlist")
}
guard let json = try? JSON(data: data) else {
fatalError("error parsing reponse")
}
// let playlist = Playlist.fromDict(json["playlist"])
//
// self.playlist = playlist
// self.isRefreshing = false
} }
//TODO: do better error checking //TODO: do better error checking
} }

View File

@ -7,6 +7,7 @@
// //
import SwiftUI import SwiftUI
import SwiftUIRefresh
import Alamofire import Alamofire
import SwiftyJSON import SwiftyJSON
@ -15,16 +16,17 @@ struct RootView: View {
@EnvironmentObject var liveUser: LiveUser @EnvironmentObject var liveUser: LiveUser
@State private var selection = 0 // Tab view selection @State private var selection = 0 // Tab view selection
@State private var playlists: Array<Playlist> = [] // Network pulled playlists
@State private var tags: Array<Tag> = [] // Network pulled tags
@State private var showAdd = false // State for showing add modal view @State private var showAdd = false // State for showing add modal view
@State private var justDeletedPlaylists: Array<Playlist> = [] // Cache of recently deleted playlists for removing from next net request @State private var justDeletedPlaylists: Array<Playlist> = [] // Cache of recently deleted playlists for removing from next net request
@State private var justDeletedTags: Array<Tag> = [] @State private var justDeletedTags: Array<Tag> = []
@State private var isRefreshingPlaylists = false
@State private var isRefreshingTags = false
// refresh playlist list on interval // refresh playlist list on interval
let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() // let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
var body: some View { var body: some View {
TabView { TabView {
@ -32,24 +34,27 @@ struct RootView: View {
// PLAYLISTS // PLAYLISTS
NavigationView { NavigationView {
List{ List{
ForEach(playlists) { playlist in ForEach(liveUser.playlists) { playlist in
PlaylistRow(playlist: playlist) PlaylistRow(playlist: playlist)
} }
.onDelete { indexSet in .onDelete { indexSet in
indexSet.forEach { index in indexSet.forEach { index in
// add to recently deleted playlist cache // add to recently deleted playlist cache
self.justDeletedPlaylists.append(self.playlists[index]) self.justDeletedPlaylists.append(self.liveUser.playlists[index])
let api = PlaylistApi.deletePlaylist(name: self.playlists[index].name) let api = PlaylistApi.deletePlaylist(name: self.liveUser.playlists[index].name)
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
} }
} }
self.playlists.remove(atOffsets: indexSet) self.liveUser.playlists.remove(atOffsets: indexSet)
} }
} }
.pullToRefresh(isShowing: $isRefreshingPlaylists) {
self.refreshPlaylists()
}
.navigationBarTitle(Text("Playlists").font(.title)) .navigationBarTitle(Text("Playlists").font(.title))
// add playlist button // add playlist button
@ -58,7 +63,7 @@ struct RootView: View {
action: { self.showAdd = true }, action: { self.showAdd = true },
label: { Text("Add") } label: { Text("Add") }
).sheet(isPresented: $showAdd) { ).sheet(isPresented: $showAdd) {
AddPlaylistSheet(state: self.$showAdd, playlists: self.$playlists) AddPlaylistSheet(state: self.$showAdd, playlists: self.$liveUser.playlists)
} }
) )
} }
@ -73,24 +78,27 @@ struct RootView: View {
// TAGS // TAGS
NavigationView { NavigationView {
List{ List{
ForEach(tags) { tag in ForEach(liveUser.tags) { tag in
TagRow(tag: tag) TagRow(tag: tag)
} }
.onDelete { indexSet in .onDelete { indexSet in
indexSet.forEach { index in indexSet.forEach { index in
// add to recently deleted playlist cache // add to recently deleted playlist cache
self.justDeletedTags.append(self.tags[index]) self.justDeletedTags.append(self.liveUser.tags[index])
let api = TagApi.deleteTag(tag_id: self.tags[index].tag_id) let api = TagApi.deleteTag(tag_id: self.liveUser.tags[index].tag_id)
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
} }
} }
self.tags.remove(atOffsets: indexSet) self.liveUser.tags.remove(atOffsets: indexSet)
} }
} }
.pullToRefresh(isShowing: $isRefreshingTags) {
self.refreshTags()
}
.navigationBarTitle(Text("Tags").font(.title)) .navigationBarTitle(Text("Tags").font(.title))
// add playlist button // add playlist button
@ -99,7 +107,7 @@ struct RootView: View {
action: { self.showAdd = true }, action: { self.showAdd = true },
label: { Text("Add") } label: { Text("Add") }
).sheet(isPresented: $showAdd) { ).sheet(isPresented: $showAdd) {
AddPlaylistSheet(state: self.$showAdd, playlists: self.$playlists) AddPlaylistSheet(state: self.$showAdd, playlists: self.$liveUser.playlists)
} }
) )
} }
@ -122,15 +130,20 @@ struct RootView: View {
} }
} }
.tag(2) .tag(2)
.onReceive(timer) { _ in // .onReceive(timer) { _ in
self.fetch() // self.fetch()
} // }
}.onAppear { }.onAppear {
self.fetch() self.fetchAll()
} }
} }
private func fetch() { private func fetchAll() {
refreshPlaylists()
refreshTags()
}
func refreshPlaylists() {
let api = PlaylistApi.getPlaylists let api = PlaylistApi.getPlaylists
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
@ -165,10 +178,12 @@ struct RootView: View {
// update state // update state
self.liveUser.playlists = playlists self.liveUser.playlists = playlists
self.playlists = self.liveUser.playlists self.isRefreshingPlaylists = false
} }
//TODO: do better error checking //TODO: do better error checking
}
func refreshTags() {
let tagApi = TagApi.getTags let tagApi = TagApi.getTags
RequestBuilder.buildRequest(apiRequest: tagApi).responseJSON{ response in RequestBuilder.buildRequest(apiRequest: tagApi).responseJSON{ response in
@ -203,7 +218,7 @@ struct RootView: View {
// update state // update state
self.liveUser.tags = tags self.liveUser.tags = tags
self.tags = self.liveUser.tags self.isRefreshingTags = false
} }
} }
} }