Skip to content

Commit

Permalink
Sticker Packs (#150)
Browse files Browse the repository at this point in the history
* Sticker Packs

* Update Swiftcord.xcodeproj/project.pbxproj

Co-authored-by: CryptoAlgo <[email protected]>

* Update Swiftcord.xcodeproj/project.pbxproj

Co-authored-by: CryptoAlgo <[email protected]>

* Update Swiftcord.xcodeproj/project.pbxproj

Co-authored-by: CryptoAlgo <[email protected]>

* Update Swiftcord.xcodeproj/project.pbxproj

Co-authored-by: CryptoAlgo <[email protected]>

* Update Swiftcord/Views/Message/MessageStickerView.swift

Co-authored-by: CryptoAlgo <[email protected]>

* Linting

* Remove DiscordKit Reference

* Update Package.resolved

* Update Package.resolved

* Update project.pbxproj

* DiscordKit main branch

* Update project.pbxproj

---------

Co-authored-by: CryptoAlgo <[email protected]>
Sjmarf and cryptoAlgorithm authored May 16, 2023

Partially verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
1 parent 3a6f06a commit 7651c43
Showing 5 changed files with 310 additions and 186 deletions.
18 changes: 10 additions & 8 deletions Swiftcord.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
03A188BE2A0FDB7500D5F4BC /* BasicStickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A188BD2A0FDB7500D5F4BC /* BasicStickerView.swift */; };
03A188C02A0FDB8A00D5F4BC /* MessageStickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A188BF2A0FDB8A00D5F4BC /* MessageStickerView.swift */; };
13A79FC0298C41C800D19AAB /* View+KeyDown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A79FBF298C41C800D19AAB /* View+KeyDown.swift */; };
13A79FC1298C41C800D19AAB /* View+KeyDown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A79FBF298C41C800D19AAB /* View+KeyDown.swift */; };
36367146283C1B6500A5CBE6 /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36367144283C19E500A5CBE6 /* AVKit.framework */; };
@@ -78,7 +80,6 @@
36429981286801C900483D0A /* AppSettingsAccessibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAAA22AE284DC0D700C1975E /* AppSettingsAccessibilityView.swift */; };
36429982286801C900483D0A /* LottieLoopMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA32EF3127C676FE00A9ED72 /* LottieLoopMode.swift */; };
36429983286801C900483D0A /* Logger+.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA28027E28095E3000B14E5C /* Logger+.swift */; };
36429984286801C900483D0A /* StickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA32EF3327C6861800A9ED72 /* StickerView.swift */; };
36429985286801C900483D0A /* ProfileAccentMask.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA54D573284497E400B11857 /* ProfileAccentMask.swift */; };
36429986286801C900483D0A /* AudioCenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAAFB5C6282AB56B00807B54 /* AudioCenterManager.swift */; };
36429987286801C900483D0A /* LottieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA32EF2927C65D2C00A9ED72 /* LottieView.swift */; };
@@ -135,7 +136,6 @@
DA32EF2A27C65D2C00A9ED72 /* LottieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA32EF2927C65D2C00A9ED72 /* LottieView.swift */; };
DA32EF3027C676B300A9ED72 /* WrapperLottieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA32EF2F27C676B300A9ED72 /* WrapperLottieView.swift */; };
DA32EF3227C676FE00A9ED72 /* LottieLoopMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA32EF3127C676FE00A9ED72 /* LottieLoopMode.swift */; };
DA32EF3427C6861800A9ED72 /* StickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA32EF3327C6861800A9ED72 /* StickerView.swift */; };
DA32EF3927C77E3300A9ED72 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA32EF3827C77E3300A9ED72 /* AttachmentView.swift */; };
DA32EF3F27C7C1D000A9ED72 /* MessageInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA32EF3E27C7C1D000A9ED72 /* MessageInputView.swift */; };
DA32EF4827C8ABFF00A9ED72 /* Date+.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA32EF4727C8ABFF00A9ED72 /* Date+.swift */; };
@@ -263,6 +263,8 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
03A188BD2A0FDB7500D5F4BC /* BasicStickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicStickerView.swift; sourceTree = "<group>"; usesTabs = 0; };
03A188BF2A0FDB8A00D5F4BC /* MessageStickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStickerView.swift; sourceTree = "<group>"; };
13A79FBF298C41C800D19AAB /* View+KeyDown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+KeyDown.swift"; sourceTree = "<group>"; };
36004E1C283D63E500F0BA73 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = "<group>"; };
36367144283C19E500A5CBE6 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; };
@@ -302,7 +304,6 @@
DA32EF2927C65D2C00A9ED72 /* LottieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieView.swift; sourceTree = "<group>"; };
DA32EF2F27C676B300A9ED72 /* WrapperLottieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrapperLottieView.swift; sourceTree = "<group>"; };
DA32EF3127C676FE00A9ED72 /* LottieLoopMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieLoopMode.swift; sourceTree = "<group>"; };
DA32EF3327C6861800A9ED72 /* StickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerView.swift; sourceTree = "<group>"; };
DA32EF3827C77E3300A9ED72 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = "<group>"; usesTabs = 0; };
DA32EF3E27C7C1D000A9ED72 /* MessageInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageInputView.swift; sourceTree = "<group>"; };
DA32EF4727C8ABFF00A9ED72 /* Date+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+.swift"; sourceTree = "<group>"; };
@@ -475,8 +476,9 @@
DAB1E47B28A4C1D300645FCD /* MessageRenderViews */,
DA32EF2327C6249000A9ED72 /* MessagesView.swift */,
DA32EF3E27C7C1D000A9ED72 /* MessageInputView.swift */,
03A188BD2A0FDB7500D5F4BC /* BasicStickerView.swift */,
03A188BF2A0FDB8A00D5F4BC /* MessageStickerView.swift */,
DAB1E48B28A510DC00645FCD /* MessageInputReplyView.swift */,
DA32EF3327C6861800A9ED72 /* StickerView.swift */,
DA2384BE27CCBB26009E15E0 /* EmbedView.swift */,
DAAFB5C2282AA5C700807B54 /* MessageInfoBarView.swift */,
);
@@ -1175,7 +1177,6 @@
36429982286801C900483D0A /* LottieLoopMode.swift in Sources */,
36429983286801C900483D0A /* Logger+.swift in Sources */,
DAFD4714289A202F0075D71B /* AttachmentProgress.swift in Sources */,
36429984286801C900483D0A /* StickerView.swift in Sources */,
36429985286801C900483D0A /* ProfileAccentMask.swift in Sources */,
36429986286801C900483D0A /* AudioCenterManager.swift in Sources */,
36429987286801C900483D0A /* LottieView.swift in Sources */,
@@ -1237,8 +1238,10 @@
DA4A888A27C0AF3000720909 /* ContentView.swift in Sources */,
DA57F44628065209001DC46E /* ChannelButton.swift in Sources */,
DA32EF6627CB772300A9ED72 /* CacheModel.xcdatamodeld in Sources */,
03A188C02A0FDB8A00D5F4BC /* MessageStickerView.swift in Sources */,
E7AF1C33282FB02A001F78DF /* UserSettingsAccount.swift in Sources */,
DA520AE227D76BEB009FD740 /* Bool+.swift in Sources */,
03A188BE2A0FDB7500D5F4BC /* BasicStickerView.swift in Sources */,
DA2802792808337B00B14E5C /* AppDelegate.swift in Sources */,
DA4A891427C49B1100720909 /* ServerButton.swift in Sources */,
DAC437792900F3FD00D3A894 /* Snowflake+.swift in Sources */,
@@ -1296,7 +1299,6 @@
DA32EF3227C676FE00A9ED72 /* LottieLoopMode.swift in Sources */,
DAFD4710289A1FEB0075D71B /* AttachmentAudio.swift in Sources */,
DA28027F28095E3100B14E5C /* Logger+.swift in Sources */,
DA32EF3427C6861800A9ED72 /* StickerView.swift in Sources */,
DA54D574284497E400B11857 /* ProfileAccentMask.swift in Sources */,
DAAFB5C7282AB56B00807B54 /* AudioCenterManager.swift in Sources */,
DA32EF2A27C65D2C00A9ED72 /* LottieView.swift in Sources */,
@@ -1800,8 +1802,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SwiftcordApp/DiscordKit";
requirement = {
kind = revision;
revision = c2f7e6a48611fac03456f725f6e4f5d3609d297b;
branch = main;
kind = branch;
};
};
DA8AEA6629029747007BAAEA /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
113 changes: 113 additions & 0 deletions Swiftcord/Views/Message/BasicStickerView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// BasicStickerView.swift
// Swiftcord
//
// Created by Vincent Kwok on 23/2/22.
//
import SwiftUI
import Lottie
import CachedAsyncImage
import DiscordKitCore
import DiscordKit

struct StickerLoadingView: View {
let size: Double
var body: some View {
RoundedRectangle(cornerRadius: 12)
.fill(.gray.opacity(Double.random(in: 0.15...0.3)))
.frame(width: size, height: size)
}
}

struct StickerErrorView: View {
let size: Double
var body: some View {
Image(systemName: "square.slash")
.font(.system(size: size - 10))
.opacity(0.5)
.frame(width: size, height: size)
}
}

enum StickerPlayCondition {
case always
case onHover
case useDefault
}

// Most basic sticker player
struct StickerItemView: View {
let sticker: StickerItem
let size: Double // Width and height of sticker
let play: StickerPlayCondition
@State private var error = false
@State private var animation: Lottie.LottieAnimation?
@State private var hovered = false
@AppStorage("stickerAlwaysAnim") private var alwaysAnimStickers = true
private func playAnimation(value: Bool) {
// Without this check, the sticker animation restarts if it's hovered
if (play == .useDefault && !alwaysAnimStickers) || play == .onHover {
hovered = value
}
}

var body: some View {
if error {
StickerErrorView(size: size)
} else {
switch sticker.format_type {
case .png:
// Literally a walk in the park compared to lottie
AsyncImage(url: URL(string: "\(DiscordKitConfig.default.cdnURL)stickers/\(sticker.id).png")!) { phase in
switch phase {
case .empty: StickerLoadingView(size: size)
case .success(let image): image.resizable().scaledToFill()
case .failure: StickerErrorView(size: size)
default: StickerErrorView(size: size)
}
}
.frame(width: size, height: size)
.clipShape(RoundedRectangle(cornerRadius: 7))
case .lottie:
if animation == nil {
StickerLoadingView(size: size).onAppear {
Lottie.LottieAnimation.loadedFrom(
url: URL(string: "\(DiscordKitConfig.default.cdnURL)stickers/\(sticker.id).json")!,
closure: { anim in
guard let anim = anim else {
error = true
return
}
animation = anim
},
animationCache: Lottie.DefaultAnimationCache.sharedCache
)
}.transition(.customOpacity)
} else {
LottieView(
animation: animation!,
play: .constant(play == .always || (play == .useDefault && alwaysAnimStickers) || hovered),
width: size,
height: size
)
.lottieLoopMode(.loop)
.frame(width: size, height: size)
.transition(.customOpacity)
.onHover(perform: playAnimation)
}
default:
// Well it doesn't animate for some reason
CachedAsyncImage(url: URL(string: "\(DiscordKitConfig.default.cdnURL)stickers/\(sticker.id).png?passthrough=true")!) { phase in
switch phase {
case .empty: StickerLoadingView(size: size)
case .success(let image): image.resizable().scaledToFill()
case .failure: StickerErrorView(size: size)
default: StickerErrorView(size: size)
}
}
.frame(width: size, height: size)
.clipShape(RoundedRectangle(cornerRadius: 7))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ struct DefaultMessageView: View {
}
if let stickerItems = message.sticker_items {
ForEach(stickerItems) { sticker in
StickerView(sticker: sticker)
MessageStickerView(sticker: sticker)
}
}
ForEach(message.attachments) { attachment in
186 changes: 186 additions & 0 deletions Swiftcord/Views/Message/MessageStickerView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//
// MessageStickerView.swift
// Swiftcord
//
// Created by Vincent Kwok on 23/2/22.
//

import SwiftUI
import Lottie
import DiscordKitCore
import DiscordKit
import CachedAsyncImage

struct StickerPackView: View {
let pack: StickerPack
@Binding var packPresenting: Bool
@State private var stickerHovered: Int?
@State private var listHovered: Bool = false
var body: some View {
VStack {
if pack.banner_asset_id != nil {
VStack {
CachedAsyncImage(url: pack.banner_asset_id?.stickerPackBannerURL(with: .webp, size: 1024)) { image in
image.resizable().scaledToFill()
} placeholder: { ProgressView().progressViewStyle(.circular)}
}.frame(height: 100)
}
VStack {
HStack(spacing: 15) {
// Back button
Button {
packPresenting = false
} label: {Image(systemName: "arrow.left")}
.controlSize(.large)
Text(pack.name).font(.title).fontWeight(.bold)
Spacer()
Text("􀐅 x\(pack.stickers.count)")
.font(.system(size: 16))
.opacity(0.7)
}
Divider()
Text(pack.description)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
List {
ForEach(0..<Int(ceil(Double(pack.stickers.count)/3.0)), id: \.self) { row in
HStack {
ForEach(0..<min(3, Int(pack.stickers.count - row * 3)), id: \.self) { column in
let index: Int = row*3+column
StickerItemView(sticker: pack.stickers[index], size: 95, play: .onHover)
.onHover {
stickerHovered = $0 ? index : nil
}
.scaleEffect((stickerHovered == index) ? 1.1 : 1.0)
.opacity((stickerHovered == index) ? 1 : 0.5)
}
}.frame(maxWidth: .infinity)
}
}
.frame(height: 320)
.onHover {listHovered = $0}
if listHovered {
Text(stickerHovered == nil ? "" : pack.stickers[stickerHovered!].name)
.frame(height: 30)
.font(.title3)
.transition(.opacity)
} else {
HStack {
Image("NitroSubscriber")
Text("You need a Nitro subscription to send stickers from this pack.")
.fixedSize(horizontal: false, vertical: true)
.frame(height: 30)
}
.transition(.opacity)
}

}.padding(14)
.animation(Animation.easeOut(duration: 0.1), value: stickerHovered)
.animation(Animation.linear(duration: 0.1), value: listHovered)
}
.frame(width: 360)
}
}

struct CustomButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.background(Color.blue)
.cornerRadius(10.0)
.padding()
.contentShape(Rectangle())
}
}

struct MessageStickerView: View {
let sticker: StickerItem
@State private var infoShow = false
@State private var error = false
@State private var fullSticker: Sticker?
@State public var packPresenting = false
@State private var fullStickerPack: StickerPack?

private func openPopoverEvt() {
AnalyticsWrapper.event(type: .openPopout, properties: [
"type": "Sticker Popout",
"sticker_pack_id": fullSticker?.pack_id ?? "",
"sticker_id": fullSticker?.id ?? ""
])
}
private func loadStickerPack() async -> StickerPack? {
guard let stickerPacks: [StickerPack] = try? await restAPI.listNitroStickerPacks() else {return nil}
for pack in stickerPacks where pack.id == fullSticker!.pack_id {
return pack
}
return nil
}

var body: some View {
Button {
if fullSticker == nil {
Task {
fullSticker = try await restAPI.getSticker(sticker.id)
openPopoverEvt()
}
} else {
openPopoverEvt()
}
infoShow.toggle()
packPresenting = false

} label: {
StickerItemView(sticker: sticker, size: 160, play: .useDefault)
.frame(width: 160, height: 160)
}
.buttonStyle(.borderless)
.popover(isPresented: $infoShow, arrowEdge: .trailing) {
if packPresenting {
if let fullStickerPack = fullStickerPack {
StickerPackView(pack: fullStickerPack, packPresenting: $packPresenting)
}
} else {
VStack(alignment: .leading, spacing: 14) {
if let fullSticker = fullSticker {
StickerItemView(sticker: sticker, size: 240, play: .always)
Divider()
Text(fullSticker.name).font(.title2).fontWeight(.bold)
if let description = fullSticker.description {
Text(description).padding(.top, -8)
}
if sticker.format_type == .aPNG {
Text("Sorry, aPNG stickers can't be played (yet)").font(.footnote)
}
if fullSticker.pack_id != nil {
Button {
Task {
fullStickerPack = await loadStickerPack()
packPresenting = true
}
} label: {
Label("View Sticker Pack", systemImage: "square.on.square")
.frame(maxWidth: .infinity)
}
.buttonStyle(FlatButtonStyle())
.controlSize(.small)
}
} else {
Text("Loading sticker...").font(.headline)
ProgressView()
.progressViewStyle(.linear)
.frame(width: 240)
.tint(.blue)
}
}
.padding(14)
.frame(width: 268)
}
}
}
}

struct StickerView_Previews: PreviewProvider {
static var previews: some View {
// MessageStickerView(sticker: StickerItem(id: ))
EmptyView()
}
}
177 changes: 0 additions & 177 deletions Swiftcord/Views/Message/StickerView.swift

This file was deleted.

0 comments on commit 7651c43

Please sign in to comment.