22 Commits

Author SHA1 Message Date
severin.memmishofer
9bf4ffe51d Own Feed now gets sent with WANT-Vector and the receiving device compares with it's own feeds, if it has that person in it's friends list, and prints to the console, how much it is behind 2023-07-15 17:49:29 +02:00
severin.memmishofer
1582f57eab Friends and their log-sequence Numbers now get listed when receiving WANT-Vector 2023-07-15 16:18:16 +02:00
severin.memmishofer
653c2068e7 Bluetooth now correctly transmits the WANT-Vector and it gets displayed on the other device 2023-07-14 16:08:05 +02:00
severin.memmishofer
9b53e1d3e3 Added Debug- Print Statements 2023-07-13 18:50:28 +02:00
Sebastian Lenzlinger
8d7d6dd8ea Add Read from Characteristic at Peripheral 2023-07-13 18:27:58 +02:00
severin.memmishofer
52c6083967 Minor changes to BT 2023-07-13 17:41:18 +02:00
Sebastian Lenzlinger
5e6c77c64d Add Button to send WANT_msg vector (Implementation Ongoing) 2023-07-13 17:18:10 +02:00
severin.memmishofer
d2b578dbad Added BT Controller & Peripheral as EnvironmentObjects 2023-07-13 16:00:02 +02:00
Sebastian Lenzlinger
2ca1465ce9 Bug Fix by renaming 2023-07-13 15:07:57 +02:00
severin.memmishofer
7cafbb94c2 Bug fixes 2023-07-13 15:04:40 +02:00
severin.memmishofer
b1848da797 Fixed Save Button for new Feed Entries 2023-07-13 14:39:17 +02:00
severin.memmishofer
b1dc6bc473 Added LogEntryView 2023-07-13 12:13:10 +02:00
severin.memmishofer
6507b50666 Added FeedCardView 2023-07-12 17:58:21 +02:00
Sebastian Lenzlinger
fba4ed65e9 Add error handling to loading unexisting files 2023-07-12 17:57:07 +02:00
Sebastian Lenzlinger
8f4ecf0cb2 Improve Persistence Handling 2023-07-12 17:01:45 +02:00
Sebastian Lenzlinger
d218e9b600 Add persistent storage 2023-07-12 14:55:45 +02:00
Sebastian Lenzlinger
232d007686 Merge remote-tracking branch 'refs/remotes/origin/UIRefinementsSL' 2023-07-12 11:17:18 +02:00
severin.memmishofer
8720640f8c Changed NavigationView to NavigationStack for better design on iPad 2023-07-12 11:13:40 +02:00
Sebastian Lenzlinger
216cdac025 Merge remote-tracking branch 'refs/remotes/origin/UIRefinementsSL' 2023-07-12 11:02:13 +02:00
Sebastian Lenzlinger
46b048472c Update gitignore 2023-07-12 11:01:58 +02:00
severin.memmishofer
4b24048477 Fixed some graphical Issues, like the toolbar labels and the navigation title 2023-07-12 10:19:43 +02:00
Sebastian Lenzlinger
5e199f41f8 Refactor Navigation Toolbar into Separate View 2023-07-11 18:22:58 +02:00
21 changed files with 681 additions and 128 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
xcuserdata
*.xcuserdatad
.DS_STORE
SLAppDevel.developerprofile

View File

@@ -20,15 +20,18 @@
96BD33132A5C400B007A6E53 /* FeedListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BD33122A5C400B007A6E53 /* FeedListView.swift */; };
96BD33162A5C403C007A6E53 /* PeeringView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BD33152A5C403C007A6E53 /* PeeringView.swift */; };
F581F59B2A5AE72F0081C383 /* BluetoothController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F581F59A2A5AE72F0081C383 /* BluetoothController.swift */; };
F5847B622A599BF4009E28D4 /* Body.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5847B612A599BF4009E28D4 /* Body.swift */; };
F5847B622A599BF4009E28D4 /* Bodyy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5847B612A599BF4009E28D4 /* Bodyy.swift */; };
F5847B642A599CC3009E28D4 /* LogEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5847B632A599CC3009E28D4 /* LogEntry.swift */; };
F5847B662A599EA4009E28D4 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5847B652A599EA4009E28D4 /* Feed.swift */; };
F5847B6A2A59AB24009E28D4 /* FeedStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5847B692A59AB24009E28D4 /* FeedStore.swift */; };
F58EB2D02A5590E800E22DA6 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = F58EB2CF2A5590E800E22DA6 /* README.md */; };
F59375722A5FF344001FA46A /* FeedDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59375712A5FF344001FA46A /* FeedDetailView.swift */; };
F5A4B1212A5D4D1F00F5AE01 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5A4B1202A5D4D1F00F5AE01 /* SettingsView.swift */; };
F5A4B1232A5D5F8B00F5AE01 /* BTPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5A4B1222A5D5F8B00F5AE01 /* BTPeripheral.swift */; };
F5A4B1252A5D7A8D00F5AE01 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5A4B1242A5D7A8D00F5AE01 /* DataStore.swift */; };
F5A4B1272A5D861E00F5AE01 /* SettingsEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5A4B1262A5D861E00F5AE01 /* SettingsEditView.swift */; };
F5F1419C2A5EFA3600C81B1A /* LogEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F1419B2A5EFA3600C81B1A /* LogEntryView.swift */; };
F5F1419E2A5EFA4700C81B1A /* FeedCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F1419D2A5EFA4700C81B1A /* FeedCardView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -65,15 +68,18 @@
96BD33122A5C400B007A6E53 /* FeedListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListView.swift; sourceTree = "<group>"; };
96BD33152A5C403C007A6E53 /* PeeringView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeeringView.swift; sourceTree = "<group>"; };
F581F59A2A5AE72F0081C383 /* BluetoothController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothController.swift; sourceTree = "<group>"; };
F5847B612A599BF4009E28D4 /* Body.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Body.swift; sourceTree = "<group>"; };
F5847B612A599BF4009E28D4 /* Bodyy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bodyy.swift; sourceTree = "<group>"; };
F5847B632A599CC3009E28D4 /* LogEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogEntry.swift; sourceTree = "<group>"; };
F5847B652A599EA4009E28D4 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
F5847B692A59AB24009E28D4 /* FeedStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedStore.swift; sourceTree = "<group>"; };
F58EB2CF2A5590E800E22DA6 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
F59375712A5FF344001FA46A /* FeedDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedDetailView.swift; sourceTree = "<group>"; };
F5A4B1202A5D4D1F00F5AE01 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
F5A4B1222A5D5F8B00F5AE01 /* BTPeripheral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPeripheral.swift; sourceTree = "<group>"; };
F5A4B1242A5D7A8D00F5AE01 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = "<group>"; };
F5A4B1262A5D861E00F5AE01 /* SettingsEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsEditView.swift; sourceTree = "<group>"; };
F5F1419B2A5EFA3600C81B1A /* LogEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogEntryView.swift; sourceTree = "<group>"; };
F5F1419D2A5EFA4700C81B1A /* FeedCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedCardView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -130,14 +136,14 @@
96454F1C2A558EBC0040BEBD /* ContentView.swift */,
96BD33112A5C3FFC007A6E53 /* Views */,
F581F59A2A5AE72F0081C383 /* BluetoothController.swift */,
F5A4B1222A5D5F8B00F5AE01 /* BTPeripheral.swift */,
96454F1E2A558EBD0040BEBD /* Assets.xcassets */,
96454F202A558EBD0040BEBD /* Preview Content */,
F5847B612A599BF4009E28D4 /* Body.swift */,
F5847B612A599BF4009E28D4 /* Bodyy.swift */,
F5847B632A599CC3009E28D4 /* LogEntry.swift */,
F5847B652A599EA4009E28D4 /* Feed.swift */,
F5847B692A59AB24009E28D4 /* FeedStore.swift */,
96BD330D2A5C254B007A6E53 /* TextApp.swift */,
F5A4B1222A5D5F8B00F5AE01 /* BTPeripheral.swift */,
F5A4B1242A5D7A8D00F5AE01 /* DataStore.swift */,
);
path = RippleChat;
@@ -176,6 +182,9 @@
96BD33152A5C403C007A6E53 /* PeeringView.swift */,
F5A4B1202A5D4D1F00F5AE01 /* SettingsView.swift */,
F5A4B1262A5D861E00F5AE01 /* SettingsEditView.swift */,
F5F1419B2A5EFA3600C81B1A /* LogEntryView.swift */,
F5F1419D2A5EFA4700C81B1A /* FeedCardView.swift */,
F59375712A5FF344001FA46A /* FeedDetailView.swift */,
);
path = Views;
sourceTree = "<group>";
@@ -312,7 +321,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F5847B622A599BF4009E28D4 /* Body.swift in Sources */,
F5847B622A599BF4009E28D4 /* Bodyy.swift in Sources */,
96BD33162A5C403C007A6E53 /* PeeringView.swift in Sources */,
F5847B662A599EA4009E28D4 /* Feed.swift in Sources */,
96BD33132A5C400B007A6E53 /* FeedListView.swift in Sources */,
@@ -320,8 +329,11 @@
96BD33102A5C27B0007A6E53 /* NewFeedEntryView.swift in Sources */,
F5A4B1232A5D5F8B00F5AE01 /* BTPeripheral.swift in Sources */,
F5A4B1252A5D7A8D00F5AE01 /* DataStore.swift in Sources */,
F5F1419C2A5EFA3600C81B1A /* LogEntryView.swift in Sources */,
F5847B6A2A59AB24009E28D4 /* FeedStore.swift in Sources */,
F5A4B1272A5D861E00F5AE01 /* SettingsEditView.swift in Sources */,
F59375722A5FF344001FA46A /* FeedDetailView.swift in Sources */,
F5F1419E2A5EFA4700C81B1A /* FeedCardView.swift in Sources */,
F581F59B2A5AE72F0081C383 /* BluetoothController.swift in Sources */,
96454F1D2A558EBC0040BEBD /* ContentView.swift in Sources */,
F5A4B1212A5D4D1F00F5AE01 /* SettingsView.swift in Sources */,
@@ -516,7 +528,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"RippleChat/Preview Content\"";
DEVELOPMENT_TEAM = B5S58UWR64;
DEVELOPMENT_TEAM = GN2B48NJ47;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Allow for bluetooth use";
@@ -531,7 +543,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = unibas.inetsec.RippleChat;
PRODUCT_BUNDLE_IDENTIFIER = unibas.inetsec.RippleChat1;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;

View File

@@ -10,6 +10,8 @@ import CoreBluetooth
class BluetoothPeripheral: NSObject, ObservableObject {
@Published var incomingMsg: String = ""
@Published var wantVector: WantMessage = WantMessage()
private var peripheralManager: CBPeripheralManager?
let BLE_SERVICE_UUID = CBUUID(string: "6e400001-7646-4b5b-9a50-71becce51558")
@@ -21,64 +23,101 @@ class BluetoothPeripheral: NSObject, ObservableObject {
}
}
// TODO: Change variable names, etc...
extension BluetoothPeripheral: CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
switch peripheral.state {
case .unknown:
print("Bluetooth Device is UNKNOWN")
print("BT Device is UNKNOWN")
case .unsupported:
print("Bluetooth Device is UNSUPPORTED")
print("BT Device is UNSUPPORTED")
case .unauthorized:
print("Bluetooth Device is UNAUTHORIZED")
print("BT Device is UNAUTHORIZED")
case .resetting:
print("Bluetooth Device is RESETTING")
print("BT Device is RESETTING")
case .poweredOff:
print("Bluetooth Device is POWERED OFF")
print("BT Device is POWERED OFF")
case .poweredOn:
print("Bluetooth Device is POWERED ON")
addServices()
print("BT Device is POWERED ON")
addBTService()
@unknown default:
fatalError()
}
}
func addServices() {
let myCharacteristic = CBMutableCharacteristic(type: BLE_CHARACTERISTIC_UUID_RX, properties: [.read, .write, .notify], value: nil, permissions: [.readable])
// 2. Create instance of CBMutableService
func addBTService() {
let myCharacteristic = CBMutableCharacteristic(type: BLE_CHARACTERISTIC_UUID_RX, properties: [.read, .write, .notify, .writeWithoutResponse], value: nil, permissions: [.readable, .writeable])
let myService = CBMutableService(type: BLE_SERVICE_UUID, primary: true)
// 3. Add characteristics to the service
myService.characteristics = [myCharacteristic]
// 4. Add service to peripheralManager
peripheralManager!.add(myService)
// 5. Start advertising
peripheralManager!.startAdvertising([CBAdvertisementDataLocalNameKey : "RippleChat", CBAdvertisementDataServiceUUIDsKey : BLE_SERVICE_UUID])
print("Started Advertising")
}
// func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
//
// messageLabel.text = "Data getting Read"
// readValueLabel.text = value
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
peripheralManager!.startAdvertising([CBAdvertisementDataLocalNameKey : "RippleChat", CBAdvertisementDataServiceUUIDsKey: [BLE_SERVICE_UUID]])
print("Started Advertising")
if(peripheralManager?.delegate == nil) {
print("peripheral is nil")
} else {
print("peripheral is not nil")
}
}
func discoverServices(_ serviceUUIDs: [CBUUID]?) {
print("test Peripheral")
print("Discovering services... \(String(describing: serviceUUIDs))")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("Discovering Services Peripheral")
// print("*******************************************************")
//
// // Perform your additional operations here
//
// }
//
// func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
//
// messageLabel.text = "Writing Data"
//
// if let value = requests.first?.value {
// writeValueLabel.text = value.hexEncodedString()
// //Perform here your additional operations on the data you get
// }
// }
// if ((error) != nil) {
// print("Error discovering services: \(error!.localizedDescription)")
// return
// }
// guard let services = peripheral.services else {
// return
// }
// //We need to discover the all characteristic
// for service in services {
// peripheral.discoverCharacteristics(nil, for: service)
// }
// print("Discovered Services: \(services)")
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
for request in requests {
if let value = request.value {
// Handle the received data
let receivedData = Data(value)
print(receivedData)
// Decode the received JSON string into your data structure
let decoder = JSONDecoder()
do {
let receivedObject = try decoder.decode(WantMessage.self, from: receivedData)
// Use the received object to update your app state as needed
print("Received Write")
self.incomingMsg = ""
self.incomingMsg = receivedObject.printMsg()
self.wantVector = receivedObject
print(receivedObject.printMsg())
} catch {
print("Failed to decode JSON: \(error)")
}
// If you want to write back to the central
// if let central = request.central {
// let dataToWrite = // some data you want to send back
// let writeType: CBCharacteristicWriteType = // choose .withResponse or .withoutResponse
// central.writeValue(dataToWrite, for: request.characteristic, type: writeType)
// }
}
// Respond to the write request
peripheral.respond(to: request, withResult: .success)
}
}
}

View File

@@ -13,6 +13,8 @@ class BluetoothController: NSObject, ObservableObject {
private var peripherals: [CBPeripheral] = []
@Published var peripheralNames: [String] = []
var writeCharacteristics: [CBCharacteristic] = []
let BLE_SERVICE_UUID = CBUUID(string: "6e400001-7646-4b5b-9a50-71becce51558")
let BLE_CHARACTERISTIC_UUID_RX = CBUUID(string: "6e400002-7646-4b5b-9a50-71becce51558")
@@ -20,19 +22,99 @@ class BluetoothController: NSObject, ObservableObject {
super.init()
self.centralManager = CBCentralManager(delegate: self, queue: .main)
}
// func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
// }
}
extension BluetoothController: CBCentralManagerDelegate {
extension BluetoothController: CBCentralManagerDelegate, CBPeripheralDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
print("Device is powered on...")
self.centralManager?.scanForPeripherals(withServices: [BLE_SERVICE_UUID])
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if !peripherals.contains(peripheral) {
peripheral.delegate = self
centralManager!.connect(peripheral)
self.peripherals.append(peripheral)
self.peripheralNames.append(peripheral.name ?? "unnamed device")
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.discoverServices([BLE_SERVICE_UUID])
print("Connected to device \(String(describing: peripheral.name))")
if(centralManager?.delegate == nil) {
print("central is nil")
} else {
print("central is not nil")
}
}
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
// Not implemented yet
}
// func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
// print("Discovering services...")
// peripheral.discoverCharacteristics(BLE_CHARACTERISTIC_UUID_RX)
// }
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("*******************************************************")
if ((error) != nil) {
print("Error discovering services: \(error!.localizedDescription)")
return
}
guard let services = peripheral.services else {
return
}
//We need to discover the all characteristic
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
print("Discovered Services: \(services)")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else {
print("No characteristics found for service \(service.uuid)")
return
}
for characteristic in characteristics {
if characteristic.uuid.isEqual(BLE_CHARACTERISTIC_UUID_RX) {
self.writeCharacteristics.append(characteristic)
peripheral.setNotifyValue(true, for: characteristic)
peripheral.readValue(for: characteristic)
print("Characteristic: \(characteristic.uuid)")
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print("updating characteristic value...")
print(characteristic.value ?? "Characteristic is nil")
}
func writeToCharacteristics(message: String) {
guard let messageData = message.data(using: .utf8) else {
print("Could not convert message to data.")
return
}
print("Writing to Characteristic...")
// Go through
for characteristic in writeCharacteristics {
// Go through connected peripherals and write to their characteristic
for peripheral in peripherals {
peripheral.writeValue(messageData, for: characteristic, type: .withoutResponse)
}
}
}
}

View File

@@ -5,9 +5,10 @@
// Created by Severin Memmishofer on 08.07.23.
//
import SwiftUI
import Foundation
struct Body: Codable {
public struct Bodyy: Codable {
let tag: String
let value: String

View File

@@ -10,7 +10,11 @@ import CoreBluetooth
struct ContentView: View {
@State var currentView = 0
@StateObject var dataStore = DataStore()
@EnvironmentObject var dataStore: DataStore
@StateObject private var bluetoothController = BluetoothController()
@StateObject private var bluetoothPeripheral = BluetoothPeripheral()
@Environment(\.scenePhase) private var scenePhase
let saveAction: ()->Void
var body: some View {
VStack {
@@ -18,26 +22,32 @@ struct ContentView: View {
case 0:
PeeringView()
.environmentObject(dataStore)
.environmentObject(bluetoothController)
.environmentObject(bluetoothPeripheral)
.navigationTitle("Peering")
case 1:
FeedListView(feeds: [])
FeedListView()
.environmentObject(dataStore)
.environmentObject(bluetoothController)
.environmentObject(bluetoothPeripheral)
.navigationTitle("Feeds")
case 2:
SettingsView()
.environmentObject(dataStore)
.environmentObject(bluetoothController)
.environmentObject(bluetoothPeripheral)
.navigationTitle("Settings")
default:
FeedListView(feeds: [])
FeedListView()
.environmentObject(dataStore)
}
}
.padding()
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
HStack {
Spacer()
Button(action: {
self.currentView = 0
}) {
VStack {
Label("Discovery", systemImage: "dot.radiowaves.left.and.right")
Image(systemName: "dot.radiowaves.left.and.right")
Text("Discovery")
}
}
@@ -46,7 +56,7 @@ struct ContentView: View {
self.currentView = 1
}) {
VStack {
Label("Feeds", systemImage: "person.2")
Image(systemName: "person.2")
Text("Feeds")
}
}
@@ -55,19 +65,24 @@ struct ContentView: View {
self.currentView = 2
}) {
VStack {
Label("Settings", systemImage: "gear")
Image(systemName: "gear")
Text("Settings")
}
}
Spacer()
}
.frame(height: UIScreen.main.bounds.height * 0.05)
}
.onChange(of: scenePhase) { phase in
if phase == .inactive { saveAction() }
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
ContentView(saveAction: {})
.environmentObject(DataStore())
}
}

View File

@@ -8,16 +8,128 @@
import SwiftUI
import Foundation
@MainActor
class DataStore: ObservableObject {
@Published var personalID: String
@Published var friends: [String]
@Published var feeds: [Feed]
init(personalID: String = "", friends: [String] = [], feeds: [Feed] = []) {
typealias FID = String
typealias SEQ = Int
@Published var personalID: String
@Published var personalFeed: Feed
@Published var friends: [FID:SEQ]
@Published var feedStores: [FeedStore]
init(personalID: String = "", personalFeed: Feed = Feed(), friends: [String:Int] = [:], feedStores: [FeedStore] = []) {
self.personalID = personalID
self.friends = friends
self.feeds = feeds
self.feedStores = feedStores
self.personalFeed = personalFeed
}
private func fileURL(for filename: String) throws -> URL {
try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
.appendingPathComponent("\(filename).json")
}
func savePersonalID() async throws {
let task = Task {
let data = try JSONEncoder().encode(personalID)
let outfile = try fileURL(for: "personalID")
try data.write(to: outfile)
}
_ = try await task.value
}
func saveFriends() async throws {
let task = Task {
let data = try JSONEncoder().encode(friends)
let outfile = try fileURL(for: "friends")
try data.write(to: outfile)
}
_ = try await task.value
}
func loadPersonalID() async throws {
let task = Task<String, Error> {
let fileURL = try self.fileURL(for: "personalID")
guard let data = try? Data(contentsOf: fileURL) else {
return ""
}
let personalID = try JSONDecoder().decode(String.self, from: data)
return personalID
}
let personalID = try await task.value
self.personalID = personalID
}
func loadFriends() async throws {
let task = Task<[String:Int], Error> {
let fileURL = try self.fileURL(for: "friends")
guard let data = try? Data(contentsOf: fileURL) else {
return [:]
}
let friends = try JSONDecoder().decode([String:Int].self, from: data)
return friends
}
let friends = try await task.value
self.friends = friends
}
func loadFeedStores() async throws {
let task = Task<[FeedStore], Error> {
var feedStores: [FeedStore] = []
for feedStore in self.feedStores {
try await feedStore.load()
feedStores.append(feedStore)
}
return feedStores
}
let feedStores = try await task.value
self.feedStores = feedStores
}
func saveFeedStores() async throws {
let task = Task {
for feedStore in self.feedStores {
try await feedStore.save(feed: feedStore.feed)
}
}
_ = try await task.value
}
func savePersonalFeed() async throws {
let task = Task {
let data = try JSONEncoder().encode(personalFeed)
let outfile = try fileURL(for: "personalFeed")
try data.write(to: outfile)
}
_ = try await task.value
}
func loadPersonalFeed() async throws {
let task = Task<Feed, Error> {
let fileURL = try self.fileURL(for: "personalFeed")
guard let data = try? Data(contentsOf: fileURL) else {
return Feed(feedID: self.personalID)
}
let personalFeed = try JSONDecoder().decode(Feed.self, from: data)
return personalFeed
}
let personalFeed = try await task.value
self.personalFeed = personalFeed
}
}
extension DataStore {
static let sampleDataStore = DataStore(personalID: "BOB", friends: SettingsView_Previews.friends, feedStores: [FeedStore(feed: Feed.sampleFeed), FeedStore(feed: Feed.sampleFeed2)])
}

View File

@@ -10,30 +10,42 @@ import Foundation
struct Feed: Codable {
let feedID: String
let feed: [LogEntry]
var feed: [LogEntry]
init(feedID: String = "", feed: [LogEntry] = []) {
self.feedID = feedID
self.feed = feed
}
func getLastLogEntry() -> LogEntry {
if self.feed.isEmpty {
return LogEntry()
} else {
return self.feed.last!
}
}
mutating func appendLogEntry(log: LogEntry) {
self.feed.append(log)
}
}
extension Feed {
static let sampleData: [LogEntry] =
[
LogEntry(feedid: "BOB", sequenceNumber: 1, body: Body(tag: Apps.nam, value: "Bob")),
LogEntry(feedid: "BOB", sequenceNumber: 2, body: Body(tag: Apps.txt, value: "My first post!")),
LogEntry(feedid: "BOB", sequenceNumber: 3, body: Body(tag: Apps.txt, value: "Welcome Alice"))
LogEntry(feedid: "BOB", sequenceNumber: 1, body: Bodyy(tag: Apps.nam, value: "Bob")),
LogEntry(feedid: "BOB", sequenceNumber: 2, body: Bodyy(tag: Apps.txt, value: "My first post!")),
LogEntry(feedid: "BOB", sequenceNumber: 3, body: Bodyy(tag: Apps.txt, value: "Welcome Alice"))
]
static let sampleData2: [LogEntry] =
[
LogEntry(feedid: "ALI", sequenceNumber: 1, body: Body(tag: Apps.nam, value: "Alice")),
LogEntry(feedid: "ALI", sequenceNumber: 2, body: Body(tag: Apps.txt, value: "Alice' first post!")),
LogEntry(feedid: "ALI", sequenceNumber: 3, body: Body(tag: Apps.txt, value: "Welcome Bob")),
LogEntry(feedid: "ALI", sequenceNumber: 4, body: Body(tag: Apps.txt, value: "Whaddup DAWG"))
LogEntry(feedid: "ALI", sequenceNumber: 1, body: Bodyy(tag: Apps.nam, value: "Alice")),
LogEntry(feedid: "ALI", sequenceNumber: 2, body: Bodyy(tag: Apps.txt, value: "Alice' first post!")),
LogEntry(feedid: "ALI", sequenceNumber: 3, body: Bodyy(tag: Apps.txt, value: "Welcome Bob")),
LogEntry(feedid: "ALI", sequenceNumber: 4, body: Bodyy(tag: Apps.txt, value: "Whaddup DAWG"))
]
static let sampleFeed: Feed = Feed(feedID: "BOB", feed: sampleData)

View File

@@ -8,8 +8,9 @@
import SwiftUI
@MainActor
class FeedStore: ObservableObject {
class FeedStore: ObservableObject, Identifiable {
let id: UUID = UUID()
@Published var feed: Feed
init(feed: Feed) {

32
RippleChat/File.swift Normal file
View File

@@ -0,0 +1,32 @@
//
// File.swift
// RippleChat
//
// Created by Severin Memmishofer on 13.07.23.
//
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let error = error {
print("Error discovering services: \(error.localizedDescription)")
return
}
for service in peripheral.services ?? [] {
if service.uuid == CBUUID(string: "YourServiceUUIDHere") {
peripheral.discoverCharacteristics([CBUUID(string: "YourCharacteristicUUIDHere")], for: service)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let error = error {
print("Error discovering characteristics: \(error.localizedDescription)")
return
}
for characteristic in service.characteristics ?? [] {
if characteristic.uuid == CBUUID(string: "YourCharacteristicUUIDHere") {
// You've found your characteristic!
}
}
}

View File

@@ -7,16 +7,22 @@
import Foundation
struct LogEntry: Codable {
struct LogEntry: Codable, Identifiable {
var id: UUID = UUID()
let feedid: String
let sequenceNumber: Int
let body: Body
let body: Bodyy
init(feedid: String, sequenceNumber: Int, body: Body) {
init(feedid: String = "", sequenceNumber: Int = 0, body: Bodyy = Bodyy()) {
self.feedid = feedid
self.sequenceNumber = sequenceNumber
self.body = body
}
}
extension LogEntry {
static let sampleLogEntry = LogEntry(feedid: "BOB", sequenceNumber: 2, body: Bodyy(tag: Apps.txt, value: "My first post!"))
}

View File

@@ -9,10 +9,35 @@ import SwiftUI
@main
struct RippleChatApp: App {
@StateObject private var dataStore = DataStore()
var body: some Scene {
WindowGroup {
ContentView()
ContentView() {
Task {
do {
try await dataStore.savePersonalID()
try await dataStore.savePersonalFeed()
try await dataStore.saveFriends()
try await dataStore.saveFeedStores()
} catch {
fatalError(error.localizedDescription)
}
}
}
.environmentObject(dataStore)
.task {
do {
try await dataStore.loadPersonalID()
try await dataStore.loadPersonalFeed()
try await dataStore.loadFriends()
try await dataStore.loadFeedStores()
} catch {
// Handle the error
print("Error loading data: \(error)")
fatalError(error.localizedDescription)
}
}
}
}
}

View File

@@ -0,0 +1,40 @@
//
// FeedDetailView.swift
// RippleChat
//
// Created by Severin Memmishofer on 12.07.23.
//
import SwiftUI
struct FeedCardView: View {
@EnvironmentObject var dataStore: DataStore
var feed: Feed
private var lastLogEntry: LogEntry {
if feed.feed.isEmpty {
return LogEntry()
} else {
return feed.feed.last!
}
}
var body: some View {
VStack (alignment: .leading) {
HStack {
Text("feedID: \(feed.feedID)")
Spacer()
Text("SEQ: \(lastLogEntry.sequenceNumber)")
}
Text("Last: \(lastLogEntry.body.value)")
}
.padding()
}
}
struct FeedCardView_Previews: PreviewProvider {
static var previews: some View {
FeedCardView(feed: Feed.sampleFeed)
.environmentObject(DataStore.sampleDataStore)
}
}

View File

@@ -0,0 +1,33 @@
//
// FeedDetailView.swift
// RippleChat
//
// Created by Severin Memmishofer on 13.07.23.
//
import SwiftUI
struct FeedDetailView: View {
@EnvironmentObject var dataStore: DataStore
var feed: Feed
init(feed: Feed) {
self.feed = feed
}
var body: some View {
NavigationStack {
List(feed.feed) { logEntry in
LogEntryView(logEntry: logEntry)
}
}
.navigationTitle("Feed: \(feed.feedID)")
}
}
struct FeedDetailView_Previews: PreviewProvider {
static var previews: some View {
FeedDetailView(feed: Feed.sampleFeed)
.environmentObject(DataStore.sampleDataStore)
}
}

View File

@@ -8,35 +8,33 @@
import SwiftUI
struct FeedListView: View {
@State var feeds: [Feed]
@StateObject var store = FeedStore(feed: Feed.sampleFeed)
var feedStores = [FeedStore(feed: Feed.sampleFeed), FeedStore(feed: Feed.sampleFeed2)]
@EnvironmentObject var dataStore: DataStore
var body: some View {
Text("FeedListView")
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
Button("Save Feed") {
Task {
do {
for feed in feedStores {
try await feed.save(feed: feed.feed)
NavigationStack {
Form {
Section(header: Text("Your own Feed:")) {
NavigationLink(destination: FeedDetailView(feed: dataStore.personalFeed)) {
FeedCardView(feed: dataStore.personalFeed)
}
}
Section(header: Text("Feeds of your Firends")) {
List(dataStore.feedStores) { feedStore in
NavigationLink(destination: FeedDetailView(feed: feedStore.feed)) {
FeedCardView(feed: feedStore.feed)
}
}
} catch {
fatalError(error.localizedDescription)
}
}
.navigationTitle("Feeds")
NewFeedEntryView()
}
Spacer()
NewFeedEntryView()
}
}
struct FeedListView_Previews: PreviewProvider {
static var previews: some View {
FeedListView(feeds: [])
FeedListView()
.environmentObject(DataStore.sampleDataStore)
}
}

View File

@@ -0,0 +1,20 @@
//
// FeedView.swift
// RippleChat
//
// Created by Severin Memmishofer on 12.07.23.
//
import SwiftUI
struct FeedView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
struct FeedView_Previews: PreviewProvider {
static var previews: some View {
FeedView()
}
}

View File

@@ -0,0 +1,39 @@
//
// LogEntryView.swift
// RippleChat
//
// Created by Severin Memmishofer on 12.07.23.
//
import SwiftUI
struct LogEntryView: View {
var logEntry: LogEntry
init(logEntry: LogEntry) {
self.logEntry = logEntry
}
var body: some View {
HStack {
VStack {
HStack {
Text("SEQ: \(logEntry.sequenceNumber)")
Spacer()
Text("Tag: \(logEntry.body.tag)")
}
HStack {
Text("Value: \(logEntry.body.value)")
Spacer()
}
}
}
.padding()
}
}
struct LogEntryView_Previews: PreviewProvider {
static var previews: some View {
LogEntryView(logEntry: LogEntry.sampleLogEntry)
}
}

View File

@@ -9,16 +9,31 @@ import SwiftUI
struct NewFeedEntryView: View {
@State private var newEntry: String = ""
@EnvironmentObject var dataStore: DataStore
var body: some View {
VStack(alignment: .leading) {
HStack {
TextField("Enter your new feed message:", text: $newEntry)
Button(action: {}) {
Button(action: {
let nextSeq = dataStore.personalFeed.getLastLogEntry().sequenceNumber + 1
let newBody = Bodyy(tag: Apps.txt, value: newEntry)
let newLogEntry = LogEntry(feedid: dataStore.personalID, sequenceNumber: nextSeq, body: newBody)
dataStore.personalFeed.appendLogEntry(log: newLogEntry)
newEntry = ""
}) {
Text("Send")
}
.task {
do {
try await dataStore.savePersonalFeed()
} catch {
// Handle the error
print("Error loading data: \(error)")
fatalError(error.localizedDescription)
}
}
}
Text("New entry: \(newEntry)")
}
.padding()
}
@@ -27,5 +42,6 @@ struct NewFeedEntryView: View {
struct NewFeedEntryView_Previews: PreviewProvider {
static var previews: some View {
NewFeedEntryView()
.environmentObject(DataStore.sampleDataStore)
}
}

View File

@@ -8,17 +8,56 @@
import SwiftUI
struct PeeringView: View {
@ObservedObject private var bluetoothController = BluetoothController()
@ObservedObject private var bluetoothPeripheral = BluetoothPeripheral()
@EnvironmentObject var dataStore: DataStore
@EnvironmentObject var btController: BluetoothController
@EnvironmentObject var btPeripheral: BluetoothPeripheral
var body: some View {
Text("Peering View")
NavigationView {
List(bluetoothController.peripheralNames, id: \.self) { peripheral in
NavigationStack {
List(btController.peripheralNames, id: \.self) { peripheral in
Text(peripheral)
}
.navigationTitle("Peripherals")
.navigationTitle("Peering")
.navigationViewStyle(StackNavigationViewStyle())
List {
ForEach(btPeripheral.wantVector.friends.keys.sorted(), id: \.self) { friend in
Text("Feed: \(friend.description), SEQ: \(btPeripheral.wantVector.friends[friend] ?? -1)")
}
}
Text("Incoming msg: \(btPeripheral.incomingMsg)")
.onChange(of: btPeripheral.incomingMsg) { newValue in
compareWithSavedFeeds(newVector: btPeripheral.wantVector)
}
Button(action: {
do {
var combinedDict = dataStore.friends
combinedDict[dataStore.personalFeed.feedID] = dataStore.personalFeed.feed.count
let WANT_msg = WantMessage(friends: combinedDict)
let encoded_msg = try JSONEncoder().encode(WANT_msg)
btController.writeToCharacteristics(message: String(data: encoded_msg, encoding: .utf8)!)
//btController.writeToCharacteristics(message: "Test")
print("Pressed Button")
} catch {
fatalError(error.localizedDescription)
}
}) {
Text("Send WANT-Vector")
}
.padding()
}
}
func compareWithSavedFeeds(newVector: WantMessage) {
print("comparing with saved Feeds...")
for friend in newVector.friends.keys.sorted() {
if(dataStore.friends.keys.contains(friend.description)) {
print("Found friend \(friend.description) in own Feeds!")
var missingFeedEntries: Int = -1
if let ownCount = dataStore.friends[friend] {
missingFeedEntries = newVector.friends[friend]! - ownCount
}
print("You are \(missingFeedEntries) behind on the feed of \(friend.description)")
}
}
}
}
@@ -26,5 +65,16 @@ struct PeeringView: View {
struct PeeringView_Previews: PreviewProvider {
static var previews: some View {
PeeringView()
.environmentObject(BluetoothPeripheral())
.environmentObject(BluetoothController())
}
}
struct WantMessage: Codable {
var command = "WANT"
var friends = [String:Int]()
func printMsg() -> String {
return ("{\(command) : \(friends.description)}")
}
}

View File

@@ -16,24 +16,36 @@ struct SettingsEditView: View {
Section(header: Text("Personal Feed ID")) {
//Label(dataStore.personalID, systemImage: "person.crop.circle")
HStack {
TextField(dataStore.personalID, text: $dataStore.personalID)
TextField(dataStore.personalID, text: $dataStore.personalID)
}
}
Section(header: Text("Friends")) {
ForEach(dataStore.friends) { friend in
Label(friend, systemImage: "person")
ForEach(dataStore.friends.keys.sorted(), id: \.self) { friend in
if let seq = dataStore.friends[friend] {
Label("\(friend) - SEQ: \(seq)", systemImage: "person")
}
}
.onDelete { indexSet in do {
indexSet.forEach { index in
let key = dataStore.friends.keys.sorted()[index]
dataStore.friends.removeValue(forKey: key)
}
dataStore.feedStores.remove(atOffsets: indexSet)
}
.onDelete {indices in
dataStore.friends.remove(atOffsets: indices)
}
HStack {
TextField("New Feed", text: $newFeedID)
Button(action: {
let newFeed = Feed(feedID: newFeedID)
let newFeedStore = FeedStore(feed: newFeed)
dataStore.feedStores.append(newFeedStore)
withAnimation {
let feedid = newFeedID
dataStore.friends.append(feedid)
dataStore.friends[newFeedID] = 0
newFeedID = ""
}
}) {
Image(systemName: "plus.circle.fill")
}

View File

@@ -12,28 +12,32 @@ struct SettingsView: View {
@EnvironmentObject var dataStore: DataStore
@State private var isPresentingEditView = false
var body: some View {
NavigationStack {
List {
HStack {
Spacer()
Button("Edit") {
isPresentingEditView = true
}
}
Section(header: Text("Personal Feed ID")) {
Label(dataStore.personalID, systemImage: "person.crop.circle")
}
Section(header: Text("Friends")) {
ForEach(dataStore.friends) { friend in
Label(friend, systemImage: "person")
ForEach(dataStore.friends.keys.sorted(), id: \.self) { friend in
if let seq = dataStore.friends[friend] {
Label("\(friend) - SEQ: \(seq)", systemImage: "person")
}
}
}
}
.navigationTitle("Settings")
.navigationViewStyle(StackNavigationViewStyle())
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Edit") {
isPresentingEditView = true
}
}
}
.sheet(isPresented: $isPresentingEditView) {
NavigationStack {
SettingsEditView()
@@ -47,21 +51,23 @@ struct SettingsView: View {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
isPresentingEditView = false
dataStore.personalFeed = Feed(feedID: dataStore.personalID)
}
}
}
}
}
}
}
}
struct SettingsView_Previews: PreviewProvider {
static var friends = [
"BOS",
"ALI",
"CYN"
"BOS":1,
"ALI":2,
"CYN":3
]
static var previews: some View {
SettingsView()