mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 18:54:11 +00:00
Channels layout improvements, other UI fixes
This commit is contained in:
@@ -3,20 +3,41 @@ import Defaults
|
||||
import Foundation
|
||||
import SwiftyJSON
|
||||
|
||||
struct Channel: Codable, Identifiable, Defaults.Serializable {
|
||||
struct Channel: Identifiable, Hashable {
|
||||
var id: String
|
||||
var name: String
|
||||
var subscriptionsCount: String
|
||||
var videos = [Video]()
|
||||
|
||||
private var subscriptionsCount: Int?
|
||||
private var subscriptionsText: String?
|
||||
|
||||
init(json: JSON) {
|
||||
id = json["authorId"].stringValue
|
||||
name = json["author"].stringValue
|
||||
subscriptionsCount = json["subCountText"].stringValue
|
||||
subscriptionsCount = json["subCount"].int
|
||||
subscriptionsText = json["subCountText"].string
|
||||
|
||||
if let channelVideos = json.dictionaryValue["latestVideos"] {
|
||||
videos = channelVideos.arrayValue.map(Video.init)
|
||||
}
|
||||
}
|
||||
|
||||
init(id: String, name: String, subscriptionsCount: String) {
|
||||
init(id: String, name: String, subscriptionsCount: Int? = nil, videos: [Video] = []) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.subscriptionsCount = subscriptionsCount
|
||||
self.videos = videos
|
||||
}
|
||||
|
||||
var subscriptionsString: String? {
|
||||
if subscriptionsCount != nil {
|
||||
return subscriptionsCount!.formattedAsAbbreviation()
|
||||
}
|
||||
|
||||
return subscriptionsText
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
}
|
||||
|
@@ -75,12 +75,8 @@ final class InvidiousAPI: Service {
|
||||
content.json.arrayValue.map(Channel.init)
|
||||
}
|
||||
|
||||
configureTransformer("/channels/*", requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
|
||||
if let channelVideos = content.json.dictionaryValue["latestVideos"] {
|
||||
return channelVideos.arrayValue.map(Video.init)
|
||||
}
|
||||
|
||||
return []
|
||||
configureTransformer("/channels/*", requestMethods: [.get]) { (content: Entity<JSON>) -> Channel in
|
||||
Channel(json: content.json)
|
||||
}
|
||||
|
||||
configureTransformer("/videos/*", requestMethods: [.get]) { (content: Entity<JSON>) -> Video in
|
||||
@@ -112,7 +108,7 @@ final class InvidiousAPI: Service {
|
||||
resource("/auth/subscriptions").child(id)
|
||||
}
|
||||
|
||||
func channelVideos(_ id: String) -> Resource {
|
||||
func channel(_ id: String) -> Resource {
|
||||
resource("/channels/\(id)")
|
||||
}
|
||||
|
||||
|
@@ -8,9 +8,6 @@ final class NavigationState: ObservableObject {
|
||||
|
||||
@Published var tabSelection: TabSelection = .subscriptions
|
||||
|
||||
@Published var showingChannel = false
|
||||
@Published var channel: Channel?
|
||||
|
||||
@Published var showingVideoDetails = false
|
||||
@Published var showingVideo = false
|
||||
@Published var video: Video?
|
||||
@@ -23,15 +20,40 @@ final class NavigationState: ObservableObject {
|
||||
@Published var presentingUnsubscribeAlert = false
|
||||
@Published var channelToUnsubscribe: Channel!
|
||||
|
||||
@Published var openChannels = Set<Channel>()
|
||||
@Published var isChannelOpen = false
|
||||
@Published var sidebarSectionChanged = false
|
||||
|
||||
func openChannel(_ channel: Channel) {
|
||||
returnToDetails = false
|
||||
self.channel = channel
|
||||
showingChannel = true
|
||||
openChannels.insert(channel)
|
||||
|
||||
isChannelOpen = true
|
||||
tabSelection = .channel(channel.id)
|
||||
}
|
||||
|
||||
func closeChannel() {
|
||||
showingChannel = false
|
||||
channel = nil
|
||||
func closeChannel(_ channel: Channel) {
|
||||
guard openChannels.remove(channel) != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
isChannelOpen = !openChannels.isEmpty
|
||||
|
||||
if tabSelection == .channel(channel.id) {
|
||||
tabSelection = .subscriptions
|
||||
}
|
||||
}
|
||||
|
||||
func closeAllChannels() {
|
||||
isChannelOpen = false
|
||||
openChannels.removeAll()
|
||||
}
|
||||
|
||||
func showOpenChannel(_ id: Channel.ID) -> Bool {
|
||||
if case .channel = tabSelection {
|
||||
return false
|
||||
} else {
|
||||
return !openChannels.contains { $0.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
func openVideoDetails(_ video: Video) {
|
||||
@@ -59,8 +81,10 @@ final class NavigationState: ObservableObject {
|
||||
get: {
|
||||
self.tabSelection
|
||||
},
|
||||
set: {
|
||||
self.tabSelection = $0 ?? .subscriptions
|
||||
set: { newValue in
|
||||
if newValue != nil {
|
||||
self.tabSelection = newValue!
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@@ -17,29 +17,30 @@ final class Subscriptions: ObservableObject {
|
||||
channels.sorted { $0.name.lowercased() < $1.name.lowercased() }
|
||||
}
|
||||
|
||||
func subscribe(_ channelID: String) {
|
||||
performChannelSubscriptionRequest(channelID, method: .post)
|
||||
func subscribe(_ channelID: String, onSuccess: @escaping () -> Void = {}) {
|
||||
performChannelSubscriptionRequest(channelID, method: .post, onSuccess: onSuccess)
|
||||
}
|
||||
|
||||
func unsubscribe(_ channelID: String) {
|
||||
performChannelSubscriptionRequest(channelID, method: .delete)
|
||||
func unsubscribe(_ channelID: String, onSuccess: @escaping () -> Void = {}) {
|
||||
performChannelSubscriptionRequest(channelID, method: .delete, onSuccess: onSuccess)
|
||||
}
|
||||
|
||||
func subscribed(_ channelID: String) -> Bool {
|
||||
func isSubscribing(_ channelID: String) -> Bool {
|
||||
channels.contains { $0.id == channelID }
|
||||
}
|
||||
|
||||
fileprivate func load() {
|
||||
fileprivate func load(onSuccess: @escaping () -> Void = {}) {
|
||||
resource.load().onSuccess { resource in
|
||||
if let channels: [Channel] = resource.typedContent() {
|
||||
self.channels = channels
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func performChannelSubscriptionRequest(_ channelID: String, method: RequestMethod) {
|
||||
fileprivate func performChannelSubscriptionRequest(_ channelID: String, method: RequestMethod, onSuccess: @escaping () -> Void = {}) {
|
||||
InvidiousAPI.shared.channelSubscription(channelID).request(method).onCompletion { _ in
|
||||
self.load()
|
||||
self.load(onSuccess: onSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -127,40 +127,15 @@ struct Video: Identifiable, Equatable {
|
||||
}
|
||||
|
||||
var viewsCount: String? {
|
||||
views != 0 ? formattedCount(views) : nil
|
||||
views != 0 ? views.formattedAsAbbreviation() : nil
|
||||
}
|
||||
|
||||
var likesCount: String? {
|
||||
formattedCount(likes)
|
||||
likes?.formattedAsAbbreviation()
|
||||
}
|
||||
|
||||
var dislikesCount: String? {
|
||||
formattedCount(dislikes)
|
||||
}
|
||||
|
||||
func formattedCount(_ count: Int!) -> String? {
|
||||
guard count != nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .decimal
|
||||
formatter.maximumFractionDigits = 1
|
||||
|
||||
var number: NSNumber
|
||||
var unit: String
|
||||
|
||||
if count < 1000 {
|
||||
return "\(count!)"
|
||||
} else if count < 1_000_000 {
|
||||
number = NSNumber(value: Double(count) / 1000.0)
|
||||
unit = "K"
|
||||
} else {
|
||||
number = NSNumber(value: Double(count) / 1_000_000.0)
|
||||
unit = "M"
|
||||
}
|
||||
|
||||
return "\(formatter.string(from: number)!)\(unit)"
|
||||
dislikes?.formattedAsAbbreviation()
|
||||
}
|
||||
|
||||
var selectableStreams: [Stream] {
|
||||
|
Reference in New Issue
Block a user