Commit cbac0b93 authored by shigemi miura's avatar shigemi miura

iPad用テキスト入力画面

緯度経度表示
parent f588da6c
......@@ -63,6 +63,8 @@
02CE4DDA2ADFBA72002E79BC /* MapRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CE4DD92ADFBA72002E79BC /* MapRepresentable.swift */; };
02F4DB672B2C173F00E86C41 /* SessionGetManualUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F4DB662B2C173F00E86C41 /* SessionGetManualUrl.swift */; };
A0F472F6BC78C5F1C5471836 /* Pods_SailAssistTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D252BA721FD314FAE4C2B4C /* Pods_SailAssistTests.framework */; };
D50533FA2EA7956900975ADD /* TransparentBackgroundViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50533F92EA7955500975ADD /* TransparentBackgroundViewModifier.swift */; };
D50533FC2EA8A93D00975ADD /* TextEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50533FB2EA8A93A00975ADD /* TextEditorView.swift */; };
D5056D3E2E8F4DE20076AC88 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D3D2E8F4DD80076AC88 /* SafariView.swift */; };
D5056D482E925FE90076AC88 /* LayoutConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D472E925FDF0076AC88 /* LayoutConstants.swift */; };
D5056D522E9343300076AC88 /* TaskTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5056D512E9343270076AC88 /* TaskTitleView.swift */; };
......@@ -255,6 +257,8 @@
B422ACD189FD390D51F10007 /* Pods-SailAssistTests.qc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SailAssistTests.qc.xcconfig"; path = "Target Support Files/Pods-SailAssistTests/Pods-SailAssistTests.qc.xcconfig"; sourceTree = "<group>"; };
C0CBB553F04E0BB4ACC72BD1 /* Pods-Sailassist.qc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sailassist.qc.xcconfig"; path = "Target Support Files/Pods-Sailassist/Pods-Sailassist.qc.xcconfig"; sourceTree = "<group>"; };
CA31D32D54D7C200EF057679 /* Pods-SailAssistTests.canary.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SailAssistTests.canary.xcconfig"; path = "Target Support Files/Pods-SailAssistTests/Pods-SailAssistTests.canary.xcconfig"; sourceTree = "<group>"; };
D50533F92EA7955500975ADD /* TransparentBackgroundViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransparentBackgroundViewModifier.swift; sourceTree = "<group>"; };
D50533FB2EA8A93A00975ADD /* TextEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEditorView.swift; sourceTree = "<group>"; };
D5056D3D2E8F4DD80076AC88 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
D5056D472E925FDF0076AC88 /* LayoutConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutConstants.swift; sourceTree = "<group>"; };
D5056D512E9343270076AC88 /* TaskTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskTitleView.swift; sourceTree = "<group>"; };
......@@ -673,6 +677,8 @@
02A1DE2D2AFB497B005BCF55 /* View */ = {
isa = PBXGroup;
children = (
D50533FB2EA8A93A00975ADD /* TextEditorView.swift */,
D50533F92EA7955500975ADD /* TransparentBackgroundViewModifier.swift */,
020B98622ADD14E40029DE4C /* ChatView.swift */,
02A1DE2E2AFB4AA0005BCF55 /* ChatInputView.swift */,
02C3E5D02AFCC16800AF7837 /* ChatTitleView.swift */,
......@@ -1111,6 +1117,7 @@
025C28002B034A1900BADC49 /* PDFViewer.swift in Sources */,
02C3E6092AFDF30000AF7837 /* ChatMemberView.swift in Sources */,
020B98412AD8C3810029DE4C /* LoginView.swift in Sources */,
D50533FC2EA8A93D00975ADD /* TextEditorView.swift in Sources */,
D5AE351D2AEBA6FC00059889 /* SessionLogin.swift in Sources */,
D5AF8A522E89252400BECA22 /* MenuMainView.swift in Sources */,
D5258C9F2B03527400365276 /* ResGetMessages.swift in Sources */,
......@@ -1200,6 +1207,7 @@
02CE4D892ADF62E1002E79BC /* EcaSettingView.swift in Sources */,
025F99722B2AE3AF00C9A18A /* TaskViewModel.swift in Sources */,
D54D17512B35923400A0EAA5 /* GetManualUrl.swift in Sources */,
D50533FA2EA7956900975ADD /* TransparentBackgroundViewModifier.swift in Sources */,
D5258CA32B036CC500365276 /* SessionGetMessage.swift in Sources */,
D52C2C082B9195A8003B286C /* MenuInformationView.swift in Sources */,
D5258C992B0334BF00365276 /* SessionShipStatus.swift in Sources */,
......
......@@ -4,18 +4,31 @@ import Speech
struct ChatInputView: View {
@EnvironmentObject private var sceneDelegate: SceneDelegate
@StateObject private var speechRecognizer = SpeechRecognizer()
@StateObject private var viewModel: ChatInputViewModel
@State private var imageData: Data = .init(capacity: 0)
@State private var source: UIImagePickerController.SourceType = .photoLibrary
@FocusState var isKeyboardFocused: Bool
@FocusState private var isKeyboardFocused: Bool
@ObservedObject var viewModel: ChatInputViewModel
@Binding private var isFocus: Bool
@Binding var isUploadingDialogPresented: Bool
@Binding private var isUploadingDialogPresented: Bool
@Binding private var isPresentingText: Bool
init(isFocus: Binding<Bool>, isUploadingDialogPresented: Binding<Bool>) {
init(
viewModel: ChatInputViewModel,
isFocus: Binding<Bool>,
isUploadingDialogPresented: Binding<Bool>,
isPresentingText: Binding<Bool>)
{
self.viewModel = viewModel
self._isFocus = isFocus
self._isUploadingDialogPresented = isUploadingDialogPresented
self._viewModel = StateObject(wrappedValue: ChatInputViewModel())
self._isPresentingText = isPresentingText
}
private var isIpad: Bool {
UIDevice.current.userInterfaceIdiom == .pad
}
var body: some View {
......@@ -31,6 +44,7 @@ struct ChatInputView: View {
sceneDelegate.tabWindow?.isHidden = true
source = .camera
viewModel.handleMediaInput(type: .camera)
isPresentingText = false
} label: {
Image(systemName: "camera")
.resizable()
......@@ -44,6 +58,7 @@ struct ChatInputView: View {
sceneDelegate.tabWindow?.isHidden = true
source = .photoLibrary
viewModel.handleMediaInput(type: .photoLibrary)
isPresentingText = false
} label: {
Label("Photo Library", systemImage: "photo.on.rectangle")
}
......@@ -67,6 +82,9 @@ struct ChatInputView: View {
if viewModel.isRecording {
speechRecognizer.transcribedText = ""
speechRecognizer.startRecording()
if isIpad {
isPresentingText = true
}
} else {
speechRecognizer.stopRecording()
viewModel.inputText = speechRecognizer.transcribedText
......@@ -78,7 +96,17 @@ struct ChatInputView: View {
.padding(5)
}
//MARK: - テキスト入力
//MARK: - テキスト入力開始ボタン
if isIpad {
Button {
isPresentingText = true
} label: {
Image(systemName: isPresentingText ? "message.fill" : "message")
.resizable()
.frame(width: 20, height: 20)
.padding(5)
}
} else {
TextEditor(text: $viewModel.inputText)
.focused($isKeyboardFocused)
.font(FontStyle.DefaultText.font)
......@@ -111,8 +139,10 @@ struct ChatInputView: View {
.onChange(of: viewModel.isKeyboardFocused) { isFocused in
isKeyboardFocused = isFocused
}
}
//MARK: - 送信ボタン
if !isIpad {
Button {
viewModel.sendChatMessage()
} label: {
......@@ -126,6 +156,7 @@ struct ChatInputView: View {
.padding(.trailing, 11)
.disabled(viewModel.inputText.isEmpty)
}
}
.background(ColorSet.BackgroundSecondary.color)
// MARK: - 画像ピッカー
......@@ -225,8 +256,3 @@ struct ChatInputView: View {
}
}
}
#Preview {
ChatInputView(isFocus: .constant(false), isUploadingDialogPresented: .constant(false))
.environmentObject(SceneDelegate())
}
......@@ -16,6 +16,8 @@ struct ChatView: View {
@State private var totalMediaCount: Int = 0
@State private var isMediaLoading: Bool = false
@State private var isUploadingDialogPresented: Bool = false
@ObservedObject var viewModel: ChatInputViewModel
@Binding var isPresentingText: Bool
var contentWidth: CGFloat
var body: some View {
......@@ -51,7 +53,10 @@ struct ChatView: View {
.padding(.top)
.onAppear {
guard !message.messages.isEmpty,
let id = message.messages.last?.messageId else { return }
let id = message.messages.last?.messageId else {
isMediaLoading = true
return
}
totalMediaCount = message.messages.reduce(0) { count, msg in
count + (msg.type >= 0 ? 1 : 0)
......@@ -129,16 +134,21 @@ struct ChatView: View {
message.viewCnt = 0
}
}
ChatInputView(isFocus: $isFocus, isUploadingDialogPresented: $isUploadingDialogPresented)
ChatInputView(
viewModel: viewModel,
isFocus: $isFocus,
isUploadingDialogPresented: $isUploadingDialogPresented,
isPresentingText: $isPresentingText
)
}
if !isMediaLoading {
LoadingView()
}
if isUploadingDialogPresented {
// if isUploadingDialogPresented {
// UpLoadingView()
}
// }
}
.background(ColorSet.BackgroundPrimary.color)
}
......@@ -235,8 +245,3 @@ struct AlertChatMessage: View {
}
}
}
#Preview {
ChatView(contentWidth: CGFloat())
}
import SwiftUI
struct TextEditorView: View {
@Binding var inputText: String
@Binding var isPresenting: Bool
var onSend: () -> Void
@FocusState private var isKeyboardFocused: Bool
var body: some View {
VStack {
VStack(spacing: 1) {
TextEditor(text: $inputText)
.focused($isKeyboardFocused)
.frame(height: 150)
.padding()
.font(FontStyle.DefaultText.font)
.scrollContentBackground(Visibility.hidden)
.foregroundColor(ColorSet.BodyChat.color)
.background(ColorSet.ChatBaloon.color)
.cornerRadius(5)
.onAppear {
isKeyboardFocused = true
}
HStack {
Button("Close") {
isPresenting = false
isKeyboardFocused = false
inputText = ""
}
.padding()
.background(ColorSet.BackgroundSecondary.color)
.cornerRadius(8)
Spacer()
Button {
onSend()
} label: {
Image("send")
.resizable()
.frame(width: 20, height: 20)
.padding(5)
.opacity(inputText.isEmpty ? 0.5 : 1.0)
}
.padding()
.background(ColorSet.BackgroundSecondary.color)
.foregroundColor(.white)
.cornerRadius(8)
.disabled(inputText.isEmpty)
}
.padding([.leading, .trailing], 1)
}
.frame(width: 250)
.padding()
.background(ColorSet.BackgroundSecondary.color)
.cornerRadius(12)
.shadow(radius: 10)
}
}
}
import SwiftUI
struct TransparentBackgroundViewModifier: ViewModifier {
func body(content: Content) -> some View {
content
.background(BackgroundClearView())
}
struct BackgroundClearView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
return UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
DispatchQueue.main.async {
uiViewController.view.superview?.superview?.backgroundColor = .clear
}
}
}
}
extension View {
func transparentBackground() -> some View {
self.modifier(TransparentBackgroundViewModifier())
}
}
......@@ -13,16 +13,24 @@ struct MapInformation: View {
var body: some View {
VStack {
VStack {
VStack(alignment: .trailing, spacing: 5) {
Text(presentLocation())
.font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)))
.foregroundColor(Color.black)
.padding(5)
Text("Heading:" + String(ship.heading) + "[°]")
.font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)))
.foregroundColor(Color.black)
Text("Course:" + String(ship.course) + "[°]")
.font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)))
.foregroundColor(Color.black)
Text("Speed:" + String(ship.speed) + "[knot]")
.font(Font(UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)))
.foregroundColor(Color.black)
}
.padding(10)
.background(Color.white.opacity(0.7))
.cornerRadius(3)
}
.padding(.top, 100)
.padding(.trailing, positionLocation())
VStack {
ForEach(ngaData.ngaArea.map{ $0.1 }.filter{ $0.passingCnt > 0 }, id: \.name) { nga in
Text("Entering NGA \(nga.name)")
......@@ -35,8 +43,10 @@ struct MapInformation: View {
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
}
//MARK: - 地図上の船情報
func presentLocation() -> String {
var location = " 00°00.000'N\n000°00.000'E"
if let shipPos = ship.location {
......@@ -44,18 +54,8 @@ struct MapInformation: View {
}
return location
}
func positionLocation() -> CGFloat {
var yPos: CGFloat = 200
if UIDevice.current.userInterfaceIdiom == .pad {
yPos = 900
}
return yPos
}
}
#Preview {
MapInformation()
}
import SwiftUI
struct LayoutConstants {
static let menuTitleHeight: CGFloat = 60 //メニュータイトルの高さ
static let menuViewWidth: CGFloat = 340 //iPadの地図上に表示されるメニューの幅
static let menuTitleHeight: CGFloat = 50 //メニュータイトルの高さ
static let menuViewWidth: CGFloat = 400 //iPadの地図上に表示されるメニューの幅
static let tabHeight: CGFloat = 50 //タブの高さ
static let menuMinimumHeight: CGFloat = menuTitleHeight + tabHeight
......
......@@ -10,8 +10,19 @@ struct CustomTabBar: View {
@ObservedObject var pushHistory = SharingData.pushHistory
var body: some View {
VStack(spacing: 0){
Divider()
ZStack(alignment: .bottomLeading) {
if UIDevice.current.userInterfaceIdiom == .pad {
RoundedRectangle(cornerRadius: 12)
.fill(ColorSet.BottomNav.color)
.frame(width: LayoutConstants.menuViewWidth, height: LayoutConstants.tabHeight)
.padding(.leading, 5)
}
VStack(alignment: UIDevice.current.userInterfaceIdiom == .phone ? .center : .leading, spacing: 0) {
Rectangle()
.fill(Color.clear)
.frame(height: 1)
HStack(spacing: 0){
ForEach(Tab.allCases, id: \.rawValue) { tab in
......@@ -57,9 +68,13 @@ struct CustomTabBar: View {
.contentShape(Rectangle())
}
}
.frame(height: LayoutConstants.tabHeight)
.frame(
width: UIDevice.current.userInterfaceIdiom == .pad ? LayoutConstants.menuViewWidth : nil,
height: LayoutConstants.tabHeight
)
}
}
.background(ColorSet.BottomNav.color)
.background(UIDevice.current.userInterfaceIdiom == .phone ? ColorSet.BottomNav.color : Color.clear)
.modifier(ChangeModeAlertModifier(
isPresented: $selectedTabModel.isShowChangeEmrMode,
currentMode: message.mode,
......
//
// MainTabView.swift
// forShip
//
// Created by Mamoru Sugita on 2023/10/13.
//
import SwiftUI
enum Tab: String, CaseIterable{
......@@ -26,9 +19,11 @@ enum Tab: String, CaseIterable{
struct MainTabView: View {
@EnvironmentObject var selectedTabModel: SelectedTabModel
@EnvironmentObject private var sceneDelegate: SceneDelegate
@StateObject private var viewModel = ChatInputViewModel()
@ObservedObject var location = SharingData.location
@State private var offset = CGSize.zero
@State var isSignout = false
@State private var isPresentingTextEditorView: Bool = false
var isTabWindowActive: Bool {
sceneDelegate.tabWindow != nil
......@@ -56,9 +51,22 @@ struct MainTabView: View {
GeometryReader { geometry in
ZStack(alignment: .bottom) {
TabView(selection: $selectedTabModel.activeTab) {
mapTab
ZStack {
MapRepresentable()
MapInformation()
.zIndex(1)
.padding(.top, geometry.safeAreaInsets.top)
.padding(.trailing, geometry.safeAreaInsets.trailing + 10)
}
.ignoresSafeArea(edges: .top)
.tag(Tab.map)
if SharingData.my.isCommunication {
ChatView(contentWidth: UIScreen.main.bounds.width).tag(Tab.chat)
ChatView(
viewModel: viewModel,
isPresentingText: $isPresentingTextEditorView,
contentWidth: UIScreen.main.bounds.width
).tag(Tab.chat)
}
NotificationView().tag(Tab.alert)
MenuView(isSignout: $isSignout).tag(Tab.menu)
......@@ -83,28 +91,24 @@ struct MainTabView: View {
enum OverlayHeightMode: CaseIterable {
case titleOnly
// case half
case full
var heightRatio: CGFloat {
switch self {
case .titleOnly: return 0.15
// case .half: return 0.5
case .full: return 1.0
}
}
}
@State private var overlayHeightMode: OverlayHeightMode = .full
@State private var overlayHeightMode: OverlayHeightMode = .titleOnly
private var padView: some View {
GeometryReader { geometry in
let overlayWidth = LayoutConstants.menuViewWidth
let fullHeight = geometry.size.height - (LayoutConstants.menuTitleHeight + geometry.safeAreaInsets.top + 15)
let overlayHeight = fullHeight * overlayHeightMode.heightRatio
let titleOnlyHeight = LayoutConstants.menuTitleHeight + geometry.safeAreaInsets.top
let overlayHeight = overlayHeightMode == .full ? fullHeight : titleOnlyHeight
ZStack(alignment: .bottomLeading) {
MapRepresentable()
.ignoresSafeArea()
MapInformation()
.zIndex(1)
.padding(.top, geometry.safeAreaInsets.top)
.padding(.trailing, geometry.safeAreaInsets.trailing + 10)
if selectedTabModel.activeTab == .menu {
MenuView(isSignout: $isSignout)
......@@ -137,8 +141,11 @@ struct MainTabView: View {
MenuTaskView()
.tag(Tab.map)
if SharingData.my.isCommunication {
ChatView(contentWidth: overlayWidth)
.tag(Tab.chat)
ChatView(
viewModel: viewModel,
isPresentingText: $isPresentingTextEditorView,
contentWidth: overlayWidth
).tag(Tab.chat)
}
NotificationView()
.tag(Tab.alert)
......@@ -149,21 +156,28 @@ struct MainTabView: View {
.background(ColorSet.BackgroundPrimary.color)
.cornerRadius(12)
.shadow(radius: 4)
.padding(.bottom, LayoutConstants.menuTitleHeight + 5)
.padding(.bottom, LayoutConstants.tabHeight + 5)
.padding(.leading, 5)
}
// MARK: - 別ViewとしてのTextEditor (iPad)
if isPresentingTextEditorView {
VStack {
TextEditorView(
inputText: $viewModel.inputText,
isPresenting: $isPresentingTextEditorView,
onSend: {
viewModel.sendChatMessage()
isPresentingTextEditorView = false
}
)
.transparentBackground()
.frame(maxWidth: geometry.size.width * 1.2, maxHeight: 300)
}
.zIndex(3)
}
}
@ViewBuilder
private var mapTab: some View {
ZStack {
MapRepresentable()
MapInformation().zIndex(1)
}
.ignoresSafeArea(edges: .top)
.tag(Tab.map)
}
private func configureTabBarAppearance() {
......@@ -173,9 +187,3 @@ struct MainTabView: View {
UITabBar.appearance().standardAppearance = appearance
}
}
#Preview {
MainTabView()
.environmentObject(SelectedTabModel())
.environmentObject(SceneDelegate())
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment