Compare commits
No commits in common. "main" and "UIRefinementsSL" have entirely different histories.
main
...
UIRefineme
22
LICENSE
22
LICENSE
@ -1,22 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Severin Memmishofer
|
||||
Copyright (c) 2023 Sebastian Lenzlinger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,6 +1,2 @@
|
||||
# RippleChat
|
||||
RippleChat is a meant to be a TinySSB compatible chat client. It is started as a university project as a proof of concept for p2p messaging in a Secure Scuttlebut fashion on iOS.
|
||||
At this stage it communicates via BLE.
|
||||
It is not currently compatible with TinySSB conforming apps like [Tremola](https://github.com/cn-uofbasel/tremola).
|
||||
Future work includes switching to the tinySSB protocol and heavily beautifying th UI.
|
||||
|
||||
2023 © Severin Memmishofer, Sebastian Lenzlinger
|
||||
|
||||
@ -15,22 +15,20 @@
|
||||
96454F362A558EBE0040BEBD /* RippleChatUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96454F352A558EBE0040BEBD /* RippleChatUITests.swift */; };
|
||||
96454F382A558EBE0040BEBD /* RippleChatUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96454F372A558EBE0040BEBD /* RippleChatUITestsLaunchTests.swift */; };
|
||||
96454F452A5593900040BEBD /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 96454F442A5593900040BEBD /* .gitignore */; };
|
||||
96BD330E2A5C254B007A6E53 /* TextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BD330D2A5C254B007A6E53 /* TextApp.swift */; };
|
||||
96BD33102A5C27B0007A6E53 /* NewFeedEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BD330F2A5C27B0007A6E53 /* NewFeedEntryView.swift */; };
|
||||
96BD33132A5C400B007A6E53 /* FeedListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BD33122A5C400B007A6E53 /* FeedListView.swift */; };
|
||||
96BD33162A5C403C007A6E53 /* DiscoveryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BD33152A5C403C007A6E53 /* DiscoveryView.swift */; };
|
||||
F581F59B2A5AE72F0081C383 /* BTCentral.swift in Sources */ = {isa = PBXBuildFile; fileRef = F581F59A2A5AE72F0081C383 /* BTCentral.swift */; };
|
||||
F5847B622A599BF4009E28D4 /* Bodyy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5847B612A599BF4009E28D4 /* Bodyy.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 */; };
|
||||
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 */
|
||||
@ -62,22 +60,20 @@
|
||||
96454F352A558EBE0040BEBD /* RippleChatUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RippleChatUITests.swift; sourceTree = "<group>"; };
|
||||
96454F372A558EBE0040BEBD /* RippleChatUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RippleChatUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||
96454F442A5593900040BEBD /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
|
||||
96BD330D2A5C254B007A6E53 /* TextApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextApp.swift; sourceTree = "<group>"; };
|
||||
96BD330F2A5C27B0007A6E53 /* NewFeedEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewFeedEntryView.swift; sourceTree = "<group>"; };
|
||||
96BD33122A5C400B007A6E53 /* FeedListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListView.swift; sourceTree = "<group>"; };
|
||||
96BD33152A5C403C007A6E53 /* DiscoveryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryView.swift; sourceTree = "<group>"; };
|
||||
F581F59A2A5AE72F0081C383 /* BTCentral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTCentral.swift; sourceTree = "<group>"; };
|
||||
F5847B612A599BF4009E28D4 /* Bodyy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bodyy.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>"; };
|
||||
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 */
|
||||
@ -133,14 +129,15 @@
|
||||
96454F1A2A558EBC0040BEBD /* RippleChatApp.swift */,
|
||||
96454F1C2A558EBC0040BEBD /* ContentView.swift */,
|
||||
96BD33112A5C3FFC007A6E53 /* Views */,
|
||||
F581F59A2A5AE72F0081C383 /* BTCentral.swift */,
|
||||
F581F59A2A5AE72F0081C383 /* BluetoothController.swift */,
|
||||
F5A4B1222A5D5F8B00F5AE01 /* BTPeripheral.swift */,
|
||||
96454F1E2A558EBD0040BEBD /* Assets.xcassets */,
|
||||
96454F202A558EBD0040BEBD /* Preview Content */,
|
||||
F5847B612A599BF4009E28D4 /* Bodyy.swift */,
|
||||
F5847B612A599BF4009E28D4 /* Body.swift */,
|
||||
F5847B632A599CC3009E28D4 /* LogEntry.swift */,
|
||||
F5847B652A599EA4009E28D4 /* Feed.swift */,
|
||||
F5847B692A59AB24009E28D4 /* FeedStore.swift */,
|
||||
96BD330D2A5C254B007A6E53 /* TextApp.swift */,
|
||||
F5A4B1242A5D7A8D00F5AE01 /* DataStore.swift */,
|
||||
);
|
||||
path = RippleChat;
|
||||
@ -176,12 +173,9 @@
|
||||
children = (
|
||||
96BD330F2A5C27B0007A6E53 /* NewFeedEntryView.swift */,
|
||||
96BD33122A5C400B007A6E53 /* FeedListView.swift */,
|
||||
96BD33152A5C403C007A6E53 /* DiscoveryView.swift */,
|
||||
96BD33152A5C403C007A6E53 /* PeeringView.swift */,
|
||||
F5A4B1202A5D4D1F00F5AE01 /* SettingsView.swift */,
|
||||
F5A4B1262A5D861E00F5AE01 /* SettingsEditView.swift */,
|
||||
F5F1419B2A5EFA3600C81B1A /* LogEntryView.swift */,
|
||||
F5F1419D2A5EFA4700C81B1A /* FeedCardView.swift */,
|
||||
F59375712A5FF344001FA46A /* FeedDetailView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@ -318,22 +312,20 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F5847B622A599BF4009E28D4 /* Bodyy.swift in Sources */,
|
||||
96BD33162A5C403C007A6E53 /* DiscoveryView.swift in Sources */,
|
||||
F5847B622A599BF4009E28D4 /* Body.swift in Sources */,
|
||||
96BD33162A5C403C007A6E53 /* PeeringView.swift in Sources */,
|
||||
F5847B662A599EA4009E28D4 /* Feed.swift in Sources */,
|
||||
96BD33132A5C400B007A6E53 /* FeedListView.swift in Sources */,
|
||||
F5847B642A599CC3009E28D4 /* LogEntry.swift in Sources */,
|
||||
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 /* BTCentral.swift in Sources */,
|
||||
F581F59B2A5AE72F0081C383 /* BluetoothController.swift in Sources */,
|
||||
96454F1D2A558EBC0040BEBD /* ContentView.swift in Sources */,
|
||||
F5A4B1212A5D4D1F00F5AE01 /* SettingsView.swift in Sources */,
|
||||
96BD330E2A5C254B007A6E53 /* TextApp.swift in Sources */,
|
||||
96454F1B2A558EBC0040BEBD /* RippleChatApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -524,7 +516,7 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"RippleChat/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = GN2B48NJ47;
|
||||
DEVELOPMENT_TEAM = B5S58UWR64;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Allow for bluetooth use";
|
||||
@ -539,7 +531,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = unibas.inetsec.RippleChat1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = unibas.inetsec.RippleChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
@ -1,120 +0,0 @@
|
||||
//
|
||||
// BluetoothController.swift
|
||||
// RippleChat
|
||||
//
|
||||
// Created by Severin Memmishofer on 09.07.23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreBluetooth
|
||||
|
||||
class BTCentral: NSObject, ObservableObject {
|
||||
private var centralManager: CBCentralManager?
|
||||
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")
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
self.centralManager = CBCentralManager(delegate: self, queue: .main)
|
||||
}
|
||||
|
||||
// func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
||||
// }
|
||||
}
|
||||
|
||||
extension BTCentral: 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,8 +10,6 @@ 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")
|
||||
@ -23,101 +21,64 @@ class BluetoothPeripheral: NSObject, ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Change variable names, etc...
|
||||
|
||||
extension BluetoothPeripheral: CBPeripheralManagerDelegate {
|
||||
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
|
||||
switch peripheral.state {
|
||||
case .unknown:
|
||||
print("BT Device is UNKNOWN")
|
||||
print("Bluetooth Device is UNKNOWN")
|
||||
case .unsupported:
|
||||
print("BT Device is UNSUPPORTED")
|
||||
print("Bluetooth Device is UNSUPPORTED")
|
||||
case .unauthorized:
|
||||
print("BT Device is UNAUTHORIZED")
|
||||
print("Bluetooth Device is UNAUTHORIZED")
|
||||
case .resetting:
|
||||
print("BT Device is RESETTING")
|
||||
print("Bluetooth Device is RESETTING")
|
||||
case .poweredOff:
|
||||
print("BT Device is POWERED OFF")
|
||||
print("Bluetooth Device is POWERED OFF")
|
||||
case .poweredOn:
|
||||
print("BT Device is POWERED ON")
|
||||
addBTService()
|
||||
print("Bluetooth Device is POWERED ON")
|
||||
addServices()
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
func addBTService() {
|
||||
let myCharacteristic = CBMutableCharacteristic(type: BLE_CHARACTERISTIC_UUID_RX, properties: [.read, .write, .notify, .writeWithoutResponse], value: nil, permissions: [.readable, .writeable])
|
||||
func addServices() {
|
||||
|
||||
let myCharacteristic = CBMutableCharacteristic(type: BLE_CHARACTERISTIC_UUID_RX, properties: [.read, .write, .notify], value: nil, permissions: [.readable])
|
||||
|
||||
// 2. Create instance of CBMutableService
|
||||
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)
|
||||
}
|
||||
|
||||
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
|
||||
peripheralManager!.startAdvertising([CBAdvertisementDataLocalNameKey : "RippleChat", CBAdvertisementDataServiceUUIDsKey: [BLE_SERVICE_UUID]])
|
||||
|
||||
// 5. Start advertising
|
||||
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("*******************************************************")
|
||||
// func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
|
||||
//
|
||||
// messageLabel.text = "Data getting Read"
|
||||
// readValueLabel.text = value
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// // 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
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
38
RippleChat/BluetoothController.swift
Normal file
38
RippleChat/BluetoothController.swift
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// BluetoothController.swift
|
||||
// RippleChat
|
||||
//
|
||||
// Created by Severin Memmishofer on 09.07.23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreBluetooth
|
||||
|
||||
class BluetoothController: NSObject, ObservableObject {
|
||||
private var centralManager: CBCentralManager?
|
||||
private var peripherals: [CBPeripheral] = []
|
||||
@Published var peripheralNames: [String] = []
|
||||
|
||||
let BLE_SERVICE_UUID = CBUUID(string: "6e400001-7646-4b5b-9a50-71becce51558")
|
||||
let BLE_CHARACTERISTIC_UUID_RX = CBUUID(string: "6e400002-7646-4b5b-9a50-71becce51558")
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
self.centralManager = CBCentralManager(delegate: self, queue: .main)
|
||||
}
|
||||
}
|
||||
|
||||
extension BluetoothController: CBCentralManagerDelegate {
|
||||
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||
if central.state == .poweredOn {
|
||||
self.centralManager?.scanForPeripherals(withServices: [BLE_SERVICE_UUID])
|
||||
}
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
|
||||
if !peripherals.contains(peripheral) {
|
||||
self.peripherals.append(peripheral)
|
||||
self.peripheralNames.append(peripheral.name ?? "unnamed device")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,9 @@
|
||||
// Created by Severin Memmishofer on 08.07.23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
|
||||
public struct Bodyy: Codable {
|
||||
struct Body: Codable {
|
||||
|
||||
let tag: String
|
||||
let value: String
|
||||
@ -10,35 +10,23 @@ import CoreBluetooth
|
||||
|
||||
struct ContentView: View {
|
||||
@State var currentView = 0
|
||||
@EnvironmentObject var dataStore: DataStore
|
||||
@StateObject private var bluetoothController = BTCentral()
|
||||
@StateObject private var bluetoothPeripheral = BluetoothPeripheral()
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
let saveAction: ()->Void
|
||||
@StateObject var dataStore = DataStore()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
switch self.currentView {
|
||||
case 0:
|
||||
DiscoveryView()
|
||||
PeeringView()
|
||||
.environmentObject(dataStore)
|
||||
.environmentObject(bluetoothController)
|
||||
.environmentObject(bluetoothPeripheral)
|
||||
.navigationTitle("Peering")
|
||||
case 1:
|
||||
FeedListView()
|
||||
FeedListView(feeds: [])
|
||||
.environmentObject(dataStore)
|
||||
.environmentObject(bluetoothController)
|
||||
.environmentObject(bluetoothPeripheral)
|
||||
.navigationTitle("Feeds")
|
||||
case 2:
|
||||
SettingsView()
|
||||
.environmentObject(dataStore)
|
||||
.environmentObject(bluetoothController)
|
||||
.environmentObject(bluetoothPeripheral)
|
||||
.navigationTitle("Settings")
|
||||
default:
|
||||
FeedListView()
|
||||
FeedListView(feeds: [])
|
||||
.environmentObject(dataStore)
|
||||
}
|
||||
HStack {
|
||||
@ -73,16 +61,14 @@ struct ContentView: View {
|
||||
}
|
||||
.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(saveAction: {})
|
||||
ContentView()
|
||||
.environmentObject(DataStore())
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,128 +8,16 @@
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
class DataStore: ObservableObject {
|
||||
|
||||
typealias FID = String
|
||||
typealias SEQ = Int
|
||||
|
||||
@Published var personalID: String
|
||||
@Published var personalFeed: Feed
|
||||
@Published var friends: [FID:SEQ]
|
||||
@Published var feedStores: [FeedStore]
|
||||
@Published var friends: [String]
|
||||
@Published var feeds: [Feed]
|
||||
|
||||
|
||||
init(personalID: String = "", personalFeed: Feed = Feed(), friends: [String:Int] = [:], feedStores: [FeedStore] = []) {
|
||||
init(personalID: String = "", friends: [String] = [], feeds: [Feed] = []) {
|
||||
self.personalID = personalID
|
||||
self.friends = friends
|
||||
self.feedStores = feedStores
|
||||
self.personalFeed = personalFeed
|
||||
self.feeds = feeds
|
||||
}
|
||||
|
||||
|
||||
|
||||
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)])
|
||||
}
|
||||
|
||||
@ -10,42 +10,30 @@ import Foundation
|
||||
struct Feed: Codable {
|
||||
|
||||
let feedID: String
|
||||
var feed: [LogEntry]
|
||||
let 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: 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"))
|
||||
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"))
|
||||
]
|
||||
|
||||
static let sampleData2: [LogEntry] =
|
||||
[
|
||||
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"))
|
||||
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"))
|
||||
]
|
||||
|
||||
static let sampleFeed: Feed = Feed(feedID: "BOB", feed: sampleData)
|
||||
|
||||
@ -8,9 +8,8 @@
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
class FeedStore: ObservableObject, Identifiable {
|
||||
class FeedStore: ObservableObject {
|
||||
|
||||
let id: UUID = UUID()
|
||||
@Published var feed: Feed
|
||||
|
||||
init(feed: Feed) {
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
//
|
||||
// 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!
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,22 +7,16 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct LogEntry: Codable, Identifiable {
|
||||
|
||||
var id: UUID = UUID()
|
||||
struct LogEntry: Codable {
|
||||
|
||||
let feedid: String
|
||||
let sequenceNumber: Int
|
||||
let body: Bodyy
|
||||
let body: Body
|
||||
|
||||
init(feedid: String = "", sequenceNumber: Int = 0, body: Bodyy = Bodyy()) {
|
||||
init(feedid: String, sequenceNumber: Int, body: Body) {
|
||||
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!"))
|
||||
}
|
||||
|
||||
@ -9,35 +9,10 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct RippleChatApp: App {
|
||||
@StateObject private var dataStore = DataStore()
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
ContentView()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
//
|
||||
// PeeringView.swift
|
||||
// RippleChat
|
||||
//
|
||||
// Created by Sebastian Lenzlinger on 10.07.23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DiscoveryView: View {
|
||||
@EnvironmentObject var dataStore: DataStore
|
||||
@EnvironmentObject var btCentral: BTCentral
|
||||
@EnvironmentObject var btPeripheral: BluetoothPeripheral
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List(btCentral.peripheralNames, id: \.self) { peripheral in
|
||||
Text(peripheral)
|
||||
}
|
||||
.navigationTitle("Peer Discovery")
|
||||
.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)
|
||||
btCentral.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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DiscoveryView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DiscoveryView()
|
||||
.environmentObject(BluetoothPeripheral())
|
||||
.environmentObject(BTCentral())
|
||||
}
|
||||
}
|
||||
|
||||
struct WantMessage: Codable {
|
||||
var command = "WANT"
|
||||
var friends = [String:Int]()
|
||||
|
||||
func printMsg() -> String {
|
||||
return ("{\(command) : \(friends.description)}")
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@ -8,33 +8,35 @@
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
} catch {
|
||||
fatalError(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Feeds")
|
||||
NewFeedEntryView()
|
||||
}
|
||||
Spacer()
|
||||
NewFeedEntryView()
|
||||
}
|
||||
}
|
||||
|
||||
struct FeedListView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
FeedListView()
|
||||
.environmentObject(DataStore.sampleDataStore)
|
||||
FeedListView(feeds: [])
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@ -9,31 +9,16 @@ 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: {
|
||||
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 = ""
|
||||
}) {
|
||||
Button(action: {}) {
|
||||
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()
|
||||
}
|
||||
@ -42,6 +27,5 @@ struct NewFeedEntryView: View {
|
||||
struct NewFeedEntryView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NewFeedEntryView()
|
||||
.environmentObject(DataStore.sampleDataStore)
|
||||
}
|
||||
}
|
||||
|
||||
31
RippleChat/Views/PeeringView.swift
Normal file
31
RippleChat/Views/PeeringView.swift
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// PeeringView.swift
|
||||
// RippleChat
|
||||
//
|
||||
// Created by Sebastian Lenzlinger on 10.07.23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PeeringView: View {
|
||||
@ObservedObject private var bluetoothController = BluetoothController()
|
||||
@ObservedObject private var bluetoothPeripheral = BluetoothPeripheral()
|
||||
@EnvironmentObject var dataStore: DataStore
|
||||
|
||||
var body: some View {
|
||||
Text("Peering View")
|
||||
NavigationStack {
|
||||
List(bluetoothController.peripheralNames, id: \.self) { peripheral in
|
||||
Text(peripheral)
|
||||
}
|
||||
.navigationTitle("Peripherals")
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PeeringView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PeeringView()
|
||||
}
|
||||
}
|
||||
@ -16,36 +16,24 @@ 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.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)
|
||||
|
||||
ForEach(dataStore.friends) { friend in
|
||||
Label(friend, systemImage: "person")
|
||||
}
|
||||
.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 {
|
||||
dataStore.friends[newFeedID] = 0
|
||||
let feedid = newFeedID
|
||||
dataStore.friends.append(feedid)
|
||||
newFeedID = ""
|
||||
}
|
||||
|
||||
}) {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
}
|
||||
|
||||
@ -22,10 +22,8 @@ struct SettingsView: View {
|
||||
Label(dataStore.personalID, systemImage: "person.crop.circle")
|
||||
}
|
||||
Section(header: Text("Friends")) {
|
||||
ForEach(dataStore.friends.keys.sorted(), id: \.self) { friend in
|
||||
if let seq = dataStore.friends[friend] {
|
||||
Label("\(friend) - SEQ: \(seq)", systemImage: "person")
|
||||
}
|
||||
ForEach(dataStore.friends) { friend in
|
||||
Label(friend, systemImage: "person")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,7 +49,6 @@ struct SettingsView: View {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Done") {
|
||||
isPresentingEditView = false
|
||||
dataStore.personalFeed = Feed(feedID: dataStore.personalID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,9 +62,9 @@ struct SettingsView: View {
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var friends = [
|
||||
"BOS":1,
|
||||
"ALI":2,
|
||||
"CYN":3
|
||||
"BOS",
|
||||
"ALI",
|
||||
"CYN"
|
||||
]
|
||||
static var previews: some View {
|
||||
SettingsView()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user