summaryrefslogtreecommitdiff
path: root/JuickNext
diff options
context:
space:
mode:
Diffstat (limited to 'JuickNext')
-rw-r--r--JuickNext/FeedView.swift55
-rw-r--r--JuickNext/Helpers/Image+Data.swift35
-rw-r--r--JuickNext/ImageFetcher.swift28
-rw-r--r--JuickNext/LoadableImageView.swift29
-rw-r--r--JuickNext/LoadableState.swift25
-rw-r--r--JuickNext/MessageFetcher.swift39
-rw-r--r--JuickNext/MessageView.swift43
-rw-r--r--JuickNext/Models.swift47
-rw-r--r--JuickNext/View/ContentView.swift13
-rw-r--r--JuickNext/View/Discussions.swift21
-rw-r--r--JuickNext/View/Today.swift24
11 files changed, 310 insertions, 49 deletions
diff --git a/JuickNext/FeedView.swift b/JuickNext/FeedView.swift
new file mode 100644
index 0000000..399da1f
--- /dev/null
+++ b/JuickNext/FeedView.swift
@@ -0,0 +1,55 @@
+//
+// FeedView.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import SwiftUI
+
+struct FeedView: View {
+ @ObservedObject var messageFetcher : MessageFetcher
+
+ let title: String
+
+ init(_ title: String, url: String) {
+ self.title = title
+ messageFetcher = MessageFetcher(url: url)
+ }
+
+ private var stateContent: AnyView {
+ switch messageFetcher.state {
+ case .loading:
+ return AnyView(
+ ProgressView()
+ )
+ case .fetched(let result):
+ switch result {
+ case .failure(let error):
+ return AnyView(
+ Text(error.localizedDescription)
+ )
+ case .success(let root):
+ return AnyView(
+ List(root) { (message: Message) in
+ MessageView(message: message)
+ }
+ )
+ }
+ }
+ }
+
+ var body: some View {
+ NavigationView {
+ stateContent
+ .navigationBarTitle(Text(title), displayMode: .inline)
+ }
+ }
+}
+
+struct FeedView_Previews: PreviewProvider {
+ static var previews: some View {
+ FeedView("Discover", url: "https://api.juick.com/messages")
+ }
+}
diff --git a/JuickNext/Helpers/Image+Data.swift b/JuickNext/Helpers/Image+Data.swift
new file mode 100644
index 0000000..dee2693
--- /dev/null
+++ b/JuickNext/Helpers/Image+Data.swift
@@ -0,0 +1,35 @@
+//
+// Image+Data.swift
+// JuickNext
+//
+// Created by Vitaly Takmazov on 15.05.2024.
+// Copyright © 2024 com.juick. All rights reserved.
+//
+
+import Foundation
+import SwiftUI
+#if canImport(UIKit)
+import UIKit
+#elseif canImport(AppKit)
+import AppKit
+#endif
+extension Image {
+ /// Initializes a SwiftUI `Image` from data.
+ init?(data: Data) {
+#if canImport(UIKit)
+ if let uiImage = UIImage(data: data) {
+ self.init(uiImage: uiImage)
+ } else {
+ return nil
+ }
+#elseif canImport(AppKit)
+ if let nsImage = NSImage(data: data) {
+ self.init(nsImage: nsImage)
+ } else {
+ return nil
+ }
+#else
+ return nil
+#endif
+ }
+}
diff --git a/JuickNext/ImageFetcher.swift b/JuickNext/ImageFetcher.swift
new file mode 100644
index 0000000..f76c0ba
--- /dev/null
+++ b/JuickNext/ImageFetcher.swift
@@ -0,0 +1,28 @@
+//
+// ImageFetcher.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import Foundation
+import Combine
+
+class ImageFetcher: ObservableObject {
+
+ @Published var data: Data = Data()
+
+ init(url: String) {
+ guard let imageUrl = URL(string: url) else {
+ return
+ }
+
+ URLSession.shared.dataTask(with: imageUrl) { (data, _, _) in
+ guard let data = data else { return }
+ DispatchQueue.main.async { [weak self] in
+ self?.data = data
+ }
+ }.resume()
+ }
+}
diff --git a/JuickNext/LoadableImageView.swift b/JuickNext/LoadableImageView.swift
new file mode 100644
index 0000000..6a41dc2
--- /dev/null
+++ b/JuickNext/LoadableImageView.swift
@@ -0,0 +1,29 @@
+//
+// LoadableImageView.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import SwiftUI
+
+struct LoadableImageView: View {
+ @ObservedObject var imageFetcher: ImageFetcher
+
+ init(with urlString: String) {
+ imageFetcher = ImageFetcher(url: urlString)
+ }
+
+ var body: some View {
+ if let image = Image(data: imageFetcher.data) {
+ return AnyView(
+ image.resizable()
+ )
+ } else {
+ return AnyView(
+ ProgressView()
+ )
+ }
+ }
+}
diff --git a/JuickNext/LoadableState.swift b/JuickNext/LoadableState.swift
new file mode 100644
index 0000000..a45edb2
--- /dev/null
+++ b/JuickNext/LoadableState.swift
@@ -0,0 +1,25 @@
+//
+// LoadableState.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import Foundation
+
+enum LoadableState<T> {
+ case loading
+ case fetched(Result<T, FetchError>)
+}
+
+enum FetchError: Error {
+ case error(String)
+
+ var localizedDescription: String {
+ switch self {
+ case .error(let message):
+ return message
+ }
+ }
+}
diff --git a/JuickNext/MessageFetcher.swift b/JuickNext/MessageFetcher.swift
new file mode 100644
index 0000000..565cca0
--- /dev/null
+++ b/JuickNext/MessageFetcher.swift
@@ -0,0 +1,39 @@
+//
+// MessageFetcher.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import Foundation
+import Combine
+
+class MessageFetcher: ObservableObject {
+
+ @Published var state: LoadableState<Root> = .loading
+
+ init(url: String) {
+ guard let apiUrl = URL(string: url) else {
+ state = .fetched(.failure(.error("Malformed API URL.")))
+ return
+ }
+
+ URLSession.shared.dataTask(with: apiUrl) { [weak self] (data, _, error) in
+ if let error = error {
+ self?.state = .fetched(.failure(.error(error.localizedDescription)))
+ return
+ }
+
+ guard let data = data else {
+ self?.state = .fetched(.failure(.error("Malformed response data")))
+ return
+ }
+ let root = try! JSONDecoder().decode(Root.self, from: data)
+
+ DispatchQueue.main.async { [weak self] in
+ self?.state = .fetched(.success(root))
+ }
+ }.resume()
+ }
+}
diff --git a/JuickNext/MessageView.swift b/JuickNext/MessageView.swift
new file mode 100644
index 0000000..398978b
--- /dev/null
+++ b/JuickNext/MessageView.swift
@@ -0,0 +1,43 @@
+//
+// MessageView.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import SwiftUI
+
+struct MessageView: View {
+ var message: Message
+ var body: some View {
+ VStack(alignment: .leading) {
+ HStack {
+ LoadableImageView(with: message.user.avatar ?? "")
+ .frame(width: 48, height: 48, alignment: .center)
+ Text(message.user.name)
+ .font(.headline)
+ .foregroundColor(.accentColor)
+ }
+ message.tags.map {
+ Text($0.joined(separator: ", "))
+ .font(.subheadline)
+ .italic()
+ .foregroundColor(.secondary)
+ }
+ Text(message.text ?? "")
+ .font(.body)
+ .padding()
+ message.attachment.map {
+ LoadableImageView(with: $0.url).scaledToFit()
+ }
+ }
+ }
+}
+
+struct MessageView_Previews: PreviewProvider {
+ static let msg = Message(id: 0, user: User(id: 0, name: "ugnich"), text: "Lorem ipsum")
+ static var previews: some View {
+ MessageView(message: msg)
+ }
+}
diff --git a/JuickNext/Models.swift b/JuickNext/Models.swift
new file mode 100644
index 0000000..77a12f5
--- /dev/null
+++ b/JuickNext/Models.swift
@@ -0,0 +1,47 @@
+//
+// Models.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import Foundation
+
+
+typealias Model = Decodable & Identifiable
+
+typealias Root = Array<Message>
+
+struct Attachment: Decodable {
+ var url: String
+}
+
+struct Entity: Decodable {
+ var type: String
+ var text: String
+ var link: String?
+ var start: Int?
+ var end: Int?
+}
+
+struct User: Model {
+ var id: Int
+ var name: String
+ var avatar: String?
+ private enum CodingKeys : String, CodingKey {
+ case id = "uid", name = "uname", avatar
+ }
+}
+
+struct Message : Model {
+ var id: Int
+ var user: User
+ var text : String?
+ var attachment: Attachment?
+ var entities: [Entity]?
+ var tags: [String]?
+ private enum CodingKeys : String, CodingKey {
+ case id = "mid", user, text = "body", attachment, entities, tags
+ }
+}
diff --git a/JuickNext/View/ContentView.swift b/JuickNext/View/ContentView.swift
index e77d6e7..22e74e0 100644
--- a/JuickNext/View/ContentView.swift
+++ b/JuickNext/View/ContentView.swift
@@ -10,17 +10,22 @@ import SwiftUI
struct ContentView: View {
@Environment(\.horizontalSizeClass) private var size
+
+ let today = FeedView("Today", url: "https://api.juick.com/messages?popular=1")
+ let discussions = FeedView("Discussions", url: "https://api.juick.com/messages/discussions")
+ let discover = FeedView("Discover", url: "https://api.juick.com/messages")
var body: some View {
let view = (size == .compact) ?
AnyView(TabView {
- Today()
- .tabItem {
+ today.tabItem {
Image("ei-clock")
}
- Discussions()
- .tabItem {
+ discussions.tabItem {
Image("ei-bell")
}
+ discover.tabItem {
+ Image("Discover")
+ }
}): AnyView(NavigationView {
VStack {
Text("Discussions")
diff --git a/JuickNext/View/Discussions.swift b/JuickNext/View/Discussions.swift
deleted file mode 100644
index 20e7fa8..0000000
--- a/JuickNext/View/Discussions.swift
+++ /dev/null
@@ -1,21 +0,0 @@
-//
-// Discussions.swift
-// Juick
-//
-// Created by Vitaly Takmazov on 14.05.2023.
-// Copyright © 2023 com.juick. All rights reserved.
-//
-
-import SwiftUI
-
-struct Discussions: View {
- var body: some View {
- Text("Discussions")
- }
-}
-
-struct Discussions_Previews: PreviewProvider {
- static var previews: some View {
- Discussions()
- }
-}
diff --git a/JuickNext/View/Today.swift b/JuickNext/View/Today.swift
deleted file mode 100644
index 161a353..0000000
--- a/JuickNext/View/Today.swift
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-// TodayView.swift
-// Juick
-//
-// Created by Vitaly Takmazov on 14.05.2023.
-// Copyright © 2023 com.juick. All rights reserved.
-//
-
-import SwiftUI
-
-struct Today: View {
- let title = "Today"
- var body: some View {
- NavigationView {
- Text(title)
- }
- }
-}
-
-struct Today_Previews: PreviewProvider {
- static var previews: some View {
- Today()
- }
-}