Skip to content

Commit

Permalink
Added reactions animation.
Browse files Browse the repository at this point in the history
  • Loading branch information
maratal committed Sep 15, 2024
1 parent 4792272 commit 20e7869
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 62 deletions.
204 changes: 144 additions & 60 deletions Example/AblyChatExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@ import SwiftUI

@MainActor
struct ContentView: View {


#if os(macOS)
let screenWidth = NSScreen.main?.frame.width ?? 500
let screenHeight = NSScreen.main?.frame.height ?? 500
#else
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
#endif

@State private var chatClient = MockChatClient(
realtime: MockRealtime.create(),
clientOptions: ClientOptions()
)

@State private var title = "Room"
@State private var messages = [BasicListItem]()
@State private var reactions = ""
@State private var reactions: [Reaction] = []
@State private var newMessage = ""
@State private var typingInfo = ""
@State private var occupancyInfo = "Connections: 0"
Expand All @@ -26,73 +34,88 @@ struct ContentView: View {
}

var body: some View {
VStack {
Text(title)
.font(.headline)
.padding(5)
HStack {
Text("")
Text(occupancyInfo)
Text(statusInfo)
}
.font(.footnote)
.frame(height: 12)
.padding(.horizontal, 8)
Text(reactions)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(5)
List(messages, id: \.id) { item in
MessageBasicView(item: item)
.flip()
}
.flip()
.listStyle(PlainListStyle())
HStack {
TextField("Type a message...", text: $newMessage)
ZStack {
VStack {
Text(title)
.font(.headline)
.padding(5)
HStack {
Text("")
Text(occupancyInfo)
Text(statusInfo)
}
.font(.footnote)
.frame(height: 12)
.padding(.horizontal, 8)
List(messages, id: \.id) { item in
MessageBasicView(item: item)
.flip()
}
.flip()
.listStyle(PlainListStyle())
HStack {
TextField("Type a message...", text: $newMessage)
#if !os(tvOS)
.textFieldStyle(RoundedBorderTextFieldStyle())
.textFieldStyle(RoundedBorderTextFieldStyle())
#endif

Button(action: {
if newMessage.isEmpty {
Task {
try await sendReaction(type: ReactionType.like.rawValue)
}
} else {
Task {
try await sendMessage()
Button(action: {
if newMessage.isEmpty {
Task {
try await sendReaction(type: ReactionType.like.rawValue)
}
} else {
Task {
try await sendMessage()
}
}
}
}) {
}) {
#if os(iOS)
Text(sendTitle)
.foregroundColor(.white)
.padding(.vertical, 6)
.padding(.horizontal, 12)
.background(Color.blue)
.cornerRadius(15)
Text(sendTitle)
.foregroundColor(.white)
.padding(.vertical, 6)
.padding(.horizontal, 12)
.background(Color.blue)
.cornerRadius(15)
#else
Text(sendTitle)
Text(sendTitle)
#endif
}
}
.padding(.horizontal, 12)
HStack {
Text(typingInfo)
.font(.footnote)
Spacer()
}
.frame(height: 12)
.padding(.horizontal, 14)
.padding(.bottom, 5)
}
.padding(.horizontal, 12)
HStack {
Text(typingInfo)
.font(.footnote)
Spacer()
ForEach(reactions) { reaction in
Text(reaction.emoji)
.font(.largeTitle)
.position(x: reaction.xPosition, y: reaction.yPosition)
.scaleEffect(reaction.scale)
.opacity(reaction.opacity)
.rotationEffect(.degrees(reaction.rotationAngle))
.onAppear {
withAnimation(.easeOut(duration: reaction.duration)) {
moveReactionUp(reaction: reaction)
}
// Start rotation animation
withAnimation(Animation.linear(duration: reaction.duration).repeatForever(autoreverses: false)) {
startRotation(reaction: reaction)
}
}
}
.frame(height: 12)
.padding(.horizontal, 14)
.padding(.bottom, 5)
.task { await showMessages() }
.task { await showReactions() }
.task { await showPresence() }
.task { await showTypings() }
.task { await showOccupancy() }
.task { await showRoomStatus() }
.task { await setDefaultTitle() }
}
.task { await showMessages() }
.task { await showReactions() }
.task { await showPresence() }
.task { await showTypings() }
.task { await showOccupancy() }
.task { await showRoomStatus() }
.task { await setDefaultTitle() }
}

func setDefaultTitle() async {
Expand All @@ -110,7 +133,7 @@ struct ContentView: View {
func showReactions() async {
for await reaction in await room().reactions.subscribe(bufferingPolicy: .unbounded) {
withAnimation {
reactions.append(reaction.displayedText)
showReaction(reaction.displayedText)
}
}
}
Expand Down Expand Up @@ -176,6 +199,67 @@ struct ContentView: View {
}
}

extension ContentView {

struct Reaction: Identifiable {
let id: UUID
let emoji: String
var xPosition: CGFloat
var yPosition: CGFloat
var scale: CGFloat
var opacity: Double
var rotationAngle: Double // New: stores the current rotation angle
var rotationSpeed: Double // New: stores the random rotation speed
var duration: Double
}

func showReaction(_ emoji: String) {
let screenWidth = screenWidth
let centerX = screenWidth / 2

// Reduce the spread to 1/5th of the screen width
let reducedSpreadRange = screenWidth / 5

// Random x position now has a smaller range, centered around the middle of the screen
let startXPosition = CGFloat.random(in: centerX - reducedSpreadRange...centerX + reducedSpreadRange)
let randomRotationSpeed = Double.random(in: 30...360) // Random rotation speed
let duration = Double.random(in: 2...4)

let newReaction = Reaction(
id: UUID(),
emoji: emoji,
xPosition: startXPosition,
yPosition: screenHeight - 100,
scale: 1.0,
opacity: 1.0,
rotationAngle: 0, // Initial angle
rotationSpeed: randomRotationSpeed,
duration: duration
)

reactions.append(newReaction)

// Remove the reaction after the animation completes
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
reactions.removeAll { $0.id == newReaction.id }
}
}

func moveReactionUp(reaction: Reaction) {
if let index = reactions.firstIndex(where: { $0.id == reaction.id }) {
reactions[index].yPosition = 0 // Move it to the top of the screen
reactions[index].scale = 0.5 // Shrink
reactions[index].opacity = 0.5 // Fade out
}
}

func startRotation(reaction: Reaction) {
if let index = reactions.firstIndex(where: { $0.id == reaction.id }) {
reactions[index].rotationAngle += 360 // Continuous rotation over time
}
}
}

struct BasicListItem {
var id: String
var title: String
Expand Down
2 changes: 1 addition & 1 deletion Example/AblyChatExample/Mocks/MockSubscriptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct MockSubscription<T: Sendable>: Sendable, AsyncSequence {
mergedSequence.makeAsyncIterator()
}

init(randomElement: @escaping @Sendable () -> Element, interval: UInt64) {
init(randomElement: @escaping @Sendable () -> Element, interval: Double) {
let (stream, continuation) = AsyncStream.makeStream(of: Element.self, bufferingPolicy: .unbounded)
self.continuation = continuation
let timer: AsyncTimerSequence<ContinuousClock> = .init(interval: .seconds(interval), clock: .init())
Expand Down
2 changes: 1 addition & 1 deletion Example/AblyChatExample/Mocks/Mocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ actor MockRoomReactions: RoomReactions {
createdAt: Date(),
clientID: self.clientID,
isSelf: false)
}, interval: 1)
}, interval: Double.random(in: 0.1...0.5))
}

func send(params: SendReactionParams) async throws {
Expand Down

0 comments on commit 20e7869

Please sign in to comment.