Skip to content
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
4 changes: 4 additions & 0 deletions Emotilt/Emotilt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Emotilt/Info.plist;
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Emotilt에서 로컬 네트워크를 이용하고자 합니다.";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "\"Emotilt wants to connect!\"";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
Expand Down Expand Up @@ -408,6 +410,8 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Emotilt/Info.plist;
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Emotilt에서 로컬 네트워크를 이용하고자 합니다.";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "\"Emotilt wants to connect!\"";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
Expand Down
10 changes: 4 additions & 6 deletions Emotilt/Emotilt/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SERVICE_NAME</key>
<string>$(SERVICE_NAME)</string>
<key>SERVICE_IDENTIFIER</key>
<string>$(SERVICE_IDENTIFIER)</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Emotilt에서 로컬 네트워크를 이용하고자 합니다.</string>
<key>NSBonjourServices</key>
<array>
<string>$(BONJOUR_SERVICES)</string>
</array>
<key>SERVICE_IDENTIFIER</key>
<string>$(SERVICE_IDENTIFIER)</string>
<key>SERVICE_NAME</key>
<string>$(SERVICE_NAME)</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
Expand Down
6 changes: 6 additions & 0 deletions Emotilt/Emotilt/Network/MPCSessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class MPCSessionManager: NSObject {

@Published var received: (MCPeerID, Data)?
@Published var connectionState: (MCPeerID, MCSessionState)?
@Published var didSendMessage: Bool?


override init() {
self.session = .init(peer: localPeerID,
securityIdentity: nil,
Expand Down Expand Up @@ -74,18 +76,22 @@ class MPCSessionManager: NSObject {
func sendMessage(_ message: Message, to peerID: MCPeerID) {
if !session.connectedPeers.contains(peerID) {
print("unconnected peer")
didSendMessage = false
return
}

guard let data = try? JSONEncoder().encode(message) else {
// fail to encode Message into data
didSendMessage = false
return
}

do {
try session.send(data, toPeers: [peerID], with: .reliable)
didSendMessage = true
} catch let error {
print(error)
didSendMessage = false
}
}
}
Expand Down
28 changes: 22 additions & 6 deletions Emotilt/Emotilt/Network/PeerSessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class PeerSessionManager: NSObject {

/// 현재 내 로컬 디바이스와 가장 가까이 있는 기기의 discoveryToken
@Published var nearestPeerToken: NIDiscoveryToken?

/// 메세지 발신 성공 여부
@Published var didSendMessage: Bool?

private var bag = Set<AnyCancellable>()

Expand Down Expand Up @@ -51,21 +54,27 @@ class PeerSessionManager: NSObject {
self?.receivedMessage = message
}
}.store(in: &bag)

mpcSessionManager.$didSendMessage.receive(on: RunLoop.main).sink { [weak self] success in
self?.didSendMessage = success
}.store(in: &bag)
}

func sendMessageToNearestPeer(_ message: Message) {
guard let token = nearestPeerToken else {
didSendMessage = false
print("no nearest peer's token")
return
}
print("trying to send to \(token)")

guard let peerID = peerList.first(where: { $0.token == token })?.id else {
didSendMessage = false
print("no matching peer in peerList")
return
}

mpcSessionManager.sendMessage(message, to: peerID)
print("Message success!")
}

/// 새로운 Peer를 추가하고 연결받을 준비를 합니다.
Expand Down Expand Up @@ -111,7 +120,6 @@ class PeerSessionManager: NSObject {
print("no matching peer")
return
}

peer.token = token
let config = NINearbyPeerConfiguration(peerToken: token)
peer.session.run(config)
Expand All @@ -125,12 +133,20 @@ extension PeerSessionManager: NISessionDelegate {
nearestPeerToken = getNearestPeer(from: nearbyObjects)
}

// MARK: Get nearest device

/// Sort nearbyObjects by direction and return the nearest object's discoveryToken
private func getNearestPeer(from nearbyObjects: [NINearbyObject]) -> NIDiscoveryToken {
let directions = nearbyObjects.sorted { $0.distance ?? .zero < $1.distance ?? .zero }
return directions[0].discoveryToken
let directionFiltered = nearbyObjects.filter { $0.direction != nil }
if !directionFiltered.isEmpty {
let directionSorted = directionFiltered.sorted {
norm_one($0.direction!) < norm_one($1.direction!)
}
return directionSorted.first!.discoveryToken
} else {
let distanceSorted = nearbyObjects.sorted {
$0.distance ?? .zero < $1.distance ?? .zero
}
return distanceSorted.first!.discoveryToken
}
}
}

Expand Down
26 changes: 23 additions & 3 deletions Emotilt/Emotilt/ViewModels/HomeViewModel.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
//
///Users/grace/Library/Mobile Documents/com~apple~CloudDocs/Desktop/EmoTilt_Xcode13.4/EmoTilt_Xcode13.4/Config.xcconfig
// HomeViewModel.swift
// Emotilt
//
// Created by 최유림 on 2023/03/01.
//

import Foundation
import CoreMotion
import UIKit

enum viewState {
case sendingSuccess //메세지 보내기 성공 직후 + 5초
case sendingFailure //메세지 보내기 실패 직후 + 5초
case motionDetectFailure //장전 이후 모션 감지 실패
case sendingTimer //장전 단계
case none //디폴트 화면
}

class HomeViewModel: BaseViewModel, ObservableObject {
/// 연결된 peer 목록
Expand All @@ -14,18 +24,28 @@ class HomeViewModel: BaseViewModel, ObservableObject {
/// 수신한 메시지
@Published var receivedMessage: Message?

/// 메세지 발신 성공 여부
@Published var didSendMessage: Bool?

var didReceiveMessage: Bool {
receivedMessage != nil
}

@Published var currentState: viewState = .none

override init(peerSessionManager: PeerSessionManager) {
super.init(peerSessionManager: peerSessionManager)

peerSessionManager.$peerList.assign(to: &$peerList)
peerSessionManager.$receivedMessage.assign(to: &$receivedMessage)
peerSessionManager.$didSendMessage.assign(to: &$didSendMessage)
}

func sendMessage(_ message: Message) {
func sendMessage(emoji: String, content: String){
let message = Message(emoji: emoji, content: content)
peerSessionManager.sendMessageToNearestPeer(message)
}
}




149 changes: 124 additions & 25 deletions Emotilt/Emotilt/Views/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,50 @@

import SwiftUI
import Combine
import CoreMotion

struct HomeView: View {

@ObservedObject var viewModel: HomeViewModel

@State private var isEmojiSheetOpen: Bool = false
@State private var emoji: String = ""
@State private var content: String = ""
@FocusState private var isTextFieldFocused: Bool

//MARK: - 메세지 전송 관련 값
let motionManager = CMMotionManager()
@State private var sendTimer: Timer?
@State private var currentState: viewState = .none
@State private var isSending = false
@State private var isAccelerating = false
@State private var accelerationRate = 0.0
@State private var counter = 0
@State private var isReadyForSending = false
@State var isDetected = false

@State var emoji : String = ""
@State var content : String = ""

var body: some View {
VStack(alignment: .center) {
Spacer()

//MARK: - viewState label
switch currentState {
case .sendingTimer:
Text("\(counter)")
.font(.system(size: 40, weight: .bold))
case .sendingSuccess:
Text("메세지 전송 성공!")
.font(.system(size: 20, weight: .bold))
case .sendingFailure:
Text("메세지 전송 실패 ㅠ")
.font(.system(size: 20, weight: .bold))
case .motionDetectFailure:
Text("메시지를 보내려면 흔들어주세요!")
.font(.system(size: 20, weight: .bold))
case .none:
EmptyView()
}

ZStack {
Button {
isEmojiSheetOpen = true
Expand All @@ -37,45 +67,49 @@ struct HomeView: View {
}
}


ZStack {
// DirectionIndicatorView()
//DirectionIndicatorView()
}
}.frame(width: 280, height: 280)

Group {
TextField("", text: $content, axis: .vertical)
.placeholder(when: content.isEmpty && !isTextFieldFocused) {

if #available(iOS 16.0, *) {
TextField("", text: $content, axis: .vertical)
.placeholder(when: content.isEmpty && !isTextFieldFocused){
Text("20자 이내")
.foregroundColor(.gray)
.opacity(0.8)
}
.focused($isTextFieldFocused)
.font(.system(size: 24, weight: .bold))
.lineLimit(2)
.frame(height: 64)
.multilineTextAlignment(.center)
.onReceive(Just(content)) { _ in
if content.count > 20 {
content = String(content.prefix(20))
}
}
.submitLabel(.done)
.onChange(of: content) { text in
if text.last == "\n" {
content = String(text.dropLast())
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
.focused($isTextFieldFocused)
.font(.system(size: 24, weight: .bold))
.lineLimit(2)
.frame(height: 64)
.multilineTextAlignment(.center)
.onReceive(Just($content)) { _ in
if content.count > 20 {
content = String(content.prefix(20))
}
}
}

.submitLabel(.done)
.onChange(of: content) { text in
if text.last == "\n" {
content = String(text.dropLast())
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
}
}
} else {
// Fallback on earlier versions
}
}

Spacer()
Spacer()

RoundedButton(label: "Send") {
viewModel.sendMessage(.init(emoji: "🤔", content: "Nyam"))
detectAcceleration()
}

}
.padding(.horizontal, 36)
.padding(.vertical, 24)
Expand All @@ -88,8 +122,73 @@ struct HomeView: View {
}
}

//MARK: - message sending
extension HomeView {
func detectAcceleration() {
isDetected = false
isSending = true
currentState = .sendingTimer

motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { [self] motion, error in
withAnimation {
isAccelerating = true
accelerationRate = (motion?.acceleration.x)! + 1
if (motion?.acceleration.x)! > 0.35 {
isDetected = true
motionManager.stopAccelerometerUpdates()
UINotificationFeedbackGenerator().notificationOccurred(.success)
sendMessage()
}
}
}

startTimer()
}

///5초 카운트다운을 시작함
func startTimer() {
if counter == 0 { counter = 5 }
sendTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { tempTimer in
withAnimation {
if self.counter > 0 { self.counter -= 1 }
if self.counter == 0 && !self.isDetected {
self.currentState = .motionDetectFailure
self.stopTimer()
}
}
}
}

func stopTimer() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { // 3초 딜레이
withAnimation {
self.currentState = .none
self.isSending = false
self.sendTimer?.invalidate()
self.sendTimer = nil
self.emoji = ""
self.content = ""
self.currentState = .none
self.motionManager.stopAccelerometerUpdates()
self.isAccelerating = false
}
}
}

func sendMessage() {
viewModel.sendMessage(emoji: emoji, content: content)
if let didSendMessage = viewModel.didSendMessage {
withAnimation {
currentState = didSendMessage ? .sendingSuccess : .sendingFailure
}
stopTimer()
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
HomeView(viewModel: .init(peerSessionManager: .debug))
}
}