Messages page layout mockup with working versions for the phone and iPad

This commit is contained in:
Garth Vander Houwen 2021-09-27 11:51:10 -07:00
parent 029ac8556f
commit 05a07b3ba7
8 changed files with 135 additions and 127 deletions

View file

@ -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;

View file

@ -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 {

View file

@ -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")
]
}
}

View file

@ -17,7 +17,7 @@ struct ContentView: View {
var body: some View {
TabView(selection: $selection) {
MessageList()
Channels()
.tabItem {
Label("Messages", systemImage: "text.bubble")
.symbolRenderingMode(.hierarchical)

View file

@ -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))

View 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()
}
}
}

View file

@ -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 = ""
}

View file

@ -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 = ""
}