mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Messages page layout mockup with working versions for the phone and iPad
This commit is contained in:
parent
029ac8556f
commit
05a07b3ba7
8 changed files with 135 additions and 127 deletions
|
|
@ -15,8 +15,8 @@
|
|||
DD47E3D226F1210600029299 /* HelperFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D126F1210600029299 /* HelperFunctions.swift */; };
|
||||
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D526F17ED900029299 /* CircleText.swift */; };
|
||||
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D826F3093800029299 /* MessageBubble.swift */; };
|
||||
DD47E3DB26F3901B00029299 /* MessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DA26F3901A00029299 /* MessageList.swift */; };
|
||||
DD47E3DD26F390A000029299 /* MessageDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DC26F390A000029299 /* MessageDetail.swift */; };
|
||||
DD47E3DB26F3901B00029299 /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DA26F3901A00029299 /* Channels.swift */; };
|
||||
DD47E3DD26F390A000029299 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DC26F390A000029299 /* Messages.swift */; };
|
||||
DD47E3DF26F39D9F00029299 /* MyInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */; };
|
||||
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AE626F6B38600ABCC23 /* Connect.swift */; };
|
||||
DD836AED26F858F900ABCC23 /* MeshData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AEC26F858F900ABCC23 /* MeshData.swift */; };
|
||||
|
|
@ -72,8 +72,8 @@
|
|||
DD47E3D126F1210600029299 /* HelperFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperFunctions.swift; sourceTree = "<group>"; };
|
||||
DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = "<group>"; };
|
||||
DD47E3D826F3093800029299 /* MessageBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubble.swift; sourceTree = "<group>"; };
|
||||
DD47E3DA26F3901A00029299 /* MessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageList.swift; sourceTree = "<group>"; };
|
||||
DD47E3DC26F390A000029299 /* MessageDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDetail.swift; sourceTree = "<group>"; };
|
||||
DD47E3DA26F3901A00029299 /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = "<group>"; };
|
||||
DD47E3DC26F390A000029299 /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = "<group>"; };
|
||||
DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoModel.swift; sourceTree = "<group>"; };
|
||||
DD836AE626F6B38600ABCC23 /* Connect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connect.swift; sourceTree = "<group>"; };
|
||||
DD836AEC26F858F900ABCC23 /* MeshData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshData.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -278,8 +278,8 @@
|
|||
DDC2E18B26CE25A70042C5E4 /* Messages */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD47E3DA26F3901A00029299 /* MessageList.swift */,
|
||||
DD47E3DC26F390A000029299 /* MessageDetail.swift */,
|
||||
DD47E3DA26F3901A00029299 /* Channels.swift */,
|
||||
DD47E3DC26F390A000029299 /* Messages.swift */,
|
||||
);
|
||||
path = Messages;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -450,7 +450,7 @@
|
|||
DDAF8C5F26ED09B50058C060 /* radioconfig.pb.swift in Sources */,
|
||||
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */,
|
||||
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */,
|
||||
DD47E3DB26F3901B00029299 /* MessageList.swift in Sources */,
|
||||
DD47E3DB26F3901B00029299 /* Channels.swift in Sources */,
|
||||
DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */,
|
||||
DD23A51326FEF5D500D9B90C /* MessageData.swift in Sources */,
|
||||
DD836AED26F858F900ABCC23 /* MeshData.swift in Sources */,
|
||||
|
|
@ -471,7 +471,7 @@
|
|||
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */,
|
||||
DDAF8C6726ED0C8C0058C060 /* remote_hardware.pb.swift in Sources */,
|
||||
DDAF8C6526ED0A490058C060 /* channel.pb.swift in Sources */,
|
||||
DD47E3DD26F390A000029299 /* MessageDetail.swift in Sources */,
|
||||
DD47E3DD26F390A000029299 /* Messages.swift in Sources */,
|
||||
DDC2E15826CE248E0042C5E4 /* MeshtasticClientApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -641,7 +641,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.15;
|
||||
MARKETING_VERSION = 1.16;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
|
|
@ -668,7 +668,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.15;
|
||||
MARKETING_VERSION = 1.16;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ class MessageData: ObservableObject {
|
|||
func load() {
|
||||
DispatchQueue.global(qos: .background).async { [weak self] in
|
||||
guard let data = try? Data(contentsOf: Self.fileURL) else {
|
||||
#if DEBUG
|
||||
//#if DEBUG
|
||||
DispatchQueue.main.async {
|
||||
self?.messages = MessageModel.data
|
||||
}
|
||||
#endif
|
||||
//#endif
|
||||
return
|
||||
}
|
||||
guard let messageList = try? JSONDecoder().decode([MessageModel].self, from: data) else {
|
||||
|
|
|
|||
|
|
@ -45,8 +45,18 @@ extension MessageModel {
|
|||
[
|
||||
// Put dev test data here
|
||||
MessageModel(messageId: 3773493287, messageTimeStamp: 1632407404, fromUserId: 4064715620, toUserId: 4294967295, fromUserLongName: "TLORA V1 #1", toUserLongName: "Unknown 1", fromUserShortName: "T#", toUserShortName: "U1", receivedACK: false, messagePayload: "I sent a super great message with amazing text", direction: "received"),
|
||||
MessageModel(messageId: 3773493338, messageTimeStamp: 1632643652, fromUserId: 2930161432, toUserId: 4294967295, fromUserLongName: "TBEAM ARMY GREEN", toUserLongName: "Unknown 1", fromUserShortName: "TAG", toUserShortName: "U1", receivedACK: false, messagePayload: "It was the best message", direction: "received"),
|
||||
MessageModel(messageId: 3773493338, messageTimeStamp: 1632643652, fromUserId: 2930161432, toUserId: 4294967295, fromUserLongName: "TBEAM ARMY GREEN", toUserLongName: "Unknown 1", fromUserShortName: "TAG", toUserShortName: "U1", receivedACK: false, messagePayload: "SwiftUI is great, but it has been lacking of specific native controls, even though that gets much better year by year. One of them was the text view. When SwiftUI was first released, it had no native equivalent of the text view; implementing a custom UIViewRepresentable type to contain UITextView was the only way to go. But since iOS 14, SwiftUI introduces TextEditor, a brand new view to write multi-line text.", direction: "received")
|
||||
MessageModel(messageId: 3773493338, messageTimeStamp: 1632643652, fromUserId: 2930161432, toUserId: 4294967295, fromUserLongName: "TBEAM ARMY GREEN", toUserLongName: "Unknown 1", fromUserShortName: "T#", toUserShortName: "U1", receivedACK: false, messagePayload: "It was the best message", direction: "received"),
|
||||
MessageModel(messageId: 3773493338, messageTimeStamp: 1632643652, fromUserId: 2930161432, toUserId: 4294967295, fromUserLongName: "TBEAM ARMY GREEN", toUserLongName: "Unknown 1", fromUserShortName: "TAG", toUserShortName: "U1", receivedACK: false, messagePayload: "SwiftUI is great, but it has been lacking of specific native controls, even though that gets much better year by year. One of them was the text view. When SwiftUI was first released, it had no native ", direction: "received"),
|
||||
MessageModel(messageId: 3773493338, messageTimeStamp: 1632643652, fromUserId: 2930161432, toUserId: 4294967295, fromUserLongName: "TBEAM ARMY GREEN", toUserLongName: "Unknown 1", fromUserShortName: "TAG", toUserShortName: "U1", receivedACK: false, messagePayload: "One of them was the text view. When SwiftUI was first released, it had no native equivalent of the text view; implementing a custom UIViewRepresentable type to contain UITextView was the only way to g", direction: "received"),
|
||||
MessageModel(messageId: 3773493338, messageTimeStamp: 1632407404, fromUserId: 2930161432, toUserId: 4294967295, fromUserLongName: "TBEAM ARMY GREEN", toUserLongName: "Unknown 1", fromUserShortName: "TAG", toUserShortName: "U1", receivedACK: false, messagePayload: "One of them was the text view. When SwiftUI was first released, it had no native equivalent of the text view; implementing a custom UIViewRepresentable type to contain UITextView was the only way to g", direction: "received"),
|
||||
|
||||
MessageModel(messageId: 3773493338, messageTimeStamp: 1632643652, fromUserId: 2930161432, toUserId: 4294967295, fromUserLongName: "TBEAM ARMY GREEN", toUserLongName: "Unknown 1", fromUserShortName: "GVH", toUserShortName: "U1", receivedACK: false, messagePayload: "yo", direction: "received"),
|
||||
|
||||
MessageModel(messageId: 3773493338, messageTimeStamp: 1632407404, fromUserId: 2930161432, toUserId: 4294967295, fromUserLongName: "TBEAM ARMY GREEN", toUserLongName: "Unknown 1", fromUserShortName: "GVH", toUserShortName: "U1", receivedACK: false, messagePayload: "yo", direction: "received")
|
||||
|
||||
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ struct ContentView: View {
|
|||
var body: some View {
|
||||
|
||||
TabView(selection: $selection) {
|
||||
MessageList()
|
||||
Channels()
|
||||
.tabItem {
|
||||
Label("Messages", systemImage: "text.bubble")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ struct MessageBubble: View {
|
|||
CircleText(text: shortName, color: isCurrentUser ? Color.blue : Color(.darkGray)).padding(.all, 5)
|
||||
VStack (alignment: .leading) {
|
||||
Text(contentMessage)
|
||||
.textSelection(.enabled)
|
||||
.padding(10)
|
||||
.foregroundColor(.white)
|
||||
.background(isCurrentUser ? Color.blue : Color(.darkGray))
|
||||
|
|
|
|||
45
MeshtasticClient/Views/Messages/Channels.swift
Normal file
45
MeshtasticClient/Views/Messages/Channels.swift
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import CoreBluetooth
|
||||
|
||||
struct Channels: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
|
||||
GeometryReader { bounds in
|
||||
|
||||
NavigationLink(destination: Messages()) {
|
||||
|
||||
List{
|
||||
|
||||
HStack {
|
||||
|
||||
Image(systemName: "dial.max.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 70, height: 70)
|
||||
.foregroundColor(Color.blue)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.padding(.trailing)
|
||||
|
||||
Text("Primary")
|
||||
.font(.largeTitle)
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Channels")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageList_Previews: PreviewProvider {
|
||||
static let meshData = MeshData()
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
Channels()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import CoreBluetooth
|
||||
|
||||
struct MessageList: View {
|
||||
|
||||
@State var typingMessage: String = ""
|
||||
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
|
||||
GeometryReader { bounds in
|
||||
|
||||
NavigationLink(destination: MessageDetail()) {
|
||||
|
||||
List{
|
||||
|
||||
HStack {
|
||||
|
||||
Image(systemName: "dial.max.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: bounds.size.width / 7, height: bounds.size.height / 7)
|
||||
.foregroundColor(Color.blue)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.padding(.trailing)
|
||||
|
||||
Text("Primary")
|
||||
.font(.largeTitle)
|
||||
}.padding([.leading, .trailing])
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Message Channels")
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.longName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") ?? "Unknown")
|
||||
|
||||
}
|
||||
)
|
||||
}.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageList_Previews: PreviewProvider {
|
||||
static let meshData = MeshData()
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
MessageList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage() {
|
||||
//chatHelper.sendMessage(Message(content: typingMessage, user: DataSource.secondUser))
|
||||
// typingMessage = ""
|
||||
}
|
||||
|
|
@ -2,20 +2,24 @@ import SwiftUI
|
|||
import MapKit
|
||||
import CoreLocation
|
||||
|
||||
struct MessageDetail: View {
|
||||
struct Messages: View {
|
||||
|
||||
enum Field: Hashable {
|
||||
case messageText
|
||||
}
|
||||
|
||||
// Keyboard State
|
||||
@State var typingMessage: String = ""
|
||||
@State private var totalBytes = 0
|
||||
@State private var lastTypingMessage = ""
|
||||
@FocusState private var focusedField: Field?
|
||||
@ObservedObject var messageData: MessageData = MessageData()
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@Namespace var topId
|
||||
@Namespace var bottomId
|
||||
|
||||
// Message Data and Bluetooth
|
||||
@ObservedObject var messageData: MessageData = MessageData()
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
var body: some View {
|
||||
|
||||
GeometryReader { bounds in
|
||||
|
|
@ -25,10 +29,8 @@ struct MessageDetail: View {
|
|||
ScrollViewReader { scrollView in
|
||||
|
||||
ScrollView {
|
||||
Text("Hidden Top Anchor")
|
||||
.hidden()
|
||||
.frame(height: 0)
|
||||
.id(topId)
|
||||
|
||||
Text("Hidden Top Anchor").hidden().frame(height: 0).id(topId)
|
||||
|
||||
ForEach(messageData.messages.sorted(by: { $0.messageTimestamp < $1.messageTimestamp })) { message in
|
||||
|
||||
|
|
@ -36,68 +38,80 @@ struct MessageDetail: View {
|
|||
}
|
||||
.onAppear(perform: { scrollView.scrollTo(bottomId) } )
|
||||
|
||||
Text("Hidden Bottom Anchor")
|
||||
.hidden()
|
||||
.frame(height: 0)
|
||||
.id(bottomId)
|
||||
Text("Hidden Bottom Anchor").hidden().frame(height: 0).id(bottomId)
|
||||
}
|
||||
.padding([.top, .leading])
|
||||
.padding(.horizontal)
|
||||
}
|
||||
HStack {
|
||||
|
||||
HStack (alignment: .top) {
|
||||
|
||||
ZStack {
|
||||
|
||||
TextEditor(text: $typingMessage)
|
||||
.onChange(of: typingMessage, perform: { value in
|
||||
|
||||
let size = value.utf8.count
|
||||
totalBytes = size
|
||||
if totalBytes <= 200 {
|
||||
// Allow the user to type
|
||||
lastTypingMessage = typingMessage
|
||||
}
|
||||
else {
|
||||
// Set the message back and remove the bytes over the count
|
||||
self.typingMessage = lastTypingMessage
|
||||
}
|
||||
})
|
||||
.keyboardType(.default)
|
||||
.padding(.horizontal, 8)
|
||||
.focused($focusedField, equals: .messageText)
|
||||
.multilineTextAlignment(.leading)
|
||||
.frame(minHeight: bounds.size.height / 4, maxHeight: bounds.size.height / 4)
|
||||
|
||||
Text(typingMessage).opacity(0).padding(.all, 0)
|
||||
|
||||
}
|
||||
.overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1))
|
||||
.padding(.bottom, 15)
|
||||
|
||||
Button(action: sendMessage) {
|
||||
Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.blue)
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.all, 15)
|
||||
HStack (alignment: .top ) {
|
||||
|
||||
if focusedField != nil {
|
||||
Button("Dismiss Keyboard") {
|
||||
focusedField = nil
|
||||
}
|
||||
.fixedSize()
|
||||
.frame(height: 15, alignment: .center)
|
||||
.padding(.top, 10)
|
||||
.font(.subheadline)
|
||||
Spacer()
|
||||
ProgressView("Bytes: \(totalBytes) / 200", value: Double(totalBytes), total: 200)
|
||||
.frame(width: 130)
|
||||
.padding(.bottom, 7)
|
||||
.font(.subheadline)
|
||||
.accentColor(Color.blue)
|
||||
}
|
||||
}
|
||||
HStack (alignment: .top) {
|
||||
|
||||
ZStack {
|
||||
|
||||
TextEditor(text: $typingMessage)
|
||||
.onChange(of: typingMessage, perform: { value in
|
||||
let size = value.utf8.count
|
||||
if size >= 200 {
|
||||
print("too big!")
|
||||
}
|
||||
print(size)
|
||||
})
|
||||
.padding(.horizontal)
|
||||
.focused($focusedField, equals: .messageText)
|
||||
.multilineTextAlignment(.leading)
|
||||
.frame(minHeight: 120, maxHeight: 120)
|
||||
|
||||
|
||||
Text(typingMessage).opacity(0).padding(.all, 2)
|
||||
|
||||
}
|
||||
.overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 2))
|
||||
.padding(.top)
|
||||
|
||||
Button(action: sendMessage) {
|
||||
Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.blue)
|
||||
}
|
||||
.padding(.top)
|
||||
|
||||
}.padding([.leading, .bottom])
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
.navigationTitle("CHANNEL - Primary")
|
||||
.navigationTitle("Channel - Primary")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.longName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") ?? "Unknown")
|
||||
|
||||
}
|
||||
)
|
||||
.onAppear{
|
||||
.onAppear {
|
||||
messageData.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
func sendMessage() {
|
||||
//chatHelper.sendMessage(Message(content: typingMessage, user: DataSource.secondUser))
|
||||
// typingMessage = ""
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue