Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
S
Sailassist
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
54
Merge Requests
54
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
gr-ssv
Sailassist
Commits
8cec0939
Commit
8cec0939
authored
Sep 29, 2025
by
shigemi miura
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
・クラス分割
・チャットのイメージデータの先読み ・イメージデータ送信時はテンポラリを表示
parent
d2befa8b
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
37 changed files
with
1450 additions
and
255 deletions
+1450
-255
project.pbxproj
Seilassist/Sailassist.xcodeproj/project.pbxproj
+0
-0
NotificationView.swift
Seilassist/Sailassist/Alert/NotificationView.swift
+3
-3
GetMessage.swift
Seilassist/Sailassist/Chat/GetMessage.swift
+6
-0
ImageQualityPreset.swift
Seilassist/Sailassist/Chat/ImageQualityPreset.swift
+8
-7
Imagepicker.swift
Seilassist/Sailassist/Chat/Imagepicker.swift
+35
-8
KeyboardResponder.swift
Seilassist/Sailassist/Chat/KeyboardResponder.swift
+36
-0
ChatInputView.swift
Seilassist/Sailassist/Chat/View/ChatInputView.swift
+0
-0
ChatUrlImageView.swift
Seilassist/Sailassist/Chat/View/ChatUrlImageView.swift
+32
-45
ChatUrlVideoView.swift
Seilassist/Sailassist/Chat/View/ChatUrlVideoView.swift
+41
-0
ChatView.swift
Seilassist/Sailassist/Chat/View/ChatView.swift
+93
-22
CustomCornerRadius.swift
Seilassist/Sailassist/Chat/View/CustomCornerRadius.swift
+0
-1
MyChatContentView.swift
Seilassist/Sailassist/Chat/View/MyChatContentView.swift
+7
-3
OtherChatContentView.swift
Seilassist/Sailassist/Chat/View/OtherChatContentView.swift
+6
-2
ChatInputViewModel.swift
...assist/Sailassist/Chat/ViewModel/ChatInputViewModel.swift
+170
-0
ChatViewModel.swift
Seilassist/Sailassist/Chat/ViewModel/ChatViewModel.swift
+43
-0
ContentView.swift
Seilassist/Sailassist/ContentView.swift
+2
-0
LocationCalculation.swift
Seilassist/Sailassist/Location/LocationCalculation.swift
+2
-2
LoginView.swift
Seilassist/Sailassist/Login/LoginView.swift
+5
-6
MapRepresentable.swift
Seilassist/Sailassist/Map/MapRepresentable.swift
+4
-2
EcaListMainView.swift
Seilassist/Sailassist/Map/Task/View/EcaListMainView.swift
+50
-0
EcaMenuView.swift
Seilassist/Sailassist/Map/Task/View/EcaMenuView.swift
+54
-0
EcaSettingMainView.swift
Seilassist/Sailassist/Map/Task/View/EcaSettingMainView.swift
+53
-0
FuelSwitchingMainView.swift
...sist/Sailassist/Map/Task/View/FuelSwitchingMainView.swift
+51
-0
FuelSwitchingView.swift
Seilassist/Sailassist/Map/Task/View/FuelSwitchingView.swift
+0
-54
MenuMainView.swift
Seilassist/Sailassist/Map/Task/View/MenuMainView.swift
+78
-0
MenuTaskView.swift
Seilassist/Sailassist/Map/Task/View/MenuTaskView.swift
+0
-0
NgaMenuView.swift
Seilassist/Sailassist/Map/Task/View/NgaMenuView.swift
+49
-0
NgaNotificationMainView.swift
...st/Sailassist/Map/Task/View/NgaNotificationMainView.swift
+51
-0
NgaNotificationView.swift
...assist/Sailassist/Map/Task/View/NgaNotificationView.swift
+0
-49
NgaSettingMainView.swift
Seilassist/Sailassist/Map/Task/View/NgaSettingMainView.swift
+60
-0
SailassistApp.swift
Seilassist/Sailassist/SailassistApp.swift
+82
-31
SessionUploadImage.swift
Seilassist/Sailassist/ServerSession/SessionUploadImage.swift
+32
-20
CustomTabBar.swift
Seilassist/Sailassist/Tab/CustomTabBar.swift
+119
-0
CustomTabBarViewModel.swift
Seilassist/Sailassist/Tab/CustomTabBarViewModel.swift
+115
-0
MainTabView.swift
Seilassist/Sailassist/Tab/MainTabView.swift
+0
-0
NotificationBadge.swift
Seilassist/Sailassist/Tab/NotificationBadge.swift
+47
-0
MainTabView.swift
Seilassist/Sailassist/Tab/View/MainTabView.swift
+116
-0
No files found.
Seilassist/Sailassist.xcodeproj/project.pbxproj
View file @
8cec0939
This diff is collapsed.
Click to expand it.
Seilassist/Sailassist/Alert/NotificationView.swift
View file @
8cec0939
...
...
@@ -38,9 +38,9 @@ struct NotificationView: View {
.
onAppear
{
let
pushCount
=
pushHist
.
viewCnt
print
(
debug
:
"
\(
pushCount
)
"
)
DispatchQueue
.
main
.
async
{
pushHist
.
viewCnt
=
0
}
//
DispatchQueue.main.async {
//
pushHist.viewCnt = 0
//
}
}
}
...
...
Seilassist/Sailassist/Chat/GetMessage.swift
View file @
8cec0939
...
...
@@ -9,6 +9,7 @@ import Foundation
class
GetMessage
{
var
sessionGetMessage
=
SessionGetMessage
()
var
chatViewModel
:
ChatViewModel
?
func
start
()
{
print
(
debug
:
"called"
)
...
...
@@ -27,6 +28,11 @@ class GetMessage {
if
let
msg
=
res
.
messages
{
//既読マーク確認
SharingData
.
message
.
messages
=
msg
Task
{
@MainActor
in
self
.
chatViewModel
?
.
loadMessages
()
}
self
.
checkUnreadMessages
()
}
SharingData
.
message
.
users
=
[]
...
...
Seilassist/Sailassist/Chat/ImageQualityPreset.swift
View file @
8cec0939
...
...
@@ -12,20 +12,21 @@ enum ImageQualityPreset {
}
}
var
targetSize
:
CGSize
{
var
scale
:
CGFloat
{
switch
self
{
case
.
low
:
return
CGSize
(
width
:
640
,
height
:
480
)
case
.
middle
:
return
CGSize
(
width
:
1280
,
height
:
960
)
case
.
high
:
return
CGSize
(
width
:
1920
,
height
:
1440
)
case
.
low
:
return
0.3
case
.
middle
:
return
0.6
case
.
high
:
return
1.0
}
}
}
extension
UIImage
{
func
resized
(
to
targetSize
:
CGSize
)
->
UIImage
{
let
renderer
=
UIGraphicsImageRenderer
(
size
:
targetSize
)
func
resized
(
to
scale
:
CGFloat
)
->
UIImage
?
{
let
newSize
=
CGSize
(
width
:
self
.
size
.
width
*
scale
,
height
:
self
.
size
.
height
*
scale
)
let
renderer
=
UIGraphicsImageRenderer
(
size
:
newSize
)
return
renderer
.
image
{
_
in
self
.
draw
(
in
:
CGRect
(
origin
:
.
zero
,
size
:
target
Size
))
self
.
draw
(
in
:
CGRect
(
origin
:
.
zero
,
size
:
new
Size
))
}
}
}
Seilassist/Sailassist/Chat/Imagepicker.swift
View file @
8cec0939
...
...
@@ -41,19 +41,46 @@ struct Imagepicker : UIViewControllerRepresentable {
}
//MARK: - Use Photo
func
imagePickerController
(
_
picker
:
UIImagePickerController
,
didFinishPickingMediaWithInfo
info
:
[
UIImagePickerController
.
InfoKey
:
Any
])
{
func
imagePickerController
(
_
picker
:
UIImagePickerController
,
didFinishPickingMediaWithInfo
info
:
[
UIImagePickerController
.
InfoKey
:
Any
])
{
if
let
image
=
info
[
.
originalImage
]
as?
UIImage
{
let
resizedImage
=
image
.
resized
(
to
:
parent
.
preset
.
targetSize
)
if
let
data
=
resizedImage
.
jpegData
(
compressionQuality
:
parent
.
preset
.
compressionQuality
)
{
parent
.
image
=
data
//MARK: - 画像データのリサイズ
DispatchQueue
.
global
(
qos
:
.
userInitiated
)
.
async
{
// リサイズ処理
let
scale
:
CGFloat
=
min
(
0.1
,
max
(
self
.
parent
.
preset
.
scale
,
1.0
))
if
let
resizedImage
=
image
.
resized
(
to
:
scale
),
let
data
=
resizedImage
.
jpegData
(
compressionQuality
:
self
.
parent
.
preset
.
compressionQuality
)
{
DispatchQueue
.
main
.
async
{
self
.
parent
.
image
=
data
self
.
parent
.
show
.
toggle
()
}
}
else
{
DispatchQueue
.
main
.
async
{
print
(
"Failed to resize or compress image"
)
self
.
parent
.
show
.
toggle
()
}
}
}
}
else
if
let
videoURL
=
info
[
.
mediaURL
]
as?
URL
{
// 動画ファイルの処理(例: Dataに変換して保存)
if
let
videoData
=
try
?
Data
(
contentsOf
:
videoURL
)
{
parent
.
image
=
videoData
//MARK: - 動画データのリサイズ
DispatchQueue
.
global
(
qos
:
.
userInitiated
)
.
async
{
if
let
videoData
=
try
?
Data
(
contentsOf
:
videoURL
)
{
DispatchQueue
.
main
.
async
{
self
.
parent
.
image
=
videoData
self
.
parent
.
show
.
toggle
()
}
}
else
{
DispatchQueue
.
main
.
async
{
print
(
"Failed to process video data"
)
self
.
parent
.
show
.
toggle
()
}
}
}
}
else
{
DispatchQueue
.
main
.
async
{
print
(
"No valid media selected"
)
self
.
parent
.
show
.
toggle
()
}
}
parent
.
show
.
toggle
()
}
}
}
Seilassist/Sailassist/Chat/KeyboardResponder.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
import
Combine
class
KeyboardResponder
:
ObservableObject
{
@Published
var
currentHeight
:
CGFloat
=
0
@Published
var
isKeyboardVisible
:
Bool
=
false
private
var
cancellableSet
:
Set
<
AnyCancellable
>
=
[]
init
()
{
let
willShow
=
NotificationCenter
.
default
.
publisher
(
for
:
UIResponder
.
keyboardWillShowNotification
)
.
compactMap
{
notification
->
CGFloat
?
in
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
0.1
)
{
self
.
isKeyboardVisible
=
true
}
return
(
notification
.
userInfo
?[
UIResponder
.
keyboardFrameEndUserInfoKey
]
as?
CGRect
)?
.
height
??
0
}
let
willHide
=
NotificationCenter
.
default
.
publisher
(
for
:
UIResponder
.
keyboardWillHideNotification
)
.
map
{
_
->
CGFloat
in
DispatchQueue
.
main
.
async
{
self
.
isKeyboardVisible
=
false
}
return
0
}
Publishers
.
Merge
(
willShow
,
willHide
)
.
receive
(
on
:
RunLoop
.
main
)
.
assign
(
to
:
\
KeyboardResponder
.
currentHeight
,
on
:
self
)
.
store
(
in
:
&
cancellableSet
)
}
deinit
{
cancellableSet
.
forEach
{
$0
.
cancel
()
}
}
}
Seilassist/Sailassist/Chat/View/ChatInputView.swift
View file @
8cec0939
This diff is collapsed.
Click to expand it.
Seilassist/Sailassist/Chat/View/ChatUrlImageView.swift
View file @
8cec0939
...
...
@@ -8,55 +8,42 @@
import
SwiftUI
struct
ChatUrlImageView
:
View
{
var
imageUrl
=
""
init
(
message
:
ChatMessage
)
{
if
let
url
=
message
.
message
{
self
.
imageUrl
=
url
}
}
let
imageUrl
:
String
var
onLoad
:
(()
->
Void
)?
=
nil
var
body
:
some
View
{
AsyncImage
(
url
:
URL
(
string
:
imageUrl
))
{
phase
in
if
let
image
=
phase
.
image
{
image
.
resizable
(
resizingMode
:
.
stretch
)
.
aspectRatio
(
contentMode
:
.
fit
)
.
frame
(
width
:
250
)
}
else
if
phase
.
error
!=
nil
{
Color
.
gray
.
opacity
(
0.2
)
.
overlay
(
Image
(
systemName
:
"rectangle.slash"
))
}
else
{
Color
.
gray
.
opacity
(
0.2
)
ProgressView
()
if
let
url
=
URL
(
string
:
imageUrl
)
{
AsyncImage
(
url
:
url
)
{
phase
in
switch
phase
{
case
.
success
(
let
image
):
image
.
resizable
()
.
aspectRatio
(
contentMode
:
.
fit
)
.
frame
(
width
:
250
)
.
onAppear
{
onLoad
?()
}
case
.
failure
(
_
):
VStack
{
Image
(
systemName
:
"exclamationmark.triangle"
)
Text
(
"Image loading failed"
)
.
font
(
.
caption
)
}
.
frame
(
width
:
250
,
height
:
250
)
.
background
(
Color
.
gray
.
opacity
(
0.2
))
case
.
empty
:
ProgressView
()
.
frame
(
width
:
250
,
height
:
250
)
.
background
(
Color
.
gray
.
opacity
(
0.2
))
@unknown
default
:
EmptyView
()
}
}
}
else
{
Color
.
gray
.
opacity
(
0.2
)
.
frame
(
width
:
250
,
height
:
250
)
.
overlay
(
Text
(
"Invalid URL"
))
}
.
cornerRadius
(
16
)
.
frame
(
height
:
250
)
}
}
#Preview {
ChatUrlImageView
(
message
:
ChatMessage
(
shipId
:
10000003
,
messageId
:
"92c2dfb5-f5ed-4943-98a3-9848d7f9a962"
,
type
:
0
,
time
:
"2023-10-06T01:51:01.872Z"
,
location
:
1
,
from
:
"はだだ"
,
fromId
:
"487420489"
,
mode
:
0
,
message
:
"999"
,
stampId
:
0
,
viewer
:
[
Viewer
(
time
:
"2023-10-06T01:51:12.973Z"
,
location
:
1
,
id
:
""
),
Viewer
(
time
:
"2023-10-06T01:51:12.973Z"
,
location
:
2
,
id
:
""
)
]
))
}
Seilassist/Sailassist/Chat/View/ChatUrlVideoView.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
import
AVKit
struct
ChatUrlVideoView
:
View
{
@State
private
var
player
:
AVPlayer
?
@State
private
var
isPlaying
=
false
let
imageUrl
:
String
var
onLoad
:
(()
->
Void
)?
=
nil
var
body
:
some
View
{
ZStack
{
if
let
url
=
URL
(
string
:
imageUrl
)
{
VideoPlayer
(
player
:
player
)
.
frame
(
height
:
250
)
.
cornerRadius
(
16
)
.
onAppear
{
player
=
AVPlayer
(
url
:
url
)
onLoad
?()
}
.
onTapGesture
{
if
isPlaying
{
player
?
.
pause
()
}
else
{
player
?
.
play
()
}
isPlaying
.
toggle
()
}
}
else
{
Color
.
gray
.
opacity
(
0.2
)
.
frame
(
height
:
250
)
.
overlay
(
VStack
{
Image
(
systemName
:
"video.slash"
)
Text
(
"Video URL is invalid"
)
.
font
(
.
caption
)
}
)
}
}
}
}
Seilassist/Sailassist/Chat/ChatView.swift
→
Seilassist/Sailassist/Chat/
View/
ChatView.swift
View file @
8cec0939
...
...
@@ -13,6 +13,10 @@ struct ChatView: View {
@State
var
isShowMember
:
Bool
=
false
@State
var
isNotification
=
Preferences
.
ChatNotification
@State
var
isFocus
:
Bool
=
true
@State
private
var
loadedMediaCount
:
Int
=
0
@State
private
var
totalMediaCount
:
Int
=
0
@State
private
var
isMediaLoading
:
Bool
=
false
@State
private
var
isUploadingDialogPresented
:
Bool
=
false
var
body
:
some
View
{
ZStack
{
...
...
@@ -30,12 +34,12 @@ struct ChatView: View {
ForEach
(
message
.
messages
,
id
:
\
.
messageId
)
{
msg
in
if
msg
.
message
!=
nil
{
if
msg
.
from
==
Preferences
.
UserName
{
//自分のメッセージ
MyChatContentView
(
message
:
msg
)
//
MARK: -
自分のメッセージ
MyChatContentView
(
message
:
msg
,
onMediaLoaded
:
{
handleMediaLoaded
(
proxy
:
proxy
)}
)
.
padding
(
.
bottom
,
24
)
}
else
{
//他人のメッセージ
OtherChatContentView
(
message
:
msg
)
//
MARK: -
他人のメッセージ
OtherChatContentView
(
message
:
msg
,
onMediaLoaded
:
{
handleMediaLoaded
(
proxy
:
proxy
)}
)
.
padding
(
.
bottom
,
24
)
}
}
else
{
...
...
@@ -48,27 +52,32 @@ struct ChatView: View {
.
onAppear
{
guard
!
message
.
messages
.
isEmpty
,
let
id
=
message
.
messages
.
last
?
.
messageId
else
{
return
}
proxy
.
scrollTo
(
id
,
anchor
:
.
bottom
)
DispatchQueue
.
main
.
async
{
message
.
viewCnt
=
0
totalMediaCount
=
message
.
messages
.
reduce
(
0
)
{
count
,
msg
in
count
+
(
msg
.
type
>=
2
?
1
:
0
)
}
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
0.2
)
{
proxy
.
scrollTo
(
id
,
anchor
:
.
bottom
)
}
}
.
onChange
(
of
:
message
.
messages
.
count
)
{
newValue
in
print
(
debug
:
"ChatView: onChange { newValue:
\(
newValue
)
"
)
withAnimation
{
if
let
id
=
message
.
messages
.
last
?
.
messageId
{
proxy
.
scrollTo
(
id
,
anchor
:
.
bottom
)
//Warningモードは既読は返さない
if
message
.
messages
.
last
?
.
message
!=
nil
{
if
message
.
messages
.
last
?
.
from
!=
Preferences
.
UserName
{
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
0.5
)
{
let
getMessage
=
GetMessage
()
getMessage
.
readNotification
()
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
0.2
)
{
proxy
.
scrollTo
(
id
,
anchor
:
.
bottom
)
//MARK: - Warningモードは既読は返さない
if
message
.
messages
.
last
?
.
message
!=
nil
{
if
message
.
messages
.
last
?
.
from
!=
Preferences
.
UserName
{
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
0.5
)
{
let
getMessage
=
GetMessage
()
getMessage
.
readNotification
()
}
}
}
}
message
.
viewCnt
=
0
}
}
}
...
...
@@ -77,7 +86,6 @@ struct ChatView: View {
guard
!
value
.
isEmpty
else
{
return
}
if
let
id
=
message
.
messages
.
last
?
.
messageId
{
proxy
.
scrollTo
(
id
,
anchor
:
.
bottom
)
message
.
viewCnt
=
0
}
}
}
...
...
@@ -116,19 +124,80 @@ struct ChatView: View {
.
onTapGesture
{
isFocus
=
false
}
ChatInputView
(
isFocus
:
$
isFocus
)
.
onAppear
{
//MARK: - 画面が表示された時に実行 既読処理
if
message
.
viewCnt
>
0
{
message
.
viewCnt
=
0
}
}
ChatInputView
(
isFocus
:
$
isFocus
,
isUploadingDialogPresented
:
$
isUploadingDialogPresented
)
}
if
!
isMediaLoading
{
LoadingView
()
}
if
isUploadingDialogPresented
{
// UpLoadingView()
}
}
.
background
(
ColorSet
.
BackgroundPrimary
.
color
)
}
//MARK: - 動画・画像読み込み完了後に最新チャットに移動
private
func
handleMediaLoaded
(
proxy
:
ScrollViewProxy
)
{
guard
!
message
.
messages
.
isEmpty
,
let
id
=
message
.
messages
.
last
?
.
messageId
else
{
return
}
loadedMediaCount
+=
1
if
loadedMediaCount
>=
totalMediaCount
{
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
0.2
)
{
proxy
.
scrollTo
(
id
,
anchor
:
.
bottom
)
isMediaLoading
=
true
}
}
}
}
// MARK: - 全てのチャットのローディング画面
struct
LoadingView
:
View
{
var
body
:
some
View
{
ZStack
{
Color
.
black
.
opacity
(
1.0
)
.
edgesIgnoringSafeArea
(
.
all
)
ProgressView
(
"Loading..."
)
.
progressViewStyle
(
CircularProgressViewStyle
(
tint
:
.
white
))
.
foregroundColor
(
.
white
)
.
padding
()
.
background
(
Color
.
gray
.
opacity
(
0.7
))
.
cornerRadius
(
10
)
}
}
}
// MARK: - 動画・画像のアップロード画面
struct
UpLoadingView
:
View
{
var
body
:
some
View
{
ZStack
{
Color
.
black
.
opacity
(
0.8
)
.
edgesIgnoringSafeArea
(
.
all
)
ProgressView
(
"UpLoading..."
)
.
progressViewStyle
(
CircularProgressViewStyle
(
tint
:
.
white
))
.
foregroundColor
(
.
white
)
.
padding
()
.
background
(
Color
.
gray
.
opacity
(
0.7
))
.
cornerRadius
(
10
)
}
}
}
struct
AlertChatMessage
:
View
{
@Environment(\
.colorScheme)
var
colorScheme
@Environment(\.colorScheme)
var
colorScheme
var
message
:
ChatMessage
var
body
:
some
View
{
if
message
.
mode
==
ChatMode
.
warningProgress
.
rawValue
{
switch
message
.
mode
{
case
ChatMode
.
warningProgress
.
rawValue
:
HStack
()
{
Rectangle
()
.
fill
(
ColorSet
.
ChatDate
.
color
)
...
...
@@ -140,7 +209,7 @@ struct AlertChatMessage: View {
.
fill
(
ColorSet
.
ChatDate
.
color
)
.
frame
(
width
:
20
,
height
:
1
)
}
}
else
{
case
ChatMode
.
normal
.
rawValue
:
HStack
()
{
Rectangle
()
.
fill
(
ColorSet
.
ChatDate
.
color
)
...
...
@@ -152,6 +221,8 @@ struct AlertChatMessage: View {
.
fill
(
ColorSet
.
ChatDate
.
color
)
.
frame
(
width
:
20
,
height
:
1
)
}
default
:
EmptyView
()
}
}
}
...
...
Seilassist/Sailassist/Chat/View/CustomCornerRadius.swift
View file @
8cec0939
...
...
@@ -46,7 +46,6 @@ enum Corners{
case
br
}
fileprivate
struct
CustomCornerRadiusModifier
:
ViewModifier
{
let
cornerRadius
:
CGFloat
let
corners
:
[
Corners
]
...
...
Seilassist/Sailassist/Chat/View/MyChatContentView.swift
View file @
8cec0939
...
...
@@ -9,14 +9,18 @@ import SwiftUI
struct
MyChatContentView
:
View
{
var
message
:
ChatMessage
var
onMediaLoaded
:
(()
->
Void
)?
=
nil
var
body
:
some
View
{
HStack
{
Spacer
()
VStack
(
alignment
:
.
trailing
,
spacing
:
6
)
{
Group
{
if
let
msg
=
message
.
message
{
if
msg
.
contains
(
"https://"
)
{
ChatUrlImageView
(
message
:
message
)
if
msg
.
contains
(
".jpg"
)
||
msg
.
contains
(
".png"
)
{
ChatUrlImageView
(
imageUrl
:
msg
,
onLoad
:
onMediaLoaded
)
}
else
if
msg
.
contains
(
".mp4"
)
||
msg
.
contains
(
".mov"
)
{
ChatUrlVideoView
(
imageUrl
:
msg
,
onLoad
:
onMediaLoaded
)
}
else
{
Text
(
msg
)
.
font
(
FontStyle
.
DefaultText
.
font
)
...
...
@@ -36,7 +40,7 @@ struct MyChatContentView: View {
}
HStack
(
spacing
:
5
){
//既読マーク
//
MARK: -
既読マーク
Text
(
DateTextLib
.
ISO86012FormatText
(
message
.
time
,
format
:
"yyyy/MM/dd HH:mm"
,
errFormat
:
""
))
.
padding
(
.
trailing
,
8
)
...
...
Seilassist/Sailassist/Chat/View/OtherChatContentView.swift
View file @
8cec0939
...
...
@@ -9,6 +9,8 @@ import SwiftUI
struct
OtherChatContentView
:
View
{
var
message
:
ChatMessage
var
onMediaLoaded
:
(()
->
Void
)?
=
nil
var
body
:
some
View
{
HStack
{
VStack
(
alignment
:
.
leading
,
spacing
:
4
)
{
...
...
@@ -20,8 +22,10 @@ struct OtherChatContentView: View {
VStack
(
alignment
:
.
leading
,
spacing
:
10
)
{
Group
{
if
let
msg
=
message
.
message
{
if
msg
.
contains
(
"https://"
)
{
ChatUrlImageView
(
message
:
message
)
if
msg
.
contains
(
".jpg"
)
||
msg
.
contains
(
".png"
)
{
ChatUrlImageView
(
imageUrl
:
msg
,
onLoad
:
onMediaLoaded
)
}
else
if
msg
.
contains
(
".mp4"
)
||
msg
.
contains
(
".mov"
)
{
ChatUrlVideoView
(
imageUrl
:
msg
,
onLoad
:
onMediaLoaded
)
}
else
{
Text
(
msg
)
.
font
(
FontStyle
.
DefaultText
.
font
)
...
...
Seilassist/Sailassist/Chat/ViewModel/ChatInputViewModel.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
import
Combine
import
Speech
enum
MediaInputType
{
case
none
case
camera
case
photoLibrary
case
fileImport
}
class
ChatInputViewModel
:
ObservableObject
{
@Published
var
failedUploadImage
:
ReqUploadImage
?
=
nil
@Published
var
isRetryDialogPresented
=
false
@Published
var
isSignalrRestart
=
false
@Published
var
isRecording
=
false
@Published
var
isChatAlert
=
false
@Published
var
inputText
=
""
@Published
var
textViewHeight
:
CGFloat
=
40
@Published
var
tempId
:
String
=
""
@Published
var
mediaInputType
:
MediaInputType
=
.
none
@Published
var
isImagePickerPresented
=
false
@Published
var
isFileImporterPresented
=
false
@Published
var
isKeyboardFocused
:
Bool
=
false
@Published
var
isUploadingDialogPresented
:
Bool
=
false
@Published
var
isFocus
:
Double
=
0.0
private
let
sessionUploadImage
=
SessionUploadImage
()
//MARK: - メディア入力
func
handleMediaInput
(
type
:
MediaInputType
)
{
self
.
mediaInputType
=
type
if
type
==
.
camera
||
type
==
.
photoLibrary
{
self
.
isImagePickerPresented
=
true
}
else
if
type
==
.
fileImport
{
self
.
isFileImporterPresented
=
true
}
}
// MARK: - Send Chat Image
func
sendChatImage
(
_
uploadImage
:
ReqUploadImage
)
{
Task
{
do
{
isUploadingDialogPresented
=
true
let
response
=
try
await
sessionUploadImage
.
requestUploadImage
(
uploadImage
)
print
(
debug
:
"Upload success:
\(
response
)
"
)
let
serverSession
=
ServerSession
()
_
=
serverSession
.
fromJSON
(
resultData
:
response
,
resltType
:
ResLogin
.
self
)
sessionUploadImage
.
progress
=
0.0
// 完了後リセット
isUploadingDialogPresented
=
false
}
catch
{
print
(
debug
:
"Upload failed:
\(
error
)
"
)
sessionUploadImage
.
progress
=
0.0
// エラー時もリセット
isUploadingDialogPresented
=
false
failedUploadImage
=
uploadImage
isRetryDialogPresented
=
true
}
}
}
// MARK: - Send Chat Message
func
sendChatMessage
()
{
guard
!
SharingData
.
message
.
sendInf
else
{
return
}
isRecording
=
false
SignalR
()
.
chatMessage
(
message
:
inputText
,
completion
:
responseChatMessage
)
SharingData
.
message
.
sendInf
=
true
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
3.0
)
{
self
.
restartChatMessage
()
}
}
// MARK: - Chat Response
private
func
responseChatMessage
(
error
:
Error
?)
{
SharingData
.
message
.
sendInf
=
false
if
let
error
=
error
{
print
(
"Chat error:
\(
error
)
"
)
isChatAlert
=
true
}
else
{
isKeyboardFocused
=
false
inputText
=
""
}
}
// MARK: - Restart Chat if No Response
private
func
restartChatMessage
()
{
if
SharingData
.
message
.
sendInf
{
SignalR
()
.
stopConnection
()
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
1.0
)
{
self
.
isSignalrRestart
=
true
}
}
}
//MARK: - テキスト高さ
func
recalculateHeight
()
{
let
textView
=
UITextView
()
textView
.
text
=
inputText
textView
.
font
=
UIFont
.
preferredFont
(
forTextStyle
:
.
body
)
let
fixedWidth
=
UIScreen
.
main
.
bounds
.
width
-
40
// 適宜調整
let
newSize
=
textView
.
sizeThatFits
(
CGSize
(
width
:
fixedWidth
,
height
:
CGFloat
.
greatestFiniteMagnitude
))
textViewHeight
=
max
(
newSize
.
height
,
40
)
// 最低高さを40に設定
}
// MARK: - ローカル画像を一時保存してURLを取得
func
saveImageToTemporaryDirectory
(
_
image
:
UIImage
)
->
String
?
{
let
fileName
=
UUID
()
.
uuidString
+
".jpg"
let
tempDir
=
FileManager
.
default
.
temporaryDirectory
let
fileURL
=
tempDir
.
appendingPathComponent
(
fileName
)
guard
let
data
=
image
.
jpegData
(
compressionQuality
:
0.8
)
else
{
print
(
"Image JPEG conversion failed"
)
return
nil
}
do
{
try
data
.
write
(
to
:
fileURL
)
return
fileURL
.
absoluteString
}
catch
{
print
(
"Image JPEG conversion failed:
\(
error
.
localizedDescription
)
"
)
return
nil
}
}
//MARK: - 送信前に送信画像をテンポラリに入れる
func
sendImageToTemporary
(
_
image
:
UIImage
)
{
if
let
localURL
=
saveImageToTemporaryDirectory
(
image
),
let
jpegData
=
image
.
jpegData
(
compressionQuality
:
1.0
)
{
tempId
=
UUID
()
.
uuidString
let
viewer
=
Viewer
(
time
:
DateTextLib
.
Date2ISO8601Text
(
Date
()),
location
:
2
,
id
:
""
,
name
:
""
)
let
tempMessage
=
ChatMessage
(
shipId
:
Preferences
.
shipId
,
messageId
:
tempId
,
type
:
2
,
time
:
DateTextLib
.
Date2ISO8601Text
(
Date
()),
location
:
2
,
from
:
Preferences
.
UserName
,
fromId
:
String
(
SharingData
.
my
.
id
),
mode
:
SharingData
.
message
.
mode
?
1
:
0
,
message
:
localURL
,
stampId
:
0
,
viewer
:
[
viewer
]
)
SharingData
.
message
.
messages
.
append
(
tempMessage
)
let
uploadImage
=
ReqUploadImage
(
shipId
:
Preferences
.
shipId
,
messageId
:
tempId
,
location
:
2
,
from
:
Preferences
.
UserName
,
fromId
:
String
(
SharingData
.
my
.
id
),
files
:
jpegData
)
sendChatImage
(
uploadImage
)
}
}
}
Seilassist/Sailassist/Chat/ViewModel/ChatViewModel.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
import
AVFoundation
@MainActor
class
ChatViewModel
:
ObservableObject
{
@Published
var
messages
:
[
ChatMessage
]
=
[]
@Published
var
isMediaPrefetched
:
Bool
=
false
func
loadMessages
()
{
messages
=
SharingData
.
message
.
messages
prefetchMedia
()
}
private
func
prefetchMedia
()
{
let
imageURLs
=
messages
.
filter
{
$0
.
type
==
2
&&
$0
.
message
!=
nil
}
.
compactMap
{
$0
.
message
}
.
compactMap
{
URL
(
string
:
$0
)
}
let
videoURLs
=
messages
.
filter
{
$0
.
type
==
3
&&
$0
.
message
!=
nil
}
.
compactMap
{
$0
.
message
}
.
compactMap
{
URL
(
string
:
$0
)
}
Task
{
await
withTaskGroup
(
of
:
Void
.
self
)
{
group
in
for
url
in
imageURLs
{
group
.
addTask
{
let
request
=
URLRequest
(
url
:
url
,
cachePolicy
:
.
returnCacheDataElseLoad
,
timeoutInterval
:
10
)
_
=
try
?
await
URLSession
.
shared
.
data
(
for
:
request
)
}
}
for
url
in
videoURLs
{
group
.
addTask
{
let
asset
=
AVURLAsset
(
url
:
url
)
do
{
_
=
try
await
asset
.
load
(
.
isPlayable
)
}
catch
{
print
(
debug
:
"Video loading failure:
\(
url
)
"
)
}
}
}
}
isMediaPrefetched
=
true
}
}
}
Seilassist/Sailassist/ContentView.swift
View file @
8cec0939
...
...
@@ -14,6 +14,7 @@ class LoginViewModel: ObservableObject{
struct
ContentView
:
View
{
@StateObject
private
var
loginViewModel
=
LoginViewModel
()
@EnvironmentObject
private
var
sceneDelegate
:
SceneDelegate
@StateObject
private
var
chatViewModel
=
ChatViewModel
()
let
selectedTabModel
=
SelectedTabModel
()
...
...
@@ -38,6 +39,7 @@ struct ContentView: View {
},
content
:
{
LoginView
(
isLogin
:
$
loginViewModel
.
isLogin
)
.
environmentObject
(
selectedTabModel
)
.
environmentObject
(
chatViewModel
)
})
}
}
...
...
Seilassist/Sailassist/Location/LocationCalculation.swift
View file @
8cec0939
...
...
@@ -908,9 +908,9 @@ class LocationCalculation{
distance
=
rtn
.
xte
*
-
1
}
if
let
dist
=
distance
{
//
if let dist = distance {
// print(debug: "checkPolyline \(dist)")
}
//
}
return
distance
}
...
...
Seilassist/Sailassist/Login/LoginView.swift
View file @
8cec0939
...
...
@@ -37,6 +37,7 @@ struct LoginView: View {
@ObservedObject
var
scannerViewModel
=
ScannerViewModel
()
@EnvironmentObject
var
locationViewModel
:
LocationViewModel
@EnvironmentObject
var
selectedTabModel
:
SelectedTabModel
@EnvironmentObject
var
chatViewModel
:
ChatViewModel
@Binding
var
isLogin
:
Bool
@State
var
isQrRead
:
Bool
=
false
@State
var
viewMode
:
LoginViewMode
=
.
SelectType
...
...
@@ -153,9 +154,7 @@ struct LoginView: View {
}
}
/**
* QRコードでのログイン
*/
//MARK: - QRコードでのログイン
func
LoginCheckQR
()
->
()
{
isProgressView
=
true
loginViewParam
.
shipId
=
Preferences
.
Id
...
...
@@ -167,9 +166,7 @@ struct LoginView: View {
isProgressView
=
false
}
/**
* Autoログイン
*/
//MARK: - Autoログイン
func
LoginCheck
()
->
()
{
let
lastUnixTime
=
Preferences
.
lastLoginDate_Int64
??
0
let
lastDate
=
DateTextLib
.
UnixTime2Date
(
lastUnixTime
)
...
...
@@ -211,6 +208,7 @@ struct LoginView: View {
isLogin
=
true
let
message
=
GetMessage
()
message
.
chatViewModel
=
chatViewModel
message
.
start
()
timer
=
Timer
.
scheduledTimer
(
withTimeInterval
:
TimerInterval
,
repeats
:
true
)
{
_
in
...
...
@@ -269,4 +267,5 @@ fileprivate struct Triangle: Shape{
#Preview {
LoginView
(
isLogin
:
.
constant
(
false
))
.
environmentObject
(
SelectedTabModel
())
.
environmentObject
(
ChatViewModel
())
}
Seilassist/Sailassist/Map/MapRepresentable.swift
View file @
8cec0939
...
...
@@ -63,7 +63,7 @@ struct MapRepresentable: UIViewControllerRepresentable {
//MARK: - 自船を画面中央に表示
if
location
.
focusOwnShip
{
mapVC
.
updateCamera
(
location
:
location
.
location
,
zoomlevel
:
10
.0
)
mapVC
.
updateCamera
(
location
:
location
.
location
,
zoomlevel
:
5
.0
)
}
if
let
mylocation
=
location
.
location
{
...
...
@@ -86,7 +86,9 @@ struct MapRepresentable: UIViewControllerRepresentable {
if
SharingData
.
nga
.
editType
==
EditNgaType
.
registered
{
mapVC
.
updateEditArea
(
remove
:
true
)
SharingData
.
nga
.
editType
=
EditNgaType
.
nonEdit
DispatchQueue
.
main
.
async
{
SharingData
.
nga
.
editType
=
EditNgaType
.
nonEdit
}
}
if
SharingData
.
nga
.
editType
==
EditNgaType
.
deletePoint
{
...
...
Seilassist/Sailassist/Map/Task/View/EcaListMainView.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
struct
EcaListMainView
:
View
{
@ObservedObject
var
taskViewModel
=
TaskViewModel
()
var
body
:
some
View
{
HStack
{
Button
(
action
:
{
taskViewModel
.
viewMode
=
.
FuelSwitching
},
label
:
{
Image
(
"ink_02"
)
})
.
frame
(
width
:
48
,
height
:
48
)
Spacer
()
Text
(
TaskViewMode
.
EcaList
.
title
)
.
font
(
FontStyle
.
TitleL
.
font
)
.
frame
(
height
:
20
)
.
padding
(
.
vertical
,
14
)
Spacer
()
Button
(
action
:
{
},
label
:
{
})
.
frame
(
width
:
48
,
height
:
48
)
}
.
padding
(
EdgeInsets
(
top
:
10
,
leading
:
8
,
bottom
:
13
,
trailing
:
17
))
Divider
()
.
background
(
ColorSet
.
LineColor03
.
color
)
ScrollViewReader
{
proxy
in
ScrollView
(
.
vertical
){
EcaListView
(
taskViewModel
:
taskViewModel
)
}
.
onChange
(
of
:
taskViewModel
.
viewMode
){
newViewMode
in
proxy
.
scrollTo
(
0
,
anchor
:
.
top
)
}
}
Spacer
()
.
frame
(
height
:
55
)
}
}
#Preview {
EcaListMainView
(
taskViewModel
:
TaskViewModel
())
}
Seilassist/Sailassist/Map/Task/View/EcaMenuView.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
struct
EcaMenuView
:
View
{
@ObservedObject
var
taskViewModel
:
TaskViewModel
@ObservedObject
var
ecaData
=
SharingData
.
eca
@Environment(\ .colorScheme)
var
colorScheme
@State
var
isDeleteAlert
:
Bool
=
false
let
deleteEcaArea
=
DeleteEcaArea
()
var
eca
:
RegisteredEca
var
body
:
some
View
{
Menu
{
Text
(
eca
.
name
)
Button
{
taskViewModel
.
edittingEcaArea
=
eca
taskViewModel
.
ecaName
=
eca
.
name
taskViewModel
.
viewMode
=
.
EcaSetting
}
label
:
{
Text
(
"Edit Notice Setting"
)
}
Button
{
taskViewModel
.
edittingEcaArea
=
eca
taskViewModel
.
ecaName
=
eca
.
name
isDeleteAlert
=
true
}
label
:
{
Text
(
"Delete ECA Task"
)
}
}
label
:
{
Image
(
systemName
:
"ellipsis"
)
.
frame
(
width
:
22
,
height
:
22
)
.
opacity
(
eca
.
isRunning
==
true
?
0.2
:
1.0
)
.
foregroundColor
(
colorScheme
==
.
light
?
.
black
:
.
white
)
}
.
disabled
(
eca
.
isRunning
)
.
alert
(
"Delete"
,
isPresented
:
$
isDeleteAlert
)
{
Button
(
"Yes"
)
{
if
let
ecaArea
=
taskViewModel
.
edittingEcaArea
{
var
newData
=
ecaArea
newData
.
isEnable
=
false
newData
.
status
=
EcaState
.
cancel
ecaData
.
editEcaArea
(
key
:
ecaArea
.
areaId
,
value
:
newData
,
type
:
EcaOperation
.
Delete
)
deleteEcaArea
.
start
(
ecaId
:
ecaArea
.
areaId
)
}
taskViewModel
.
edittingEcaArea
=
nil
}
Button
(
"No"
){}
}
message
:
{
Text
(
"Have you finished "
+
taskViewModel
.
ecaName
+
" fuel switching?"
)
}
}
}
Seilassist/Sailassist/Map/Task/View/EcaSettingMainView.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
struct
EcaSettingMainView
:
View
{
@ObservedObject
var
taskViewModel
=
TaskViewModel
()
var
body
:
some
View
{
//タイトルエリア
HStack
{
Button
(
action
:
{
taskViewModel
.
viewMode
=
.
FuelSwitching
},
label
:
{
Image
(
"ink_02"
)
})
.
frame
(
width
:
48
,
height
:
48
)
Spacer
()
Text
(
TaskViewMode
.
EcaSetting
.
title
)
.
font
(
FontStyle
.
TitleL
.
font
)
.
frame
(
height
:
20
)
.
padding
(
.
vertical
,
14
)
Spacer
()
Button
(
action
:
{
},
label
:
{
})
.
frame
(
width
:
48
,
height
:
48
)
}
.
padding
(
EdgeInsets
(
top
:
10
,
leading
:
8
,
bottom
:
13
,
trailing
:
17
))
Divider
()
.
background
(
ColorSet
.
LineColor03
.
color
)
ScrollViewReader
{
proxy
in
ScrollView
(
.
vertical
){
if
let
edittingEcaArea
=
taskViewModel
.
edittingEcaArea
{
EcaSettingView
(
taskViewModel
:
taskViewModel
,
edittingEca
:
edittingEcaArea
)
}
}
.
onChange
(
of
:
taskViewModel
.
viewMode
){
newViewMode
in
proxy
.
scrollTo
(
0
,
anchor
:
.
top
)
}
}
Spacer
()
.
frame
(
height
:
55
)
}
}
#Preview {
EcaSettingMainView
(
taskViewModel
:
TaskViewModel
())
}
Seilassist/Sailassist/Map/Task/View/FuelSwitchingMainView.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
struct
FuelSwitchingMainView
:
View
{
@ObservedObject
var
taskViewModel
=
TaskViewModel
()
var
body
:
some
View
{
HStack
{
Button
(
action
:
{
taskViewModel
.
viewMode
=
.
MenuList
},
label
:
{
Image
(
"ink_02"
)
})
.
frame
(
width
:
48
,
height
:
48
)
Spacer
()
Text
(
TaskViewMode
.
FuelSwitching
.
title
)
.
font
(
FontStyle
.
TitleL
.
font
)
.
frame
(
height
:
20
)
.
padding
(
.
vertical
,
14
)
Spacer
()
Button
(
action
:
{
},
label
:
{
})
.
frame
(
width
:
48
,
height
:
48
)
}
.
padding
(
EdgeInsets
(
top
:
10
,
leading
:
8
,
bottom
:
13
,
trailing
:
17
))
Divider
()
.
background
(
ColorSet
.
LineColor03
.
color
)
//ECAリスト
ScrollViewReader
{
proxy
in
ScrollView
(
.
vertical
){
FuelSwitchingView
(
taskViewModel
:
taskViewModel
)
}
.
onChange
(
of
:
taskViewModel
.
viewMode
){
newViewMode
in
proxy
.
scrollTo
(
0
,
anchor
:
.
top
)
}
}
Spacer
()
.
frame
(
height
:
55
)
}
}
#Preview {
FuelSwitchingMainView
(
taskViewModel
:
TaskViewModel
())
}
Seilassist/Sailassist/Map/Task/View/FuelSwitchingView.swift
View file @
8cec0939
...
...
@@ -4,7 +4,6 @@
//
// Created by Mamoru Sugita on 2023/10/18.
//
import
SwiftUI
struct
FuelSwitchingView
:
View
{
...
...
@@ -96,59 +95,6 @@ struct FuelSwitchingView: View {
}
}
struct
EcaMenuView
:
View
{
@ObservedObject
var
taskViewModel
:
TaskViewModel
@ObservedObject
var
ecaData
=
SharingData
.
eca
@Environment(\ .colorScheme)
var
colorScheme
@State
var
isDeleteAlert
:
Bool
=
false
let
deleteEcaArea
=
DeleteEcaArea
()
var
eca
:
RegisteredEca
var
body
:
some
View
{
Menu
{
Text
(
eca
.
name
)
Button
{
taskViewModel
.
edittingEcaArea
=
eca
taskViewModel
.
ecaName
=
eca
.
name
taskViewModel
.
viewMode
=
.
EcaSetting
}
label
:
{
Text
(
"Edit Notice Setting"
)
}
Button
{
taskViewModel
.
edittingEcaArea
=
eca
taskViewModel
.
ecaName
=
eca
.
name
isDeleteAlert
=
true
}
label
:
{
Text
(
"Delete ECA Task"
)
}
}
label
:
{
Image
(
systemName
:
"ellipsis"
)
.
frame
(
width
:
22
,
height
:
22
)
.
opacity
(
eca
.
isRunning
==
true
?
0.2
:
1.0
)
.
foregroundColor
(
colorScheme
==
.
light
?
.
black
:
.
white
)
}
.
disabled
(
eca
.
isRunning
)
.
alert
(
"Delete"
,
isPresented
:
$
isDeleteAlert
)
{
Button
(
"Yes"
)
{
if
let
ecaArea
=
taskViewModel
.
edittingEcaArea
{
var
newData
=
ecaArea
newData
.
isEnable
=
false
newData
.
status
=
EcaState
.
cancel
ecaData
.
editEcaArea
(
key
:
ecaArea
.
areaId
,
value
:
newData
,
type
:
EcaOperation
.
Delete
)
deleteEcaArea
.
start
(
ecaId
:
ecaArea
.
areaId
)
}
taskViewModel
.
edittingEcaArea
=
nil
}
Button
(
"No"
){}
}
message
:
{
Text
(
"Have you finished "
+
taskViewModel
.
ecaName
+
" fuel switching?"
)
}
}
}
#Preview {
FuelSwitchingView
(
taskViewModel
:
TaskViewModel
())
}
Seilassist/Sailassist/Map/Task/View/MenuMainView.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
struct
MenuMainView
:
View
{
@ObservedObject
var
taskViewModel
=
TaskViewModel
()
var
body
:
some
View
{
VStack
(
spacing
:
0
)
{
headerView
Divider
()
.
background
(
ColorSet
.
LineColor03
.
color
)
ScrollViewReader
{
proxy
in
ScrollView
(
.
vertical
){
VStack
(
spacing
:
16
)
{
Color
.
clear
.
frame
(
height
:
1
)
.
id
(
"top"
)
MenuButton
(
title
:
TaskViewMode
.
FuelSwitching
.
title
,
isEnabled
:
SharingData
.
my
.
isFuelSwitchTask
,
action
:
{
taskViewModel
.
viewMode
=
.
FuelSwitching
}
)
MenuButton
(
title
:
TaskViewMode
.
NgaNotification
.
title
,
isEnabled
:
SharingData
.
my
.
isNga
,
action
:
{
taskViewModel
.
viewMode
=
.
NgaNotification
}
)
}
.
padding
(
.
top
,
16
)
}
.
onChange
(
of
:
taskViewModel
.
viewMode
)
{
_
in
withAnimation
{
proxy
.
scrollTo
(
"top"
,
anchor
:
.
top
)
}
}
}
Spacer
()
.
frame
(
height
:
55
)
}
.
padding
(
EdgeInsets
(
top
:
10
,
leading
:
8
,
bottom
:
13
,
trailing
:
17
))
}
private
var
headerView
:
some
View
{
HStack
{
Spacer
()
Text
(
TaskViewMode
.
MenuList
.
title
)
.
font
(
FontStyle
.
TitleL
.
font
)
.
frame
(
height
:
20
)
.
padding
(
.
vertical
,
14
)
Spacer
()
}
}
}
struct
MenuButton
:
View
{
let
title
:
String
let
isEnabled
:
Bool
let
action
:
()
->
Void
var
body
:
some
View
{
Button
(
action
:
action
)
{
Text
(
title
)
.
font
(
FontStyle
.
DefaultText
.
font
)
.
padding
()
.
frame
(
maxWidth
:
.
infinity
,
minHeight
:
42
)
}
.
foregroundColor
(
ColorSet
.
ButtonText
.
color
)
.
background
(
isEnabled
?
ColorSet
.
PrimaryActiveIcon
.
color
:
ColorSet
.
SecondaryDisable
.
color
)
.
cornerRadius
(
30
)
.
disabled
(
!
isEnabled
)
.
frame
(
width
:
153
)
}
}
#Preview {
MenuMainView
(
taskViewModel
:
TaskViewModel
())
}
Seilassist/Sailassist/Map/Task/View/MenuTaskView.swift
View file @
8cec0939
This diff is collapsed.
Click to expand it.
Seilassist/Sailassist/Map/Task/View/NgaMenuView.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
struct
NgaMenuView
:
View
{
@ObservedObject
var
taskViewModel
:
TaskViewModel
@ObservedObject
var
ngaData
=
SharingData
.
nga
@Environment(\ .colorScheme)
var
colorScheme
@State
var
isDelete
:
Bool
=
false
var
nga
:
RegisteredNga
var
body
:
some
View
{
Menu
{
Text
(
nga
.
name
)
Button
{
ngaData
.
editNga
=
nga
taskViewModel
.
viewMode
=
.
NgaSetting
ngaData
.
editType
=
EditNgaType
.
nonEdit
}
label
:
{
Text
(
"Edit NGA Setting"
)
}
//削除
Button
{
ngaData
.
editNga
=
nga
isDelete
=
true
}
label
:
{
Text
(
"Delete NGA Task"
)
}
.
disabled
(
nga
.
isLock
)
}
label
:
{
Image
(
systemName
:
"ellipsis"
)
.
frame
(
width
:
22
,
height
:
22
)
.
opacity
(
nga
.
isRunning
?
0.2
:
1.0
)
.
foregroundColor
(
colorScheme
==
.
light
?
.
black
:
.
white
)
}
.
disabled
(
nga
.
isRunning
)
.
alert
(
"Delete"
,
isPresented
:
$
isDelete
)
{
Button
(
"Yes"
)
{
if
let
newData
=
ngaData
.
editNga
{
ngaData
.
editNgaArea
(
value
:
newData
,
type
:
NgaOperation
.
Delete
)
}
}
Button
(
"No"
){}
}
message
:
{
if
let
editNga
=
ngaData
.
editNga
{
Text
(
"Delete "
+
editNga
.
name
+
" ?"
)
}
}
}
}
Seilassist/Sailassist/Map/Task/View/NgaNotificationMainView.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
struct
NgaNotificationMainView
:
View
{
@ObservedObject
var
taskViewModel
=
TaskViewModel
()
var
body
:
some
View
{
//タイトルエリア
HStack
{
Button
(
action
:
{
taskViewModel
.
viewMode
=
.
MenuList
},
label
:
{
Image
(
"ink_02"
)
})
.
frame
(
width
:
48
,
height
:
48
)
Spacer
()
Text
(
TaskViewMode
.
NgaNotification
.
title
)
.
font
(
FontStyle
.
TitleL
.
font
)
.
frame
(
height
:
20
)
.
padding
(
.
vertical
,
14
)
Spacer
()
Button
(
action
:
{
},
label
:
{
})
.
frame
(
width
:
48
,
height
:
48
)
}
.
padding
(
EdgeInsets
(
top
:
10
,
leading
:
8
,
bottom
:
13
,
trailing
:
17
))
Divider
()
.
background
(
ColorSet
.
LineColor03
.
color
)
ScrollViewReader
{
proxy
in
ScrollView
(
.
vertical
){
NgaNotificationView
(
taskViewModel
:
taskViewModel
)
}
.
onChange
(
of
:
taskViewModel
.
viewMode
){
newViewMode
in
proxy
.
scrollTo
(
0
,
anchor
:
.
top
)
}
}
Spacer
()
.
frame
(
height
:
55
)
}
}
#Preview {
NgaNotificationMainView
(
taskViewModel
:
TaskViewModel
())
}
Seilassist/Sailassist/Map/Task/View/NgaNotificationView.swift
View file @
8cec0939
...
...
@@ -4,7 +4,6 @@
//
// Created by Mamoru Sugita on 2023/10/18.
//
import
SwiftUI
struct
NgaNotificationView
:
View
{
...
...
@@ -87,54 +86,6 @@ struct NgaNotificationView: View {
}
}
struct
NgaMenuView
:
View
{
@ObservedObject
var
taskViewModel
:
TaskViewModel
@ObservedObject
var
ngaData
=
SharingData
.
nga
@Environment(\ .colorScheme)
var
colorScheme
@State
var
isDelete
:
Bool
=
false
var
nga
:
RegisteredNga
var
body
:
some
View
{
Menu
{
Text
(
nga
.
name
)
Button
{
ngaData
.
editNga
=
nga
taskViewModel
.
viewMode
=
.
NgaSetting
ngaData
.
editType
=
EditNgaType
.
nonEdit
}
label
:
{
Text
(
"Edit NGA Setting"
)
}
//削除
Button
{
ngaData
.
editNga
=
nga
isDelete
=
true
}
label
:
{
Text
(
"Delete NGA Task"
)
}
.
disabled
(
nga
.
isLock
)
}
label
:
{
Image
(
systemName
:
"ellipsis"
)
.
frame
(
width
:
22
,
height
:
22
)
.
opacity
(
nga
.
isRunning
?
0.2
:
1.0
)
.
foregroundColor
(
colorScheme
==
.
light
?
.
black
:
.
white
)
}
.
disabled
(
nga
.
isRunning
)
.
alert
(
"Delete"
,
isPresented
:
$
isDelete
)
{
Button
(
"Yes"
)
{
if
let
newData
=
ngaData
.
editNga
{
ngaData
.
editNgaArea
(
value
:
newData
,
type
:
NgaOperation
.
Delete
)
}
}
Button
(
"No"
){}
}
message
:
{
if
let
editNga
=
ngaData
.
editNga
{
Text
(
"Delete "
+
editNga
.
name
+
" ?"
)
}
}
}
}
#Preview {
NgaNotificationView
(
taskViewModel
:
TaskViewModel
())
}
Seilassist/Sailassist/Map/Task/View/NgaSettingMainView.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
struct
NgaSettingMainView
:
View
{
@ObservedObject
var
taskViewModel
=
TaskViewModel
()
@ObservedObject
var
nga
=
SharingData
.
nga
@ObservedObject
var
map
=
SharingData
.
map
var
body
:
some
View
{
//タイトルエリア
HStack
{
Button
(
action
:
{
if
taskViewModel
.
viewMode
==
.
NgaSetting
&&
nga
.
editType
==
EditNgaType
.
movePoint
{
nga
.
editNga
=
nga
.
moveNga
nga
.
editType
=
EditNgaType
.
addPoint
map
.
isMapFree
=
true
}
else
{
taskViewModel
.
viewMode
=
.
NgaNotification
nga
.
editType
=
EditNgaType
.
registered
}
},
label
:
{
Image
(
"ink_02"
)
})
.
frame
(
width
:
48
,
height
:
48
)
Spacer
()
Text
(
TaskViewMode
.
NgaSetting
.
title
)
.
font
(
FontStyle
.
TitleL
.
font
)
.
frame
(
height
:
20
)
.
padding
(
.
vertical
,
14
)
Spacer
()
Button
(
action
:
{
},
label
:
{
})
.
frame
(
width
:
48
,
height
:
48
)
}
.
padding
(
EdgeInsets
(
top
:
10
,
leading
:
8
,
bottom
:
13
,
trailing
:
17
))
Divider
()
.
background
(
ColorSet
.
LineColor03
.
color
)
ScrollViewReader
{
proxy
in
ScrollView
(
.
vertical
){
NgaSettingView
(
taskViewModel
:
taskViewModel
)
}
.
onChange
(
of
:
taskViewModel
.
viewMode
){
newViewMode
in
proxy
.
scrollTo
(
0
,
anchor
:
.
top
)
}
}
Spacer
()
.
frame
(
height
:
55
)
}
}
#Preview {
NgaSettingMainView
(
taskViewModel
:
TaskViewModel
())
}
Seilassist/Sailassist/SailassistApp.swift
View file @
8cec0939
...
...
@@ -84,6 +84,7 @@ var connection: HubConnection?
class
AppDelegate
:
NSObject
,
UIApplicationDelegate
,
MSNotificationHubDelegate
,
MSInstallationLifecycleDelegate
{
@ObservedObject
var
msg
=
SharingData
.
message
private
var
hubConnectionDelegate
:
HubConnectionDelegate
?
private
var
isReconnecting
=
false
// 再接続中かどうかを管理するフラグ
func
application
(
_
application
:
UIApplication
,
didFinishLaunchingWithOptions
launchOptions
:
[
UIApplication
.
LaunchOptionsKey
:
Any
]?
=
nil
)
->
Bool
{
print
(
debug
:
"called"
)
...
...
@@ -105,7 +106,7 @@ class AppDelegate: NSObject, UIApplicationDelegate ,MSNotificationHubDelegate, M
application
.
registerForRemoteNotifications
()
}
//ユニークな値の生成
//
MARK: -
ユニークな値の生成
if
Preferences
.
DeviceId
==
""
{
Preferences
.
DeviceId
=
UUID
()
.
uuidString
}
...
...
@@ -117,32 +118,37 @@ class AppDelegate: NSObject, UIApplicationDelegate ,MSNotificationHubDelegate, M
.
withAutoReconnect
()
// .withLogging(minLogLevel: .error)
.
withLogging
(
minLogLevel
:
.
debug
)
.
withHubConnectionOptions
(
configureHubConnectionOptions
:
{
options
in
options
.
keepAliveInterval
=
3
0
})
.
withHubConnectionOptions
(
configureHubConnectionOptions
:
{
options
in
options
.
keepAliveInterval
=
2
0
})
.
build
()
if
let
r_connection
=
connection
{
//Chat
r_connection
.
on
(
method
:
"ChatMessage"
,
callback
:
{
(
message
:
ResChatMessage
)
in
self
.
handleChatMessage
(
message
:
message
)
})
setupSignalRHandlers
(
connection
:
r_connection
)
r_connection
.
start
()
}
//Photo / Image
r_connection
.
on
(
method
:
"chatMessage"
,
callback
:
{
(
message
:
ResChatMessage
)
in
self
.
handleChatMessage
(
message
:
message
)
})
return
true
}
r_connection
.
on
(
method
:
"AckMessage"
,
callback
:
{
(
message
:
ResAckMessage
)
in
self
.
handleAckMessage
(
message
:
message
)
})
private
func
setupSignalRHandlers
(
connection
:
HubConnection
)
{
// Chatメッセージ処理
connection
.
on
(
method
:
"ChatMessage"
,
callback
:
{
(
message
:
ResChatMessage
)
in
self
.
handleChatMessage
(
message
:
message
)
})
r_connection
.
on
(
method
:
"ChatMode"
,
callback
:
{
(
message
:
ResChatMode
)
in
self
.
handleChatMode
(
message
:
message
)
})
// Photo / Image
connection
.
on
(
method
:
"chatMessage"
,
callback
:
{
(
message
:
ResChatMessage
)
in
self
.
handleChatMessage
(
message
:
message
)
})
r_connection
.
start
()
}
// Ackメッセージ処理
connection
.
on
(
method
:
"AckMessage"
,
callback
:
{
(
message
:
ResAckMessage
)
in
self
.
handleAckMessage
(
message
:
message
)
})
return
true
// Chatモード処理
connection
.
on
(
method
:
"ChatMode"
,
callback
:
{
(
message
:
ResChatMode
)
in
self
.
handleChatMode
(
message
:
message
)
})
}
func
application
(
_
application
:
UIApplication
,
configurationForConnecting
connectingSceneSession
:
UISceneSession
,
options
:
UIScene
.
ConnectionOptions
)
->
UISceneConfiguration
{
...
...
@@ -162,7 +168,7 @@ class AppDelegate: NSObject, UIApplicationDelegate ,MSNotificationHubDelegate, M
completionHandler
(
.
noData
)
}
// Push通知を受信した時(サイレントプッシュ)
//
MARK: -
Push通知を受信した時(サイレントプッシュ)
func
notificationHub
(
_
notificationHub
:
MSNotificationHub
,
didReceivePushNotification
notification
:
MSNotificationHubMessage
)
{
print
(
debug
:
"called"
)
// let title = notification.title ?? ""
...
...
@@ -188,9 +194,36 @@ class AppDelegate: NSObject, UIApplicationDelegate ,MSNotificationHubDelegate, M
}
private
func
handleChatMessage
(
message
:
ResChatMessage
)
{
print
(
debug
:
"called"
)
let
ownMsg
=
ChatMessage
(
shipId
:
message
.
shipId
,
messageId
:
message
.
messageId
,
type
:
message
.
type
,
time
:
message
.
time
,
location
:
message
.
location
,
from
:
message
.
from
,
fromId
:
message
.
fromId
,
mode
:
message
.
mode
,
message
:
message
.
message
,
stampId
:
message
.
stampId
,
viewer
:
[])
self
.
msg
.
messages
.
append
(
ownMsg
)
DispatchQueue
.
main
.
async
{
print
(
debug
:
"called"
)
let
newMsg
=
ChatMessage
(
shipId
:
message
.
shipId
,
messageId
:
message
.
messageId
,
type
:
message
.
type
,
time
:
message
.
time
,
location
:
message
.
location
,
from
:
message
.
from
,
fromId
:
message
.
fromId
,
mode
:
message
.
mode
,
message
:
message
.
message
,
stampId
:
message
.
stampId
,
viewer
:
[]
)
if
let
index
=
self
.
msg
.
messages
.
lastIndex
(
where
:
{
$0
.
messageId
.
lowercased
()
==
newMsg
.
messageId
.
lowercased
()
})
{
var
updatedMsg
=
newMsg
let
existingMsg
=
self
.
msg
.
messages
[
index
]
if
existingMsg
.
message
!.
hasPrefix
(
"file://"
)
{
updatedMsg
.
message
=
existingMsg
.
message
}
self
.
msg
.
messages
[
index
]
=
updatedMsg
}
else
{
self
.
msg
.
messages
.
append
(
newMsg
)
}
}
}
private
func
handleAckMessage
(
message
:
ResAckMessage
)
{
...
...
@@ -219,7 +252,20 @@ class AppDelegate: NSObject, UIApplicationDelegate ,MSNotificationHubDelegate, M
self
.
msg
.
messages
.
append
(
ownMsg
)
}
//アプリ終了時
//MARK: - Signal-R再接続
func
reconnect
()
{
guard
!
isReconnecting
else
{
return
}
// 再接続中なら何もしない
isReconnecting
=
true
print
(
debug
:
"Attempting to reconnect..."
)
DispatchQueue
.
global
()
.
asyncAfter
(
deadline
:
.
now
()
+
5.0
)
{
[
weak
self
]
in
guard
let
self
=
self
else
{
return
}
connection
?
.
start
()
self
.
isReconnecting
=
false
}
}
//MARK: - アプリ終了時
func
applicationWillTerminate
(
_
aNotification
:
UIApplication
)
{
if
let
r_connection
=
connection
{
r_connection
.
stop
()
...
...
@@ -232,12 +278,14 @@ class AppDelegate: NSObject, UIApplicationDelegate ,MSNotificationHubDelegate, M
func
connectionDidFailToOpen
(
error
:
Error
)
{
print
(
debug
:
"connectionDidFailToOpen:
\(
error
)
"
)
reconnect
()
}
func
connectionDidClose
(
error
:
Error
?)
{
if
let
err
=
error
{
print
(
debug
:
"connectionDidClose:
\(
err
)
"
)
}
reconnect
()
}
func
connectionWillReconnect
(
error
:
Error
)
{
...
...
@@ -253,17 +301,15 @@ class SignalR: NSObject {
@ObservedObject
var
msg
=
SharingData
.
message
func
chatMessage
(
message
:
String
,
completion
:
@escaping
(
_
error
:
Error
?)
->
Void
)
{
print
(
debug
:
"called"
)
var
request
=
ReqMessage
(
shipId
:
Preferences
.
shipId
,
messageId
:
UUID
()
.
uuidString
.
lowercased
())
request
.
type
=
0
//0:テキスト, 1:スタンプ
request
.
time
=
DateTextLib
.
Date2ISO8601Text
(
Date
())
request
.
location
=
2
//1:Shore , 2:Ship
request
.
from
=
Preferences
.
UserName
//投稿者名
request
.
fromId
=
String
(
SharingData
.
my
.
id
)
//ユーザーID
if
SharingData
.
message
.
mode
{
request
.
mode
=
1
}
else
{
request
.
mode
=
0
}
request
.
mode
=
SharingData
.
message
.
mode
?
1
:
0
request
.
message
=
message
request
.
stampId
=
0
//スタンプ番号 0:Fire~
...
...
@@ -299,11 +345,15 @@ class SignalR: NSObject {
let
test
=
connection
.
debugDescription
print
(
debug
:
"Test: Chat Message
\(
test
)
"
)
connection
!.
stop
()
guard
let
connection
=
connection
else
{
return
}
connection
.
stop
()
}
func
startConnection
()
{
connection
!.
start
()
guard
let
connection
=
connection
else
{
return
}
if
connection
.
connectionId
==
nil
{
connection
.
start
()
}
}
}
...
...
@@ -355,6 +405,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
let
arrCategory
=
arrAPS
[
"category"
]
as?
String
??
""
switch
arrCategory
{
case
"chat"
:
print
(
debug
:
"chat"
)
guard
let
arrAlert
=
arrAPS
[
"alert"
]
as?
[
String
:
Any
]
else
{
completionHandler
([[
.
banner
,
.
badge
,
.
sound
]])
return
...
...
Seilassist/Sailassist/ServerSession/SessionUploadImage.swift
View file @
8cec0939
...
...
@@ -20,29 +20,25 @@ class SessionUploadImage : ObservableObject {
func
cancelUpload
()
{
uploadTask
?
.
cancel
()
uploadTask
=
nil
isUploading
=
false
progress
=
0.0
}
/**
* メッセージ
*/
//MARK: - 画像のアップロード
func
requestUploadImage
(
_
uploadImage
:
ReqUploadImage
)
async
throws
->
Data
{
print
(
debug
:
"calld"
)
print
(
debug
:
"call
e
d"
)
guard
!
Calling
else
{
throw
APIError
.
busy
}
Calling
=
true
defer
{
Calling
=
false
}
// リクエストURLの組み立て
guard
let
req_url
=
URL
(
string
:
HttpRequestType
.
UploadImage
.
rawValue
)
else
{
throw
APIError
.
invalidURL
}
let
imageFileName
=
"itemp.jpg"
let
boundary
=
"----------
\(
UUID
()
.
uuidString
)
"
var
httpBody
=
Data
()
func
appendFormField
(
name
:
String
,
value
:
String
)
{
httpBody
.
append
(
"--
\(
boundary
)\r\n
"
.
data
(
using
:
.
utf8
)
!
)
...
...
@@ -57,7 +53,7 @@ class SessionUploadImage : ObservableObject {
appendFormField
(
name
:
"FromId"
,
value
:
uploadImage
.
fromId
)
httpBody
.
append
(
"--
\(
boundary
)\r\n
"
.
data
(
using
:
.
utf8
)
!
)
httpBody
.
append
(
"Content-Disposition: form-data; name=
\"
files
\"
; filename=
\"
\(
imageFileName
)
\"\r\n
"
.
data
(
using
:
.
utf8
)
!
)
httpBody
.
append
(
"Content-Disposition: form-data; name=
\"
files
\"
; filename=
\"
itemp.jpg
\"\r\n
"
.
data
(
using
:
.
utf8
)
!
)
httpBody
.
append
(
"Content-Type: image/jpeg
\r\n\r\n
"
.
data
(
using
:
.
utf8
)
!
)
httpBody
.
append
(
uploadImage
.
files
)
httpBody
.
append
(
"
\r\n
--
\(
boundary
)
--
\r\n
"
.
data
(
using
:
.
utf8
)
!
)
...
...
@@ -65,22 +61,35 @@ class SessionUploadImage : ObservableObject {
var
request
=
URLRequest
(
url
:
req_url
)
request
.
httpMethod
=
"POST"
request
.
setValue
(
"multipart/form-data; boundary=
\(
boundary
)
"
,
forHTTPHeaderField
:
"Content-Type"
)
let
session
=
URLSession
(
configuration
:
.
default
,
delegate
:
UploadProgressDelegate
(
parent
:
self
),
delegateQueue
:
nil
)
isUploading
=
true
uploadTask
=
session
.
uploadTask
(
with
:
request
,
from
:
httpBody
)
uploadTask
?
.
resume
()
let
(
data
,
response
)
=
try
await
session
.
upload
(
for
:
request
,
from
:
httpBody
)
isUploading
=
false
progress
=
0.0
return
try
await
withCheckedThrowingContinuation
{
continuation
in
self
.
uploadTask
=
session
.
uploadTask
(
with
:
request
,
from
:
httpBody
)
{
data
,
response
,
error
in
DispatchQueue
.
main
.
async
{
self
.
isUploading
=
false
self
.
progress
=
0.0
}
if
let
error
=
error
{
continuation
.
resume
(
throwing
:
error
)
return
}
guard
let
httpResponse
=
response
as?
HTTPURLResponse
,
(
200
...
299
)
.
contains
(
httpResponse
.
statusCode
),
let
data
=
data
else
{
continuation
.
resume
(
throwing
:
APIError
.
serverError
)
return
}
continuation
.
resume
(
returning
:
data
)
}
guard
let
httpResponse
=
response
as?
HTTPURLResponse
,
(
200
...
299
)
.
contains
(
httpResponse
.
statusCode
)
else
{
throw
APIError
.
serverError
self
.
uploadTask
?
.
resume
()
}
return
data
}
func
postFormAsync
(
boundary
:
String
,
url
:
URL
,
body
:
Data
)
async
throws
->
Data
{
...
...
@@ -105,8 +114,11 @@ class SessionUploadImage : ObservableObject {
self
.
parent
=
parent
}
func
urlSession
(
_
session
:
URLSession
,
task
:
URLSessionTask
,
didSendBodyData
bytesSent
:
Int64
,
totalBytesSent
:
Int64
,
totalBytesExpectedToSend
:
Int64
)
{
func
urlSession
(
_
session
:
URLSession
,
task
:
URLSessionTask
,
didSendBodyData
bytesSent
:
Int64
,
totalBytesSent
:
Int64
,
totalBytesExpectedToSend
:
Int64
)
{
guard
totalBytesExpectedToSend
>
0
else
{
return
}
DispatchQueue
.
main
.
async
{
self
.
parent
?
.
progress
=
Double
(
totalBytesSent
)
/
Double
(
totalBytesExpectedToSend
)
}
...
...
Seilassist/Sailassist/Tab/CustomTabBar.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
struct
CustomTabBar
:
View
{
@EnvironmentObject
private
var
selectedTabModel
:
SelectedTabModel
@StateObject
private
var
viewModel
=
CustomTabBarViewModel
()
@Environment(\.openURL)
var
openURL
@ObservedObject
var
my
=
SharingData
.
my
@ObservedObject
var
message
=
SharingData
.
message
@ObservedObject
var
location
=
SharingData
.
location
@ObservedObject
var
pushHistory
=
SharingData
.
pushHistory
var
body
:
some
View
{
VStack
(
spacing
:
0
){
Divider
()
HStack
(
spacing
:
0
){
ForEach
(
Tab
.
allCases
,
id
:
\
.
rawValue
)
{
tab
in
VStack
{
ZStack
(
alignment
:
.
bottomTrailing
)
{
if
!
SharingData
.
my
.
isCommunication
&&
tab
==
Tab
.
chat
{
Image
(
"tab_chat_Invalid"
)
.
onTapGesture
{
viewModel
.
handleTabSelection
(
selectedTabModel
:
selectedTabModel
,
tab
:
tab
,
message
:
message
,
history
:
pushHistory
,
location
:
location
,
)
}
}
else
{
Image
(
selectedTabModel
.
activeTab
==
tab
?
tab
.
rawValue
+
"_selected"
:
tab
.
rawValue
)
.
onTapGesture
{
viewModel
.
handleTabSelection
(
selectedTabModel
:
selectedTabModel
,
tab
:
tab
,
message
:
message
,
history
:
pushHistory
,
location
:
location
,
)
}
}
if
tab
==
Tab
.
chat
{
NotificationBadge
(
viewModel
:
viewModel
.
chatBadgeViewModel
,
font
:
FontStyle
.
VersionText
.
font
)
}
if
tab
==
Tab
.
alert
{
NotificationBadge
(
viewModel
:
viewModel
.
alertBadgeViewModel
,
font
:
FontStyle
.
VersionText
.
font
)
}
}
Text
(
tab
.
title
)
.
font
(
.
caption
)
}
.
frame
(
maxWidth
:
.
infinity
,
maxHeight
:
.
infinity
)
.
contentShape
(
Rectangle
())
}
}
.
frame
(
height
:
50
)
}
.
background
(
ColorSet
.
BottomNav
.
color
)
.
modifier
(
ChangeModeAlertModifier
(
isPresented
:
$
selectedTabModel
.
isShowChangeEmrMode
,
currentMode
:
message
.
mode
,
onConfirm
:
{
var
chatMode
=
message
.
mode
chatMode
.
toggle
()
let
signalRService
=
SignalR
()
signalRService
.
chatMode
(
mode
:
chatMode
,
completion
:
responseChatMode
)
message
.
sendInf
=
true
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
3.0
)
{
restartChatMode
()
}
}))
.
modifier
(
LocationAlertModifier
(
isPresented
:
$
selectedTabModel
.
isLocationAlert
))
.
modifier
(
UpdateAlertModifier
(
isPresented
:
$
my
.
isUpDate
,
appStoreURL
:
URL
(
string
:
HttpRequestType
.
AppStore
.
rawValue
)
!
))
.
modifier
(
WarningModeAlertModifier
(
isPresented
:
$
viewModel
.
isModeAlert
,
isSignalrRestert
:
$
viewModel
.
isSignalrRestart
,
onConfirm
:
{
let
signalRService
=
SignalR
()
signalRService
.
startConnection
()
viewModel
.
isSignalrRestart
=
false
}))
//MARK: - チャットTab上の既読マーク
.
onChange
(
of
:
message
.
viewCnt
)
{
newValue
in
viewModel
.
chatBadgeViewModel
.
updateViewCnt
(
to
:
newValue
)
}
//MARK: - アラートTab上の既読マーク
.
onChange
(
of
:
pushHistory
.
viewCnt
)
{
newValue
in
viewModel
.
alertBadgeViewModel
.
updateViewCnt
(
to
:
newValue
)
}
}
//MARK: - Warninngモードレスポンス
func
responseChatMode
(
error
:
Error
?)
{
print
(
debug
:
"responseChatMode"
)
message
.
sendInf
=
false
if
let
e
=
error
{
print
(
debug
:
"Error chat:
\(
e
)
"
)
viewModel
.
isModeAlert
=
true
}
else
{
message
.
changeMode
()
}
}
//MARK: - モード変更レスポンスが無かった場合
func
restartChatMode
()
{
if
message
.
sendInf
{
let
signalRService
=
SignalR
()
signalRService
.
stopConnection
()
Thread
.
sleep
(
forTimeInterval
:
1.0
)
viewModel
.
isSignalrRestart
=
true
}
}
}
Seilassist/Sailassist/Tab/CustomTabBarViewModel.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
import
Foundation
import
UIKit
import
Combine
class
CustomTabBarViewModel
:
ObservableObject
{
@Published
var
chatBadgeViewModel
=
NotificationBadgeViewModel
()
@Published
var
alertBadgeViewModel
=
NotificationBadgeViewModel
()
@Published
var
isMenuTaskViewVisible
=
false
@Published
var
isSignalrRestart
=
false
@Published
var
isModeAlert
=
false
private
let
messageService
=
GetMessage
()
func
handleTabSelection
(
selectedTabModel
:
SelectedTabModel
,
tab
:
Tab
,
message
:
SharingData
.
Message
,
history
:
SharingData
.
PushHistory
,
location
:
SharingData
.
Location
)
{
selectedTabModel
.
activeTab
=
tab
switch
tab
{
case
.
map
:
selectedTabModel
.
isPoppver
.
toggle
()
location
.
focusOwnShip
=
true
if
UIDevice
.
current
.
userInterfaceIdiom
==
.
pad
{
isMenuTaskViewVisible
.
toggle
()
updateMapMenuVisibility
()
}
case
.
chat
:
messageService
.
start
()
messageService
.
readNotification
()
messageService
.
checkUnreadMessages
()
message
.
viewCnt
=
0
chatBadgeViewModel
.
resetBadge
()
resetPopoverAndMenu
(
selectedTabModel
:
selectedTabModel
)
case
.
alert
:
history
.
viewCnt
=
0
alertBadgeViewModel
.
resetBadge
()
resetPopoverAndMenu
(
selectedTabModel
:
selectedTabModel
)
default
:
resetPopoverAndMenu
(
selectedTabModel
:
selectedTabModel
)
}
}
private
func
resetPopoverAndMenu
(
selectedTabModel
:
SelectedTabModel
)
{
selectedTabModel
.
isPoppver
=
false
if
UIDevice
.
current
.
userInterfaceIdiom
==
.
pad
{
isMenuTaskViewVisible
=
false
updateMapMenuVisibility
()
}
}
//MARK: - マップメニュー表示の切り替え
private
func
updateMapMenuVisibility
()
{
let
screenWidth
=
UIScreen
.
main
.
bounds
.
width
let
screenHeight
=
UIScreen
.
main
.
bounds
.
height
let
menuWidth
=
min
(
400
,
screenWidth
*
0.3
)
let
menuHeight
=
screenHeight
*
0.8
if
isMenuTaskViewVisible
{
MenuWindowManager
.
shared
.
showMenuView
(
MenuTaskView
()
.
frame
(
width
:
menuWidth
,
height
:
menuHeight
)
.
background
(
ColorSet
.
BackgroundSecondary
.
color
)
.
cornerRadius
(
15
),
frame
:
CGRect
(
x
:
5
,
y
:
screenHeight
*
0.12
,
width
:
menuWidth
,
height
:
menuHeight
)
)
}
else
{
MenuWindowManager
.
shared
.
hideMenuView
()
}
}
}
class
MenuWindowManager
{
static
let
shared
=
MenuWindowManager
()
private
var
window
:
UIWindow
?
func
showMenuView
<
Content
:
View
>
(
_
content
:
Content
,
frame
:
CGRect
)
{
guard
window
==
nil
else
{
return
}
let
hostingController
=
UIHostingController
(
rootView
:
content
)
if
let
windowScene
=
UIApplication
.
shared
.
connectedScenes
.
first
as?
UIWindowScene
{
var
rFrame
=
frame
rFrame
.
size
.
height
-=
20
let
newWindow
=
UIWindow
(
windowScene
:
windowScene
)
newWindow
.
rootViewController
=
hostingController
newWindow
.
windowLevel
=
.
alert
+
1
newWindow
.
backgroundColor
=
UIColor
.
clear
newWindow
.
makeKeyAndVisible
()
newWindow
.
alpha
=
0.8
newWindow
.
frame
=
rFrame
newWindow
.
layer
.
cornerRadius
=
15
newWindow
.
layer
.
masksToBounds
=
true
self
.
window
=
newWindow
}
}
func
hideMenuView
()
{
guard
let
window
=
window
else
{
return
}
window
.
isHidden
=
true
window
.
rootViewController
=
nil
self
.
window
=
nil
}
}
Seilassist/Sailassist/Tab/MainTabView.swift
deleted
100644 → 0
View file @
d2befa8b
This diff is collapsed.
Click to expand it.
Seilassist/Sailassist/Tab/NotificationBadge.swift
0 → 100644
View file @
8cec0939
import
SwiftUI
class
NotificationBadgeViewModel
:
ObservableObject
{
@Published
private(set)
var
viewCnt
:
Int
=
0
@Published
private(set)
var
hasUnread
:
Bool
=
false
init
(
viewCnt
:
Int
=
0
)
{
self
.
viewCnt
=
viewCnt
}
//MARK: - 未読カウントを更新
func
updateViewCnt
(
to
newValue
:
Int
)
{
viewCnt
=
newValue
hasUnread
=
newValue
>
0
}
//MARK: - バッジをリセット(既読状態にする)
func
resetBadge
()
{
viewCnt
=
0
hasUnread
=
false
}
//MARK: - 現在の未読数を取得
func
getUnreadCount
()
->
Int
{
return
viewCnt
}
}
struct
NotificationBadge
:
View
{
@ObservedObject
var
viewModel
:
NotificationBadgeViewModel
let
font
:
Font
var
body
:
some
View
{
Group
{
if
viewModel
.
getUnreadCount
()
>
0
{
Ellipse
()
.
fill
(
Color
.
red
)
.
frame
(
width
:
12
,
height
:
12
)
.
overlay
(
Text
(
viewModel
.
getUnreadCount
()
<
10
?
"
\(
viewModel
.
getUnreadCount
()
)
"
:
"-"
)
.
font
(
font
)
.
foregroundColor
(
.
white
)
)
}
}
}
}
Seilassist/Sailassist/Tab/View/MainTabView.swift
0 → 100644
View file @
8cec0939
//
// MainTabView.swift
// forShip
//
// Created by Mamoru Sugita on 2023/10/13.
//
import
SwiftUI
enum
Tab
:
String
,
CaseIterable
{
case
map
=
"tab_map"
case
chat
=
"tab_chat"
case
alert
=
"tab_notification"
case
menu
=
"tab_menu"
var
title
:
String
{
switch
self
{
case
.
map
:
return
"Map"
case
.
chat
:
return
"Chat"
case
.
alert
:
return
"Alert"
case
.
menu
:
return
"Menu"
}
}
}
struct
MainTabView
:
View
{
@EnvironmentObject
var
selectedTabModel
:
SelectedTabModel
@EnvironmentObject
private
var
sceneDelegate
:
SceneDelegate
@ObservedObject
var
location
=
SharingData
.
location
@State
private
var
offset
=
CGSize
.
zero
@State
var
isSignout
=
false
var
isTabWindowActive
:
Bool
{
sceneDelegate
.
tabWindow
!=
nil
}
var
isTaskSel
:
Bool
{
selectedTabModel
.
activeTab
==
.
map
}
var
body
:
some
View
{
Group
{
if
UIDevice
.
current
.
userInterfaceIdiom
==
.
phone
{
phoneView
}
else
{
padView
}
}
.
onAppear
{
SharingData
.
location
.
focusOwnShip
=
true
configureTabBarAppearance
()
}
}
private
var
phoneView
:
some
View
{
ZStack
(
alignment
:
.
bottom
)
{
TabView
(
selection
:
$
selectedTabModel
.
activeTab
)
{
mapTab
if
SharingData
.
my
.
isCommunication
{
ChatView
()
.
tag
(
Tab
.
chat
)
}
NotificationView
()
.
tag
(
Tab
.
alert
)
MenuView
(
isSignout
:
$
isSignout
)
.
tag
(
Tab
.
menu
)
}
.
sheet
(
isPresented
:
.
constant
(
isTaskSel
&&
isTabWindowActive
))
{
MenuTaskView
()
.
zIndex
(
0
)
.
presentationDragIndicator
(
.
hidden
)
.
presentationDetents
([
.
height
(
150
),
.
medium
,
.
fraction
(
0.99
)])
.
presentationCornerRadius
(
15
)
.
presentationBackgroundInteraction
(
.
enabled
(
upThrough
:
.
medium
))
.
presentationBackground
(
ColorSet
.
BackgroundSecondary
.
color
)
.
interactiveDismissDisabled
()
}
}
}
private
var
padView
:
some
View
{
ZStack
(
alignment
:
.
bottom
)
{
TabView
(
selection
:
$
selectedTabModel
.
activeTab
)
{
GeometryReader
{
geometry
in
mapTab
.
padding
(
.
bottom
,
geometry
.
safeAreaInsets
.
bottom
+
10
)
}
if
SharingData
.
my
.
isCommunication
{
ChatView
()
.
padding
(
.
bottom
,
50
)
.
tag
(
Tab
.
chat
)
}
NotificationView
()
.
padding
(
.
bottom
,
50
)
.
tag
(
Tab
.
alert
)
MenuView
(
isSignout
:
$
isSignout
)
.
padding
(
.
bottom
,
50
)
.
tag
(
Tab
.
menu
)
}
.
tabViewStyle
(
PageTabViewStyle
(
indexDisplayMode
:
.
never
))
}
}
@ViewBuilder
private
var
mapTab
:
some
View
{
ZStack
{
MapRepresentable
()
MapInformation
()
.
zIndex
(
1
)
}
.
ignoresSafeArea
(
edges
:
.
top
)
.
tag
(
Tab
.
map
)
}
private
func
configureTabBarAppearance
()
{
let
appearance
=
UITabBarAppearance
()
appearance
.
backgroundColor
=
.
clear
UITabBar
.
appearance
()
.
scrollEdgeAppearance
=
appearance
UITabBar
.
appearance
()
.
standardAppearance
=
appearance
}
}
#Preview {
MainTabView
()
.
environmentObject
(
SelectedTabModel
())
.
environmentObject
(
SceneDelegate
())
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment