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

Commit

Permalink
feat: 🎸 gif supported through SDWebImage (#32)
Browse files Browse the repository at this point in the history
* feat: 🎸 gif supported by SDWebImage.

* feat: 🎸 placeholder and failure support alse README updated

* docs: ✏️ update README

Co-authored-by: XiaoYu <33440079+ShadowTourist@users.noreply.github.com>
  • Loading branch information
MarcoEidinger and ShadowTourist committed Nov 10, 2021
1 parent a5bfc6a commit 706e909
Show file tree
Hide file tree
Showing 13 changed files with 258 additions and 82 deletions.
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
8 changes: 7 additions & 1 deletion CAITestApp/CAITestApp/MockData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,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 @@ -40,6 +40,12 @@ 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: "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
16 changes: 8 additions & 8 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,21 @@
}
},
{
"package": "TinyNetworking",
"repositoryURL": "https://github.com/objcio/tiny-networking",
"package": "SDWebImage",
"repositoryURL": "https://github.com/SDWebImage/SDWebImage",
"state": {
"branch": null,
"revision": "a9af2917018e513c77061139fe3ce4e4d67cf259",
"version": "0.4.1"
"revision": "76dd4b49110b8624317fc128e7fa0d8a252018bc",
"version": "5.11.1"
}
},
{
"package": "URLImage",
"repositoryURL": "https://github.com/dmytro-anokhin/url-image",
"package": "TinyNetworking",
"repositoryURL": "https://github.com/objcio/tiny-networking",
"state": {
"branch": null,
"revision": "ccab89ad1cedb04f25dd4df1776dd8c8583b914a",
"version": "2.2.5"
"revision": "a9af2917018e513c77061139fe3ce4e4d67cf259",
"version": "0.4.1"
}
},
{
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
5 changes: 1 addition & 4 deletions Sources/SAPCAI/UI/Common/SwiftUI/CarouselDetailPage.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import SwiftUI
import URLImage

struct CarouselDetailPage: View {
@EnvironmentObject private var themeManager: ThemeManager
Expand Down Expand Up @@ -40,12 +39,10 @@ struct CarouselDetailPage: View {

@ViewBuilder var carouselHeaderImage: some View {
if let imgURL = carouselItem?.itemPicture?.sourceUrl {
URLImage(url: imgURL) { image in
ImageViewWrapper(url: imgURL) { image in
image
.resizable()
.scaledToFill()
.frame(width: screenWidth, height: 180.0 / 375 * screenWidth)
.aspectRatio(contentMode: .fill)
.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 @@ -21,18 +20,11 @@ struct CarouselImageView: View {
}
.frame(width: self.itemWidth, alignment: .leading)
} else {
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()
}
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.isSAPIcon {
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

0 comments on commit 706e909

Please sign in to comment.