From 09e2ab50bb43f9627a6614965010f43f920ce05c Mon Sep 17 00:00:00 2001 From: HoshinoSuzumi Date: Tue, 16 Jul 2024 21:14:19 +0800 Subject: [PATCH] feat: device scanning --- components.d.ts | 11 +- package.json | 3 +- pnpm-lock.yaml | 38 ++ src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 6 +- src-tauri/src/main.rs | 363 +++++++++++------- src/App.vue | 33 +- src/assets/css/style.css | 100 +++++ src/components/DrawerContainer.vue | 26 +- src/components/Greet.vue | 47 --- src/components/PageContainer.vue | 37 ++ src/components/SignalIndicator.vue | 27 ++ .../icons/SystemUiconsSignalFull.vue | 10 + .../icons/SystemUiconsSignalLow.vue | 10 + .../icons/SystemUiconsSignalMedium.vue | 10 + src/components/icons/TablerBluetooth.vue | 10 + .../icons/TablerBluetoothConnected.vue | 10 + src/components/icons/TablerBluetoothOff.vue | 10 + src/components/icons/TablerBluetoothX.vue | 10 + src/components/icons/TablerReload.vue | 10 + src/main.ts | 9 +- src/pages/index.vue | 69 +++- src/stores/index.ts | 71 ++++ tailwind.config.js | 8 - tailwind.config.ts | 18 + 25 files changed, 725 insertions(+), 223 deletions(-) delete mode 100644 src/components/Greet.vue create mode 100644 src/components/PageContainer.vue create mode 100644 src/components/SignalIndicator.vue create mode 100644 src/components/icons/SystemUiconsSignalFull.vue create mode 100644 src/components/icons/SystemUiconsSignalLow.vue create mode 100644 src/components/icons/SystemUiconsSignalMedium.vue create mode 100644 src/components/icons/TablerBluetooth.vue create mode 100644 src/components/icons/TablerBluetoothConnected.vue create mode 100644 src/components/icons/TablerBluetoothOff.vue create mode 100644 src/components/icons/TablerBluetoothX.vue create mode 100644 src/components/icons/TablerReload.vue create mode 100644 src/stores/index.ts delete mode 100644 tailwind.config.js create mode 100644 tailwind.config.ts diff --git a/components.d.ts b/components.d.ts index bb69fa8..4c83482 100644 --- a/components.d.ts +++ b/components.d.ts @@ -8,9 +8,18 @@ export {} declare module 'vue' { export interface GlobalComponents { DrawerContainer: typeof import('./src/components/DrawerContainer.vue')['default'] - Greet: typeof import('./src/components/Greet.vue')['default'] + PageContainer: typeof import('./src/components/PageContainer.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + SignalIndicator: typeof import('./src/components/SignalIndicator.vue')['default'] + SystemUiconsSignalFull: typeof import('./src/components/icons/SystemUiconsSignalFull.vue')['default'] + SystemUiconsSignalLow: typeof import('./src/components/icons/SystemUiconsSignalLow.vue')['default'] + SystemUiconsSignalMedium: typeof import('./src/components/icons/SystemUiconsSignalMedium.vue')['default'] + TablerBluetooth: typeof import('./src/components/icons/TablerBluetooth.vue')['default'] + TablerBluetoothConnected: typeof import('./src/components/icons/TablerBluetoothConnected.vue')['default'] + TablerBluetoothOff: typeof import('./src/components/icons/TablerBluetoothOff.vue')['default'] + TablerBluetoothX: typeof import('./src/components/icons/TablerBluetoothX.vue')['default'] TablerDeviceWatch: typeof import('./src/components/icons/TablerDeviceWatch.vue')['default'] + TablerReload: typeof import('./src/components/icons/TablerReload.vue')['default'] } } diff --git a/package.json b/package.json index 2197999..7dd51a8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "heartbeat-cat", "private": true, - "version": "0.0.0", + "version": "0.0.1", "type": "module", "scripts": { "dev": "vite", @@ -11,6 +11,7 @@ }, "dependencies": { "@tauri-apps/api": "^1", + "pinia": "^2.1.7", "vue": "^3.3.4", "vue-router": "^4.4.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c155df6..8812a43 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@tauri-apps/api': specifier: ^1 version: 1.6.0 + pinia: + specifier: ^2.1.7 + version: 2.1.7(typescript@5.5.3)(vue@3.4.31(typescript@5.5.3)) vue: specifier: ^3.3.4 version: 3.4.31(typescript@5.5.3) @@ -798,6 +801,18 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + pinia@2.1.7: + resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==} + peerDependencies: + '@vue/composition-api': ^1.4.0 + typescript: '>=4.4.4' + vue: ^2.6.14 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + typescript: + optional: true + pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} @@ -1011,6 +1026,17 @@ packages: vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + vue-demi@0.14.8: + resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + vue-router@4.4.0: resolution: {integrity: sha512-HB+t2p611aIZraV2aPSRNXf0Z/oLZFrlygJm+sZbdJaW6lcFqEDQwnzUBXn+DApw+/QzDU/I9TeWx9izEjTmsA==} peerDependencies: @@ -1676,6 +1702,14 @@ snapshots: pify@2.3.0: {} + pinia@2.1.7(typescript@5.5.3)(vue@3.4.31(typescript@5.5.3)): + dependencies: + '@vue/devtools-api': 6.6.3 + vue: 3.4.31(typescript@5.5.3) + vue-demi: 0.14.8(vue@3.4.31(typescript@5.5.3)) + optionalDependencies: + typescript: 5.5.3 + pirates@4.0.6: {} pkg-types@1.1.3: @@ -1907,6 +1941,10 @@ snapshots: vscode-uri@3.0.8: {} + vue-demi@0.14.8(vue@3.4.31(typescript@5.5.3)): + dependencies: + vue: 3.4.31(typescript@5.5.3) + vue-router@4.4.0(vue@3.4.31(typescript@5.5.3)): dependencies: '@vue/devtools-api': 6.6.3 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 86e638d..cc46fa4 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1319,7 +1319,7 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heartbeat-cat" -version = "0.0.0" +version = "0.0.1" dependencies = [ "btleplug", "futures", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 431743c..bf4fd45 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "heartbeat-cat" -version = "0.0.0" -description = "A Tauri App" -authors = ["you"] +version = "0.0.1" +description = "Catch your heartbeats" +authors = ["TimothyYin"] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e1e5f02..d3f4464 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -6,9 +6,11 @@ use btleplug::api::CentralEvent::{ DeviceConnected, DeviceDisconnected, DeviceDiscovered, DeviceUpdated, ManufacturerDataAdvertisement, ServiceDataAdvertisement, ServicesAdvertisement, }; -use btleplug::api::{Central, Manager as _, Peripheral as _, ScanFilter}; -use btleplug::platform::{Adapter, Manager as BtleManager, Peripheral}; +use btleplug::api::{BDAddr, Central, Manager as _, Peripheral as _, ScanFilter}; +use btleplug::platform::{Adapter, Manager as BtleManager, Peripheral, PeripheralId}; use futures::StreamExt; +use std::error::Error; +use std::sync::Arc; use tauri::{AppHandle, Manager, State}; use tokio; use tokio::sync::Mutex; @@ -16,177 +18,249 @@ use tokio::sync::Mutex; #[derive(serde::Serialize, Clone)] struct BleDevice { name: String, - address: String, + address: BDAddr, + rssi: i16, } -struct BleConnection<'a> { - central: Mutex>, - peripheral: &'a Mutex>, - scan_devices: Mutex>, +struct BleConnection { + _is_events_registered: Arc>, + central: Arc>>, + peripheral: Arc>>, } -#[tauri::command] -async fn request_central_events<'a>( - app_handle: AppHandle, - connection: State<'a, BleConnection<'a>>, -) -> Result { - let central = connection.central.lock().await; - let peripheral = connection.peripheral.lock().await; - let central = central.as_ref().unwrap(); - let central = central.clone(); +impl BleConnection { + fn new(central: Adapter) -> Self { + Self { + _is_events_registered: Arc::new(Mutex::new(false)), + central: Arc::new(Mutex::new(Some(central))), + peripheral: Arc::new(Mutex::new(None)), + } + } - let mut event_stream = central.events().await.unwrap(); + async fn set_peripheral(&self, peripheral: Option) { + let mut p = self.peripheral.lock().await; + *p = peripheral; + } - tauri::async_runtime::spawn(async move { - while let Some(event) = event_stream.next().await { - if let DeviceDiscovered(_) | DeviceUpdated(_) = event.clone() { - let mut devices = vec![]; - for p in central.peripherals().await.unwrap() { - devices.push(BleDevice { - name: p - .properties() + pub async fn start_scan(&self) -> Result<(), String> { + let central = self.central.lock().await; + if let Err(e) = central + .as_ref() + .unwrap() + .start_scan(ScanFilter { services: vec![] }) + .await + { + return Err(e.to_string()); + } else { + Ok(()) + } + } + + pub async fn stop_scan(&self) -> Result<(), String> { + let central = self.central.lock().await; + if let Err(e) = central.as_ref().unwrap().stop_scan().await { + return Err(e.to_string()); + } else { + Ok(()) + } + } + + pub async fn is_connected(&self) -> bool { + let peripheral = self.peripheral.lock().await; + peripheral.is_some() + } + + pub async fn connect(&self, address: BDAddr, app: &AppHandle) -> Result<(), Box> { + self.stop_scan().await.unwrap(); + let central = self.central.lock().await; + let peripheral = central + .as_ref() + .unwrap() + .peripheral(&PeripheralId::from(address)) + .await?; + peripheral.connect().await?; + peripheral.discover_services().await?; + // 如果 peripheral.services() 不包含 0x180D 服务,则返回错误 + if !peripheral + .services() + .iter() + .any(|s| s.uuid == uuid_from_u16(0x180D)) + { + return Err("Peripheral does not have the required service".into()); + } + self.set_peripheral(Some(peripheral)).await; + + let peripheral = self.peripheral.lock().await; + let device = BleDevice { + name: peripheral + .as_ref() + .unwrap() + .properties() + .await? + .unwrap() + .local_name + .unwrap_or("Unknown".to_string()), + address: peripheral.as_ref().unwrap().address(), + rssi: peripheral + .as_ref() + .unwrap() + .properties() + .await? + .unwrap() + .rssi + .unwrap(), + }; + app.emit_all("device-connected", device).unwrap(); + Ok(()) + } + + pub async fn disconnect(&self) -> Result<(), Box> { + let mut peripheral = self.peripheral.lock().await; + peripheral.as_ref().unwrap().disconnect().await?; + *peripheral = None; + Ok(()) + } + + pub async fn register_central_events(&self, app: &AppHandle) { + let central = self.central.lock().await; + let central_clone = central.clone(); // Clone the central variable + let app_handle = app.clone(); // Clone the AppHandle to move into the tokio::spawn closure + let mut event_stream = central.as_ref().unwrap().events().await.unwrap(); + + let mut self_clone = self.clone(); // Clone the BleConnection to move into the tokio::spawn closure + + tokio::spawn(async move { + while let Some(event) = event_stream.next().await { + match event { + DeviceDiscovered(peripheral) | DeviceUpdated(peripheral) => { + let p = central_clone + .as_ref() + .unwrap() + .peripheral(&peripheral) .await - .unwrap() - .unwrap() - .local_name - .unwrap_or("Unknown".to_string()), - address: p.address().to_string(), - }); - } - println!("Devices: {:?}", serde_json::to_string(&devices).unwrap()); - app_handle - .emit_all("scan-list-update", serde_json::to_string(&devices).unwrap()) - .unwrap(); - } - if let DeviceConnected(_) = event.clone() { - let peripheral = peripheral.as_ref().unwrap().clone(); - app_handle - .emit_all( - "device-connected", - serde_json::to_string(&BleDevice { - name: peripheral + .unwrap(); + let device = BleDevice { + name: p .properties() .await .unwrap() .unwrap() .local_name .unwrap_or("Unknown".to_string()), - address: peripheral.address().to_string(), - }) - .unwrap(), - ) - .unwrap(); + address: p.address(), + rssi: p.properties().await.unwrap().unwrap().rssi.unwrap(), + }; + app_handle + .emit_all("device-discovered", Some(device)) + .unwrap(); + } + DeviceDisconnected(_) => { + // 在这里引用 self + let mut peripheral = self_clone.peripheral.lock().await; + *peripheral = None; + } + _ => {} + } } + }); + } + // implements a Clone + pub fn clone(&self) -> Self { + Self { + _is_events_registered: self._is_events_registered.clone(), + central: self.central.clone(), + peripheral: self.peripheral.clone(), } - }); - - Ok(true) + } } + +#[tauri::command] +async fn register_central_events<'a>( + app_handle: AppHandle, + connection: State<'a, BleConnection>, +) -> Result { + if *connection._is_events_registered.lock().await { + return Ok(false); + } else { + connection.register_central_events(&app_handle).await; + *connection._is_events_registered.lock().await = true; + Ok(true) + } +} + #[tauri::command] async fn start_scan(connection: State<'_, BleConnection>) -> Result { - let central = connection.central.lock().await; - let central = central.as_ref().unwrap(); - central - .start_scan(ScanFilter::default()) - .await - .unwrap_or_else(|_| { - println!("Failed to start scan"); - }); - Ok(true) + let err = connection.start_scan().await; + if let Err(e) = err { + return Err(e.to_string()); + } else { + Ok(true) + } } #[tauri::command] async fn stop_scan(connection: State<'_, BleConnection>) -> Result { - let central = connection.central.lock().await; - let central = central.as_ref().unwrap(); - central.stop_scan().await.unwrap_or_else(|_| { - println!("Failed to stop scan"); - }); - Ok(true) + let err = connection.stop_scan().await; + if let Err(e) = err { + return Err(e.to_string()); + } else { + Ok(true) + } } #[tauri::command] -async fn connect( - address: String, - connection: State<'_, BleConnection>, - app_handle: AppHandle, -) -> Result { - let central = connection.central.lock().await; - let central = central.as_ref().unwrap(); - - let peripheral = central - .peripherals() - .await - .unwrap() - .into_iter() - .find(|p| p.address().to_string() == address) - .unwrap() - .clone(); - peripheral.connect().await.unwrap(); - - *connection.peripheral.lock().await = Some(peripheral.clone()); - - peripheral.discover_services().await.unwrap_or_else(|_| { - println!("Failed to discover services"); - }); - let service = peripheral - .services() - .into_iter() - .find(|s| s.uuid == uuid_from_u16(0x180D)) - .unwrap(); - let characteristic = service - .characteristics - .into_iter() - .find(|c| c.uuid == uuid_from_u16(0x2A37)) - .unwrap(); - - peripheral - .subscribe(&characteristic) - .await - .unwrap_or_else(|_| { - println!("Failed to subscribe to characteristic"); - }); - - tokio::spawn(async move { - let mut notification_stream = peripheral.notifications().await.unwrap(); - while let Some(notification) = notification_stream.next().await { - if notification.uuid == uuid_from_u16(0x2A37) { - app_handle - .emit_all("heart-rate", notification.value[1]) - .unwrap(); - } - } - }); - - Ok(true) +async fn is_connected(connection: State<'_, BleConnection>) -> Result { + let status = connection.is_connected().await; + Ok(status) } #[tauri::command] -async fn disconnect(connection: State<'_, BleConnection>) -> Result { - let connection = connection.peripheral.lock().await; - let connection = connection.as_ref().unwrap(); - - connection.disconnect().await.unwrap(); - - Ok(true) -} - -#[tauri::command] -async fn get_connected_device(connection: State<'_, BleConnection>) -> Result { - let connection = connection.peripheral.lock().await; - let connection = connection.as_ref().unwrap(); - - Ok(serde_json::to_string(&BleDevice { - name: connection +async fn get_connected_device(connection: State<'_, BleConnection>) -> Result { + let peripheral = connection.peripheral.lock().await; + let device = BleDevice { + name: peripheral + .as_ref() + .unwrap() .properties() .await .unwrap() .unwrap() .local_name .unwrap_or("Unknown".to_string()), - address: connection.address().to_string(), - }) - .unwrap()) + address: peripheral.as_ref().unwrap().address(), + rssi: peripheral + .as_ref() + .unwrap() + .properties() + .await + .unwrap() + .unwrap() + .rssi + .unwrap(), + }; + Ok(device) +} + +#[tauri::command] +async fn connect( + address: BDAddr, + connection: State<'_, BleConnection>, + app_handle: AppHandle, +) -> Result { + if let Err(e) = connection.connect(address, &app_handle).await { + Err(e.to_string()) + } else { + Ok(true) + } +} + +#[tauri::command] +async fn disconnect(connection: State<'_, BleConnection>) -> Result { + if let Err(e) = connection.disconnect().await { + Err(e.to_string()) + } else { + Ok(true) + } } #[tokio::main] @@ -197,19 +271,16 @@ async fn main() { .await .unwrap() .into_iter() - .next() + .nth(0) .unwrap(); tauri::Builder::default() - .manage(BleConnection { - central: Mutex::new(Some(central)), - peripheral: Default::default(), - scan_devices: Default::default(), - }) + .manage(BleConnection::new(central)) .invoke_handler(tauri::generate_handler![ - request_central_events, + register_central_events, start_scan, stop_scan, + is_connected, connect, disconnect, get_connected_device diff --git a/src/App.vue b/src/App.vue index ba6767e..c091e76 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,10 +1,11 @@ @@ -38,6 +44,7 @@ listen("device-connected", (event) => { @import './assets/css/style.css'; :root { + --primary-color: #F25E86; --app-background: #f8f8f8; --title-bar-background: #e4e4e4; @@ -51,6 +58,24 @@ listen("device-connected", (event) => { --content-background: var(--app-background); } +::-webkit-scrollbar { + --bar-width: 8px; + width: var(--bar-width); + height: var(--bar-width); +} + +::-webkit-scrollbar-track { + background-color: transparent; +} + +::-webkit-scrollbar-thumb { + --bar-color: rgba(0, 0, 0, .2); + background-color: var(--bar-color); + border-radius: 20px; + background-clip: content-box; + border: 1px solid transparent; +} + /* Scale */ .scale-enter-active, .scale-leave-active { diff --git a/src/assets/css/style.css b/src/assets/css/style.css index d93f3d4..a2aaed7 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -17,4 +17,104 @@ .allow-select { @apply select-text; } +} + +.header { + position: sticky; + top: 0; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 12px; + background: var(--app-background) linear-gradient(90deg, #f25e8500 0%, #f25e851c 50%, #f25e851c 100%); + @apply border-b; +} + +.header .title { + font-size: 18px; + font-weight: normal; +} + +.header .actions { + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; +} + +.header .actions .additional { + display: flex; + flex-direction: row; + align-items: center; + font-size: 12px; + color: rgb(150, 150, 150); +} + +.additional>* { + margin-left: 4px; +} + +.header .actions .additional .accent { + font-size: 22px; + font-weight: bold; + color: #F25E86; + margin-left: 6px; +} + +.header .actions>*:first-child { + margin-left: 0; +} + +.header .actions>* { + margin-left: 4px; +} + +.btn { + border: none; + outline: none; + padding: 6px 12px; + min-width: 30px; + border-radius: 4px; + cursor: pointer; + text-transform: uppercase; + color: #fff; + background-color: #F25E86; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -ms-border-radius: 4px; + -o-border-radius: 4px; + box-shadow: 0 0 6px 1px rgb(0 0 0 / 20%); + transition: all .3s ease; + -webkit-transition: all .3s ease; + -moz-transition: all .3s ease; + -ms-transition: all .3s ease; + -o-transition: all .3s ease; + @apply text-xs; +} + +.btn.outline { + background-color: transparent; + color: #F25E86; + border: 1px solid #F25E86; + box-shadow: none; +} + +.btn:hover { + box-shadow: 2px 2px 6px 1px rgb(0 0 0 / 20%); +} + +.btn:active { + box-shadow: 2px 2px 6px 1px rgb(0 0 0 / 20%), 2px 2px 6px 1px rgb(0 0 0 / 20%) inset; +} + +.btn:disabled { + box-shadow: none; + background-color: rgba(0, 0, 0, 0.08); + color: #8f8f8f; + cursor: default; +} + +.btn.outline:disabled { + border: 1px solid rgba(0, 0, 0, 0.1); } \ No newline at end of file diff --git a/src/components/DrawerContainer.vue b/src/components/DrawerContainer.vue index 4ec78ac..6e74dbb 100644 --- a/src/components/DrawerContainer.vue +++ b/src/components/DrawerContainer.vue @@ -1,5 +1,8 @@ - - diff --git a/src/components/PageContainer.vue b/src/components/PageContainer.vue new file mode 100644 index 0000000..5885a4b --- /dev/null +++ b/src/components/PageContainer.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/components/SignalIndicator.vue b/src/components/SignalIndicator.vue new file mode 100644 index 0000000..e23daac --- /dev/null +++ b/src/components/SignalIndicator.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/components/icons/SystemUiconsSignalFull.vue b/src/components/icons/SystemUiconsSignalFull.vue new file mode 100644 index 0000000..3aa0077 --- /dev/null +++ b/src/components/icons/SystemUiconsSignalFull.vue @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/src/components/icons/SystemUiconsSignalLow.vue b/src/components/icons/SystemUiconsSignalLow.vue new file mode 100644 index 0000000..1cc078b --- /dev/null +++ b/src/components/icons/SystemUiconsSignalLow.vue @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/src/components/icons/SystemUiconsSignalMedium.vue b/src/components/icons/SystemUiconsSignalMedium.vue new file mode 100644 index 0000000..1ff82ff --- /dev/null +++ b/src/components/icons/SystemUiconsSignalMedium.vue @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/src/components/icons/TablerBluetooth.vue b/src/components/icons/TablerBluetooth.vue new file mode 100644 index 0000000..f5d5a70 --- /dev/null +++ b/src/components/icons/TablerBluetooth.vue @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/src/components/icons/TablerBluetoothConnected.vue b/src/components/icons/TablerBluetoothConnected.vue new file mode 100644 index 0000000..4ec954b --- /dev/null +++ b/src/components/icons/TablerBluetoothConnected.vue @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/src/components/icons/TablerBluetoothOff.vue b/src/components/icons/TablerBluetoothOff.vue new file mode 100644 index 0000000..bc8275f --- /dev/null +++ b/src/components/icons/TablerBluetoothOff.vue @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/src/components/icons/TablerBluetoothX.vue b/src/components/icons/TablerBluetoothX.vue new file mode 100644 index 0000000..49ea5c2 --- /dev/null +++ b/src/components/icons/TablerBluetoothX.vue @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/src/components/icons/TablerReload.vue b/src/components/icons/TablerReload.vue new file mode 100644 index 0000000..ec8e5c4 --- /dev/null +++ b/src/components/icons/TablerReload.vue @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 539b130..449fd1f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,6 @@ import { createApp } from "vue"; import { createRouter, createWebHistory } from "vue-router"; +import { createPinia } from "pinia"; import App from "./App.vue"; @@ -7,7 +8,10 @@ const routes = [ { path: "/", component: () => import("./pages/index.vue") }, { path: "/charts", component: () => import("./pages/charts.vue") }, { path: "/widgets", component: () => import("./pages/widgets.vue") }, - { path: "/streaming-plugins", component: () => import("./pages/streaming-plugins.vue") }, + { + path: "/streaming-plugins", + component: () => import("./pages/streaming-plugins.vue"), + }, { path: "/settings", component: () => import("./pages/settings.vue") }, ]; @@ -16,4 +20,5 @@ const router = createRouter({ routes, }); -createApp(App).use(router).mount("#app"); +const pinia = createPinia(); +createApp(App).use(router).use(pinia).mount("#app"); diff --git a/src/pages/index.vue b/src/pages/index.vue index 2944698..118a447 100644 --- a/src/pages/index.vue +++ b/src/pages/index.vue @@ -1,12 +1,73 @@ diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 0000000..919e5d1 --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,71 @@ +import { defineStore } from "pinia"; +import { ref, watchEffect } from "vue"; +import { invoke } from "@tauri-apps/api/tauri"; + +export interface Device { + name: string; + address: string; + rssi: number; +} + +export const useBrcatStore = defineStore("brcat", () => { + const scanning_devices = ref([]); + + const is_connected = ref(false); + const connected_device = ref(null); + + const is_scanning = ref(false); + + setInterval(async () => { + is_connected.value = await invoke("is_connected"); + }, 500); + + watchEffect(async () => { + scanning_devices.value = []; + if (is_connected.value) { + connected_device.value = await invoke("get_connected_device"); + } else { + connected_device.value = null; + is_scanning.value = false; + setTimeout(() => { + startScan(); + }, 500); + } + stopScan(); + }); + + function pushDevice(device: Device) { + if (scanning_devices.value.some((d) => d.address === device.address)) { + scanning_devices.value = scanning_devices.value.map((d) => + d.address === device.address ? device : d + ); + } else { + scanning_devices.value.push(device); + } + // scanning_devices.value = scanning_devices.value.sort( + // (a, b) => b.rssi - a.rssi + // ); + } + + function startScan() { + invoke("start_scan"); + is_scanning.value = true; + console.log("start scan"); + } + + function stopScan() { + invoke("stop_scan"); + is_scanning.value = false; + console.log("stop scan"); + } + + return { + is_connected, + is_scanning, + connected_device, + scanning_devices, + pushDevice, + startScan, + stopScan, + }; +}); diff --git a/tailwind.config.js b/tailwind.config.js deleted file mode 100644 index 45ce112..0000000 --- a/tailwind.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -export default { - content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], - theme: { - extend: {}, - }, - plugins: [], -}; diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..fc6e630 --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,18 @@ +import { Config } from "tailwindcss"; + +export default { + content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], + theme: { + extend: { + fontSize: { + "2xs": [ + "0.625rem", + { + lineHeight: "0.625rem", + }, + ], + }, + }, + }, + plugins: [], +};