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

Challenge 2 Submission From Intout (Mert Tecimen) #7

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
23 changes: 23 additions & 0 deletions Airdrop Progress Animation.swiftpm/Color + Extentions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Color + Extentions.swift
// Airdrop Progress Animation
//
// Created by Mert Tecimen on 4.08.2022.
//

import SwiftUI

extension Color{

public static let badgeBackgroundColor: Color = .init(red: 154/255, green: 154/255, blue: 154/255)

public static let progressRingAccentColor: Color = .init(red: 20/255, green: 126/255, blue: 255/255)

public static let progressRingBackgroundColor: Color = .init(red: 104/255, green: 103/255, blue: 108/255)

public static let backgroundColor: Color = .init(red: 32/255, green: 32/255, blue: 32/255)

public static let textColor: Color = .init(red: 220/255, green: 220/255, blue: 220/255)


}
20 changes: 20 additions & 0 deletions Airdrop Progress Animation.swiftpm/Constants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Constants.swift
// Airdrop Progress Animation
//
// Created by Mert Tecimen on 4.08.2022.
//

import Foundation

enum States{
case idle, waiting, sending, sent
}

enum Device: String, CaseIterable{
case iphone = "iPhone"
case macbook = "MacBook"
case iPad = "iPad"
}


17 changes: 17 additions & 0 deletions Airdrop Progress Animation.swiftpm/ContentView + ViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// ContentView + ViewModel.swift
// Airdrop Progress Animation
//
// Created by Mert Tecimen on 4.08.2022.
//

import Foundation


extension ContentView{
class ViewModel: ObservableObject{
@Published var state: States = .idle
//let users: [User] = [.init(id: UUID(), name: "Moe")]
let users: [User] = [.init(id: UUID(), name: "Moe"), .init(id: UUID(), name: "Homer"), .init(id: UUID(), name: "Apu"), .init(id: UUID(), name: "Lisa")]
}
}
39 changes: 39 additions & 0 deletions Airdrop Progress Animation.swiftpm/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import SwiftUI


// On the Content View, view model contains dummy data that represents AirDrop users; PersonBagde (AirDrop) views are displayed on 3 column lazyVGrid.

struct ContentView: View {

@State private var progress: CGFloat = 0
@StateObject private var viewModel = ViewModel()


private var threeColumnGrid = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]

var body: some View {
VStack {
Text("Tap on individual icon to start animation. Longpress to reset animation for individual icon.")
.fontWeight(.light)
.foregroundColor(.textColor)
.padding([.all], 10)
LazyVGrid(columns: threeColumnGrid, spacing: 50) {
ForEach(viewModel.users){ user in
VStack {
PersonBadgeView(user: user)
// Aspect ratio setted to 1 to make circular icon even on height and width.
.aspectRatio(1.0, contentMode: .fit)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
}
}
.padding(.all, 10)
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background{
Color.backgroundColor
.ignoresSafeArea(.container)
}
}
}
10 changes: 10 additions & 0 deletions Airdrop Progress Animation.swiftpm/MyApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SwiftUI

@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
43 changes: 43 additions & 0 deletions Airdrop Progress Animation.swiftpm/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// swift-tools-version: 5.6

// WARNING:
// This file is automatically generated.
// Do not edit it by hand because the contents will be replaced.

import PackageDescription
import AppleProductTypes

let package = Package(
name: "Airdrop Progress Animation",
platforms: [
.iOS("15.2")
],
products: [
.iOSApplication(
name: "Airdrop Progress Animation",
targets: ["AppModule"],
bundleIdentifier: "Mert-Tecimen.Airdrop-Progress-Animation",
teamIdentifier: "R2MF39ASQT",
displayVersion: "1.0",
bundleVersion: "1",
appIcon: .placeholder(icon: .tv),
accentColor: .presetColor(.red),
supportedDeviceFamilies: [
.pad,
.phone
],
supportedInterfaceOrientations: [
.portrait,
.landscapeRight,
.landscapeLeft,
.portraitUpsideDown(.when(deviceFamilies: [.pad]))
]
)
],
targets: [
.executableTarget(
name: "AppModule",
path: "."
)
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// PersonBadgeView + ViewModel.swift
// Airdrop Progress Animation
//
// Created by Mert Tecimen on 5.08.2022.
//

import Foundation


struct User: Identifiable{
let id: UUID
let name: String
let device: String = Device.allCases.randomElement()?.rawValue ?? ""
}

extension PersonBadgeView{
class ViewModel: ObservableObject{
@Published var state: States = .idle
let waitDelay: Double
let sendingDelay: Double
var user: User!


init(waitDelay: Double, sendingDelay: Double) {
self.waitDelay = waitDelay
self.sendingDelay = sendingDelay
}

func requestTransfer(){
self.state = .waiting
print("Waiting")
DispatchQueue.main.asyncAfter(deadline: .now() + waitDelay){ [unowned self] in
transfer()
}
}

func transfer(){
self.state = .sending
print("Sending")

// Waits till the animation is compeleted.
DispatchQueue.main.asyncAfter(deadline: .now() + ((sendingDelay * 2))){ [unowned self] in
self.setSent()
}
}

func setSent(){
self.state = .sent
print("Sent")
}

func resetTransfer(){
self.state = .idle
}



}
}
148 changes: 148 additions & 0 deletions Airdrop Progress Animation.swiftpm/PersonBadgeView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//
// PersonBadgeView.swift
// Airdrop Progress Animation
//
// Created by Mert Tecimen on 4.08.2022.
//

import SwiftUI

// There are no optimal solutions (as far as I know) to stopping "repeatForever" animations, so In couple of WWDC sessions demonstraids that recommended way of doings is switching between animated and non-animated versions of a view. Which makes me a bit uncomfortable, a stop method for animations would be appreciated.
fileprivate struct BlinkingStatusText: View{

@Binding var text: String
@State var textColor: Color

var body: some View{
Text(text)
.foregroundColor(textColor)
.onAppear{
withAnimation(.linear(duration: 0.8).repeatForever(autoreverses: true)){
textColor = .clear
}
}
}
}

struct ProgressRing: View{

@Binding var progressAmount: CGFloat

var body: some View{
GeometryReader{ geometry in
ZStack{
Circle()
.stroke(
Color.progressRingBackgroundColor,
lineWidth: geometry.size.width * 1/20
)
Circle()
.trim(from: 0, to: progressAmount / 100)
.stroke(
Color.progressRingAccentColor,
lineWidth: geometry.size.width * 1/20
)
.rotationEffect(.degrees(-90))
}
}
}
}

// Data transfer on AirDrop contains 4 states: idle, waiting, sending and sent. View Model in PersonBadge view contains a state variable, when airdrop is requested those states are iterated via given delay values.
struct PersonBadgeView: View {

@State var progress: CGFloat = 0
let user: User
// Random sending delay and wait delay for users to add variaty.
@StateObject private var viewModel: ViewModel = .init(waitDelay: Double.random(in: 2...5), sendingDelay: Double.random(in: 1...5))

@State private var deviceTextColor: Color = .textColor
@State private var statusTextColor: Color = .textColor
@State private var statusText: String = ""

init(user: User){
self.user = user
}

var body: some View {
GeometryReader{ geometry in
VStack {
ZStack{
ZStack{
Circle()
.foregroundColor(.badgeBackgroundColor)
.padding([.all], geometry.size.width / 25)
// User Icon and animations are contained in UserIconView.
UserIconView(state: $viewModel.state, size: .init(width: geometry.size.width / 1.5, height: geometry.size.height / 1.5))
.frame(maxWidth: geometry.size.width * 3/4, maxHeight: geometry.size.height * 3/4)
}
// ProgressRing is wraps around UserIconView and displays a progress animation that completes in given sending delay time.
ProgressRing(progressAmount: $progress)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.aspectRatio(1.0, contentMode: .fit)
.onReceive(viewModel.$state){ state in
switch state{
case .idle:
resetAnimation()
statusText = "from \(user.name)"
statusTextColor = .textColor
case .waiting:
statusText = "Waiting..."
statusTextColor = .badgeBackgroundColor
case .sending:
startAnimation()
statusText = "Sending..."
case .sent:
statusText = "Sent"
statusTextColor = .progressRingAccentColor
}
}
.onAppear{
viewModel.user = user
}
.onTapGesture{
if viewModel.state == .idle{
viewModel.requestTransfer()
}
}
.onLongPressGesture{
viewModel.resetTransfer()
}
Text("\(user.device)")
.foregroundColor(deviceTextColor)
// Switching between animated and non-animated versions of text view.
if viewModel.state == .waiting{
BlinkingStatusText(text: $statusText, textColor: statusTextColor)
} else {
Text(statusText)
.foregroundColor(statusTextColor)
}

}
}
}

private func startAnimation(){
// Ease Ins to first half of the progress animation.
withAnimation(.easeIn(duration: viewModel.sendingDelay)){
progress = 50
}
// Ease Outs to second half of the progress animaiton.
withAnimation(.easeOut(duration: viewModel.sendingDelay).delay(viewModel.sendingDelay)){
progress = 100
}
}

private func resetAnimation(){
// Resets progress animation.
progress = 0
}

}

struct PersonBadgeView_Previews: PreviewProvider {
static var previews: some View {
PersonBadgeView(user: .init(id: UUID(), name: "Tony"))
}
}
Loading