Skip to content
This repository has been archived by the owner on Sep 24, 2024. It is now read-only.

feat: 🎸 gif supported by SDWebImage #32

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@
"version": "1.2.0"
}
},
{
"package": "SDWebImage",
"repositoryURL": "https://github.com/SDWebImage/SDWebImage",
"state": {
"branch": null,
"revision": "76dd4b49110b8624317fc128e7fa0d8a252018bc",
"version": "5.11.1"
}
},
{
"package": "SnapshotTesting",
"repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing",
Expand All @@ -64,15 +73,6 @@
"version": "0.4.1"
}
},
{
"package": "URLImage",
"repositoryURL": "https://github.com/dmytro-anokhin/url-image",
"state": {
"branch": null,
"revision": "ccab89ad1cedb04f25dd4df1776dd8c8583b914a",
"version": "2.2.5"
}
},
{
"package": "Zip",
"repositoryURL": "https://github.com/marmelroy/Zip.git",
Expand Down
15 changes: 14 additions & 1 deletion CAITestApp/CAITestApp/MockData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct MockData {

// text message (user)
arr.append(CAIResponseMessageData(text: "Can you show me a really, really, really long text message to verify that text wraps and looks pretty. And then some next with quick reply buttons!", false))

// text message (bot)
arr.append(CAIResponseMessageData(text: "Sure, no problem at all. Here you go! In the next message I will send you a text with quick reply buttons. What would you like to see next?", true))

Expand All @@ -32,6 +32,19 @@ struct MockData {

// image (bot)
arr.append(CAIResponseMessageData(imageName: "https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2019-mustang-shelby-gt350-101-1528733363.jpg?crop=0.817xw:1.00xh;0.149xw,0&resize=640:*"))

// text message (user)
arr.append(CAIResponseMessageData(text: "give me some pics", false))

// image (bot)
arr.append(CAIResponseMessageData(imageName: "https://via.placeholder.com/800x100.jpg"))
arr.append(CAIResponseMessageData(imageName: "https://via.placeholder.com/100x800.jpg"))

// text message (user)
arr.append(CAIResponseMessageData(text: "Please show me a gif", false))

// image (bot)
arr.append(CAIResponseMessageData(imageName: "http://assets.sbnation.com/assets/2512203/dogflops.gif"))

// text message (user)
arr.append(CAIResponseMessageData(text: "I want to watch a video", false))
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
)
],
dependencies: [
.package(name: "URLImage", url: "https://github.com/dmytro-anokhin/url-image", .upToNextMajor(from: "2.0.0")),
.package(name: "SDWebImage", url: "https://github.com/SDWebImage/SDWebImage", .upToNextMinor(from: "5.11.0")),
.package(name: "Down", url: "https://github.com/johnxnguyen/Down", .upToNextMinor(from: "0.11.0")),
.package(name: "cloud-sdk-ios", url: "https://github.com/SAP/cloud-sdk-ios", .exact("5.1.3-xcfrwk"))
],
Expand All @@ -35,7 +35,7 @@ let package = Package(
.target(
name: "SAPCAI",
dependencies: [
"URLImage",
"SDWebImage",
"Down"
],
resources: [
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ curl -X POST "<BaseUrl>/connect/v1/channels" \

- [SAPCommon](https://help.sap.com/doc/978e4f6c968c4cc5a30f9d324aa4b1d7/Latest/en-US/Documents/Frameworks/SAPCommon/index.html) for Logging
- [SAPFoundation](https://help.sap.com/doc/978e4f6c968c4cc5a30f9d324aa4b1d7/Latest/en-US/Documents/Frameworks/SAPFoundation/index.html) for Network Connectivity and Authentication
- [URLImage](https://github.com/dmytro-anokhin/url-image) for asynchronous image loading in SwiftUI
- [SDWebImage](https://github.com/SDWebImage/SDWebImage) for asynchronous image loading and gif animation in SwiftUI
- [Down](https://github.com/johnxnguyen/Down) for Markdown / CommonMark rendering in Swift

## Installation
Expand Down Expand Up @@ -146,6 +146,9 @@ AssistantView()
.onDisappear {
viewModel.cancelSubscriptions()
dataPublisher.resetConversation()
//SAPCAI uses `SDWebImage` and its image caching capabilities but it is the app developers responsibility to clear the cache if that is desired
SDImageCache.shared.clearMemory()
SDImageCache.shared.clearDisk(onCompletion: nil)
})
```

Expand Down Expand Up @@ -196,7 +199,6 @@ You can also provide an alternative color palette or provide a custom theme.
### Image related

- Not being able to save, copy, or share an image as part of a bot response. Currently, there is no gesture handler attached to an image view which could allow opening a contextual menu offering such features (similar to iMessage or WhatsApp).
- Animated images (GIF), as part of a bot response, will be viewed as a static image (dependency to https://github.com/dmytro-anokhin/url-image/issues/43)

## How to obtain support

Expand Down
20 changes: 6 additions & 14 deletions Sources/SAPCAI/UI/Common/SwiftUI/AvatarView.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import SwiftUI
import URLImage

struct AvatarView: View {
var imageUrl: String

var body: some View {
URLImage(url: URL(string: imageUrl)!,
inProgress: { _ -> Image in
Image(systemName: "person.crop.circle")
},
failure: { error, _ in
Text(error.localizedDescription)
},
content: { image in
image
.resizable() // Make image resizable
.aspectRatio(contentMode: .fill) // Fill the frame
.clipped() // Clip overlaping parts
})
ImageViewWrapper(url: URL(string: imageUrl),
placeholder: { Image(systemName: "person.crop.circle") },
failure: { _ in Image(systemName: "person.crop.circle") },
content: { $0 })
.scaledToFill()
.frame(width: 32, height: 32, alignment: .center)
.clipped()
}
}

Expand Down
18 changes: 5 additions & 13 deletions Sources/SAPCAI/UI/Common/SwiftUI/CarouselImageView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import SwiftUI
import URLImage

struct CarouselImageView: View {
var media: MediaItem?
Expand All @@ -13,18 +12,11 @@ struct CarouselImageView: View {
var body: some View {
Group {
if let mediaItem = media, let sourceUrl = mediaItem.sourceUrl {
URLImage(url: sourceUrl,
inProgress: { _ -> Image in
mediaItem.placeholder
},
failure: { error, _ in
Text(error.localizedDescription)
},
content: { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
})
ImageViewWrapper(url: sourceUrl,
placeholder: { mediaItem.placeholder },
failure: { Text($0.localizedDescription) },
content: { $0 })
.scaledToFill()
.frame(width: self.itemWidth, height: self.vSizeClass == .regular ? 180 : 80)
.clipped()
} else {
Expand Down
32 changes: 14 additions & 18 deletions Sources/SAPCAI/UI/Common/SwiftUI/ImageUIView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import SwiftUI
import URLImage

/// Renders an image from a MediaItem data model
///
Expand Down Expand Up @@ -34,28 +33,25 @@ struct ImageUIView: View {
self.fallback = fallback
}

var boundingBox: BoundingBox {
BoundingBox(minWidth: 44,
minHeight: 44,
maxWidth: self.hSizeClass == .regular ? 480 : self.geometry.size.width * 0.75,
maxHeight: self.vSizeClass == .regular ? 400 : 240)
}

// :nodoc:
var body: some View {
Group {
if let sourceUrl = media?.sourceUrl {
URLImage(url: sourceUrl) { image, info in
SizeConverter(
CGSize(width: CGFloat(info.cgImage.width), height: CGFloat(info.cgImage.height)),
BoundingBox(minWidth: 44,
minHeight: 44,
maxWidth: self.hSizeClass == .regular ? 480 : self.geometry.size.width * 0.75,
maxHeight: self.vSizeClass == .regular ? 400 : 240),
content: { targetSize in
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: targetSize.width, height: targetSize.height)
}
)
.preference(key: ImageSizeInfoPrefKey.self,
value: CGSize(width: info.cgImage.width,
height: info.cgImage.height))
ImageViewWrapper(url: sourceUrl) { imgView in
let size = imgView.imageManager.imgSize
SizeConverter(size, boundingBox, content: { targetSize in
imgView.frame(width: targetSize.width, height: targetSize.height)
.preference(key: ImageSizeInfoPrefKey.self, value: targetSize)
})
}
.scaledToFill()
} else {
fallback
}
Expand Down
18 changes: 7 additions & 11 deletions Sources/SAPCAI/UI/Common/SwiftUI/ImageView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import SwiftUI
import URLImage

/// Renders an image from a MediaItem data model
struct ImageView: View {
Expand All @@ -10,16 +9,13 @@ struct ImageView: View {
if imageUrl.absoluteString.range(of: "sap-icon") != nil {
IconImageView(iconUrl: imageUrl.absoluteString, iconSize: CGSize(width: 50, height: 50))
} else {
URLImage(url: imageUrl,
failure: { _, _ in
Image(systemName: "photo")
},
content: { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(8)
})
ImageViewWrapper(url: imageUrl,
failure: { _ in
Image(systemName: "photo")
},
content: { $0 })
.scaledToFit()
.cornerRadius(8)
}
}
}
Expand Down
55 changes: 55 additions & 0 deletions Sources/SAPCAI/UI/Common/UIKit/ImageViewRepresentable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import SDWebImage
import SwiftUI

struct ImageViewRepresentable: UIViewRepresentable {
var url: URL?
var imageManager: ImageManager
let wrapper = UIImageViewWrapper()

func makeUIView(context: Self.Context) -> UIImageViewWrapper {
self.wrapper
}

func updateUIView(_ uiView: UIImageViewWrapper, context: UIViewRepresentableContext<ImageViewRepresentable>) {
uiView.imageView.contentMode = self.imageManager.contentMode
if case ImageManager.ImageState.error = self.imageManager.state, uiView.imageView.image == nil {
self.loadImage()
}
}

func loadImage() {
self.wrapper.imageView.sd_setImage(with: self.url,
placeholderImage: nil) { image, error, _, _ in
if let error = error {
imageManager.state = .error(error)
} else if let image = image {
imageManager.state = .completed(image: image)
} else {
imageManager.state = .error(NSError(domain: "cai.com", code: 1, userInfo: nil))
}
}
}
}

class UIImageViewWrapper: UIView {
lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
imageView.setContentHuggingPriority(.defaultLow, for: .vertical)
imageView.setContentHuggingPriority(.defaultLow, for: .horizontal)
return imageView
}()

override init(frame: CGRect) {
super.init(frame: frame)
addSubview(self.imageView)
self.imageView.bindFrameToSuperviewBounds()
}

required init?(coder: NSCoder) {
super.init(coder: coder)
addSubview(self.imageView)
self.imageView.bindFrameToSuperviewBounds()
}
}
Loading