Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Leif/p 12 allow customized content #18

Merged
merged 4 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions Sources/Picture/Internal/MultipleImageView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import SwiftUI

// TODO: [P-9](https://github.com/0xOpenBytes/Picture/issues/9)
public struct MultipleImageView: View {
let images: [Image]

public var body: some View {
switch images.count {
case 0: placeholderView
case 1: SingleImageView(image: images[0])
case 2: doubleImageView
case 3: tripleImageView
default: fallbackImageView

}
}

private var placeholderView: some View {
Image(systemName: "photo.artframe")
.resizable()
.aspectRatio(contentMode: .fit)
}

private var doubleImageView: some View {
VStack {
images[0]
.resizable()
.aspectRatio(contentMode: .fit)
images[1]
.resizable()
.aspectRatio(contentMode: .fit)
}
}

private var tripleImageView: some View {
HStack {
images[0]
.resizable()
.aspectRatio(contentMode: .fit)
VStack {
images[1]
.resizable()
.aspectRatio(contentMode: .fit)

images[2]
.resizable()
.aspectRatio(contentMode: .fit)
}
}
}

private var fallbackImageView: some View {
Grid {
GridRow {
images[0]
.resizable()
.aspectRatio(contentMode: .fit)
images[1]
.resizable()
.aspectRatio(contentMode: .fit)
}
GridRow {
images[2]
.resizable()
.aspectRatio(contentMode: .fit)
Image(systemName: "ellipsis")
.resizable()
.aspectRatio(contentMode: .fit)
}
}
}
}

struct MultipleImageView_Preview: PreviewProvider {
static var previews: some View {
MultipleImageView(
images: [
Image(systemName: "photo.artframe")
]
)

MultipleImageView(
images: [
Image(systemName: "photo.artframe"),
Image(systemName: "photo.artframe")
]
)

MultipleImageView(
images: [
Image(systemName: "photo.artframe"),
Image(systemName: "photo.artframe"),
Image(systemName: "photo.artframe")
]
)

MultipleImageView(
images: [
Image(systemName: "photo.artframe"),
Image(systemName: "photo.artframe"),
Image(systemName: "photo.artframe"),
Image(systemName: "photo.artframe")
]
)
}
}
40 changes: 40 additions & 0 deletions Sources/Picture/Internal/PictureViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import OSLog
import SwiftUI

class PictureViewModel: ObservableObject {
private let logger = Logger(subsystem: "PictureViewModel", category: "VM")
private var task: Task<Void, Never>?

let sources: [PictureSource]

@Published var images: [Image]

init(sources: [PictureSource]) {
self.sources = sources
self.task = nil
self.images = []
}

deinit {
task?.cancel()
}

@MainActor
func load() {
task?.cancel()

task = Task {
for source in sources.prefix(5) {
do {
if let loadedImage = try await source.load() {
DispatchQueue.main.async { [weak self] in
self?.images.append(loadedImage)
}
}
} catch {
logger.error("\(error.localizedDescription)")
}
}
}
}
}
16 changes: 16 additions & 0 deletions Sources/Picture/Internal/SingleImageView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SwiftUI

// TODO: [P-8](https://github.com/0xOpenBytes/Picture/issues/8)
public struct SingleImageView: View {
let image: Image

public var body: some View {
image
DanielSincere marked this conversation as resolved.
Show resolved Hide resolved
}
}

struct SingleImageView_Preview: PreviewProvider {
static var previews: some View {
SingleImageView(image: Image(systemName: "photo.artframe"))
}
}
96 changes: 90 additions & 6 deletions Sources/Picture/Public/Picture+init.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,103 @@
import SwiftUI

extension Picture {
// MARK: - Default Implementation

extension Picture
where SingleImageContent == SingleImageView,
MultipleImageContent == MultipleImageView
{

public init(
sources: [PictureSource]
) {
self.init(
sources: sources,
singleImageContent: SingleImageView.init,
multipleImageContent: MultipleImageContent.init
)
}

public init(image: Image) {
self.init(sources: [.local(image)])
self.init(
sources: [.local(image)],
singleImageContent: SingleImageView.init,
multipleImageContent: MultipleImageContent.init
)
}

public init(url: URL) {
self.init(sources: [.remote(url)])
self.init(
sources: [.remote(url)],
singleImageContent: SingleImageView.init,
multipleImageContent: MultipleImageContent.init
)
}

public init(images: [Image]) {
self.init(sources: images.map(PictureSource.local))
self.init(
sources: images.map(PictureSource.local),
singleImageContent: SingleImageView.init,
multipleImageContent: MultipleImageContent.init
)
}

public init(urls: [URL]) {
self.init(sources: urls.map(PictureSource.remote))
self.init(
sources: urls.map(PictureSource.remote),
singleImageContent: SingleImageView.init,
multipleImageContent: MultipleImageContent.init
)
}
}

// MARK: - Single Image Initialization

extension Picture where MultipleImageContent == MultipleImageView {
public init(
image: Image,
@ViewBuilder singleImageContent: @escaping (Image) -> SingleImageContent
) {
self.init(
sources: [.local(image)],
singleImageContent: singleImageContent,
multipleImageContent: MultipleImageContent.init
)
}

public init(
url: URL,
@ViewBuilder singleImageContent: @escaping (Image) -> SingleImageContent
) {
self.init(
sources: [.remote(url)],
singleImageContent: singleImageContent,
multipleImageContent: MultipleImageContent.init
)
}
}

// MARK: - Multiple Image Initialization

extension Picture where SingleImageContent == SingleImageView {
public init(
images: [Image],
@ViewBuilder multipleImageContent: @escaping ([Image]) -> MultipleImageContent
) {
self.init(
sources: images.map(PictureSource.local),
singleImageContent: SingleImageView.init,
multipleImageContent: multipleImageContent
)
}

public init(
urls: [URL],
@ViewBuilder multipleImageContent: @escaping ([Image]) -> MultipleImageContent
) {
self.init(
sources: urls.map(PictureSource.remote),
singleImageContent: SingleImageView.init,
multipleImageContent: multipleImageContent
)
}
}
42 changes: 36 additions & 6 deletions Sources/Picture/Public/Picture.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import SwiftUI

public struct Picture: View {
public struct Picture<
SingleImageContent: View,
MultipleImageContent: View
>: View {
private let sources: [PictureSource]
private let singleImageContent: (Image) -> SingleImageContent
private let multipleImageContent: ([Image]) -> MultipleImageContent


@ObservedObject private var viewModel: PictureViewModel
@State private var isFullscreen: Bool = false
@State private var isViewingImage: Bool = false
@State private var currentSourceIndex: Int = 0

public init(sources: [PictureSource]) {
public init(
sources: [PictureSource],
@ViewBuilder singleImageContent: @escaping (Image) -> SingleImageContent,
@ViewBuilder multipleImageContent: @escaping ([Image]) -> MultipleImageContent
) {
self._viewModel = ObservedObject(initialValue: PictureViewModel(sources: sources))
self.sources = sources
self.singleImageContent = singleImageContent
self.multipleImageContent = multipleImageContent
}

@ViewBuilder
Expand Down Expand Up @@ -70,10 +84,14 @@ public struct Picture: View {

public var body: some View {
Group {
if sources.count == 1 {
Text("Image")
} else {
Text("Images: \(sources.count)")
switch viewModel.images.count {
case 0:
ProgressView()
.onAppear {
viewModel.load()
}
case 1: singleImageBody
default: multipleImageBody
}
}
.sheet(isPresented: $isFullscreen) {
Expand All @@ -83,6 +101,18 @@ public struct Picture: View {
isFullscreen = true
}
}

@ViewBuilder
private var singleImageBody: some View {
if let image = viewModel.images.first {
singleImageContent(image)
}
}

@ViewBuilder
private var multipleImageBody: some View {
multipleImageContent(viewModel.images)
}
}

struct Picture_Preview: PreviewProvider {
Expand Down
32 changes: 32 additions & 0 deletions Sources/Picture/Public/PictureSourceImage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import SwiftUI

public struct PictureSourceImage: View {
@ObservedObject private var viewModel: InteractableImageViewModel

public init(
source: PictureSource
) {
self._viewModel = ObservedObject(
initialValue: InteractableImageViewModel(source: source)
)
}

public var body: some View {
if let image = viewModel.image {
image
.resizable()
.aspectRatio(contentMode: .fit)
} else {
ProgressView()
.onAppear {
viewModel.load()
}
}
}
}

struct PictureSourceImage_Preview: PreviewProvider {
static var previews: some View {
PictureSourceImage(source: .local(Image(systemName: "photo.artframe")))
}
}