-
Notifications
You must be signed in to change notification settings - Fork 0
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
send message #5
base: main
Are you sure you want to change the base?
send message #5
Changes from 6 commits
6374b46
75cd0f3
5b90f28
3e1a384
9070b42
eec49d8
27c3ce7
55630eb
f78b57a
4c8d7c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,16 @@ | |
// | ||
|
||
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 목록 | ||
|
@@ -18,14 +28,83 @@ class HomeViewModel: BaseViewModel, ObservableObject { | |
receivedMessage != nil | ||
} | ||
|
||
//MARK: - 뷰 관련 값 | ||
@Published var emoji : String = "" | ||
@Published var content : String = "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 메세지를 보내는 로직이 뷰쪽에 있으면 mvvm 구조랑 맞지 앚는 것 같아서 뷰모델으로 빼놓았습니다. 뷰에서는 뷰모델의 viewstate 값에 따라서 중간 레이블 값만 (타이머, 전송 성공, 전송 실패, 모션 감지 실패 등) 바꿀 수 있도록 했고, 뷰에서 모션 감지나 보내는 로직이 들어가 있으면 복잡할 것 같아서요. 어떻게 생각하시나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네 메시지를 보내는 로직이 뷰모델이 있는건 맞는데, |
||
|
||
//MARK: - 메세지 전송 관련 값 | ||
let motionManager = CMMotionManager() | ||
@Published var sendTimer: Timer? | ||
@Published var currentState: viewState = .none | ||
@Published var isSending: Bool = false | ||
@Published var isAccelerating: Bool = false | ||
@Published var accelerationRate: Double = 0.0 | ||
@Published var counter: Int = 0 | ||
@Published var isReadyForSending: Bool = false | ||
var isDetected: Bool = false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 변수들 모두 view 내부에서만 사용되고 뷰모델을 통한 세션과의 연결은 필요하지 않으므로 특별한 이유가 없다면 뷰에 있는 것이 맞아보이는데 혹시 어떻게 생각하시나요 |
||
|
||
override init(peerSessionManager: PeerSessionManager) { | ||
super.init(peerSessionManager: peerSessionManager) | ||
|
||
peerSessionManager.$peerList.assign(to: &$peerList) | ||
peerSessionManager.$receivedMessage.assign(to: &$receivedMessage) | ||
} | ||
} | ||
|
||
//MARK: - message send logic | ||
extension HomeViewModel { | ||
|
||
///장전을 시작하고 모션을 감지함 | ||
func detectAcceleration(){ | ||
isDetected = false | ||
isSending = true | ||
currentState = .sendingTimer | ||
|
||
motionManager | ||
.startAccelerometerUpdates(to: OperationQueue.current!, withHandler: { [self] (motion, error) in | ||
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 | ||
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){ | ||
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(_ message: Message) { | ||
peerSessionManager.sendMessageToNearestPeer(message) | ||
func sendMessage(){ | ||
let message = Message(emoji: emoji, content: content) | ||
peerSessionManager.sendMessageToNearestPeer(message){ success in | ||
self.currentState = (success) ? .sendingSuccess : .sendingFailure | ||
} | ||
stopTimer() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,85 +11,108 @@ import Combine | |
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 | ||
|
||
var body: some View { | ||
VStack(alignment: .center) { | ||
Spacer() | ||
|
||
//MARK: - viewState label | ||
switch (viewModel.currentState){ | ||
case .sendingTimer: | ||
Text("\(viewModel.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: | ||
Text("") | ||
} | ||
|
||
ZStack { | ||
Button { | ||
isEmojiSheetOpen = true | ||
} label: { | ||
ZStack { | ||
if emoji.isEmpty { | ||
RoundedRectangle(cornerRadius: 32) | ||
.fill(.tertiary.opacity(0.4)) | ||
.frame(width: 168, height: 168) | ||
if viewModel.emoji.isEmpty { | ||
RoundedRectangle(cornerRadius: 32) | ||
.fill(.tertiary.opacity(0.4)) | ||
.frame(width: 168, height: 168) | ||
} | ||
|
||
Text(viewModel.emoji) | ||
.font(.system(size: 168)) | ||
} | ||
|
||
Text(emoji) | ||
.font(.system(size: 168)) | ||
} | ||
} | ||
|
||
|
||
ZStack { | ||
//DirectionIndicatorView() | ||
} | ||
}.frame(width: 280, height: 280) | ||
|
||
ZStack { | ||
// DirectionIndicatorView() | ||
} | ||
}.frame(width: 280, height: 280) | ||
|
||
Group { | ||
TextField("", text: $content, axis: .vertical) | ||
.placeholder(when: content.isEmpty && !isTextFieldFocused) { | ||
Group { | ||
if #available(iOS 16.0, *) { | ||
TextField("", text: $viewModel | ||
.content, axis: .vertical) | ||
.placeholder(when: viewModel.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($viewModel.content)) { _ in | ||
if viewModel.content.count > 20 { | ||
viewModel.content = String(viewModel.content.prefix(20)) | ||
} | ||
} | ||
.submitLabel(.done) | ||
.onChange(of: viewModel.content) { text in | ||
if text.last == "\n" { | ||
viewModel.content = String(text.dropLast()) | ||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분은 입력값의 마지막이 개행 문자인지 검사하는 것보다는 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분은 지호님께서 수정하신 부분인것 같은데 깃에서 제가 추가한걸로 인식한 것 같네요! |
||
} else { | ||
// Fallback on earlier versions | ||
} | ||
|
||
} | ||
|
||
Spacer() | ||
Spacer() | ||
|
||
RoundedButton(label: "Send") { | ||
//viewModel.detectAcceleration() | ||
viewModel.sendMessage() | ||
} | ||
} | ||
|
||
Spacer() | ||
Spacer() | ||
|
||
RoundedButton(label: "Send") { | ||
viewModel.sendMessage(.init(emoji: "🤔", content: "Nyam")) | ||
} | ||
|
||
} | ||
.padding(.horizontal, 36) | ||
.padding(.vertical, 24) | ||
.onTapGesture(perform: { | ||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil) | ||
}) | ||
.sheet(isPresented: $isEmojiSheetOpen) { | ||
EmojiSheetView(selected: $emoji) | ||
.padding(.horizontal, 36) | ||
.padding(.vertical, 24) | ||
.onTapGesture(perform: { | ||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil) | ||
}) | ||
.sheet(isPresented: $isEmojiSheetOpen) { | ||
EmojiSheetView(selected: $viewModel.emoji) | ||
} | ||
} | ||
} | ||
} | ||
|
||
struct ContentView_Previews: PreviewProvider { | ||
static var previews: some View { | ||
HomeView(viewModel: .init(peerSessionManager: .debug)) | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사소하지만..Swift 스타일로 수정해주시면 좋을 것 같아요