Skip to content

Commit a8a51d7

Browse files
feat: Add local network server for cross-device link management (#133)
* Initial plan * Add local network server infrastructure Co-authored-by: yogeshpaliyal <[email protected]> * Update documentation for local network server feature Co-authored-by: yogeshpaliyal <[email protected]> * feat: add initial HTML interface for Deepr Link Manager * feat: implement tag management and filtering for links * feat: add ServerStatusBar component and integrate with navigation --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: yogeshpaliyal <[email protected]> Co-authored-by: Yogesh Choudhary Paliyal <[email protected]>
1 parent 26b90ed commit a8a51d7

File tree

15 files changed

+1594
-38
lines changed

15 files changed

+1594
-38
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@
2121
- Organize links by tags
2222
- Save link by sharing from other app (eg: chrome, etc.)
2323
- Save links to markdown file in local storage. (can be used for obsidian)
24+
- **Local network server:** Access and manage links from other devices on the same network
25+
26+
### 🌐 Local Network Server
27+
28+
The local network server feature allows you to access and manage your links from other devices on the same network. This is useful for:
29+
- Adding links from your desktop browser to your mobile device
30+
- Viewing your saved links on a bigger screen
31+
- Integrating with automation tools and scripts
32+
33+
**Usage:**
34+
1. Open the app and go to Settings
35+
2. Tap on "Local Network Server"
36+
3. Toggle the server switch to start it
37+
4. Use the displayed URL or scan the QR code from another device
38+
5. Access the web interface or use the REST API
39+
40+
**API Endpoints:**
41+
- `GET /api/links` - Get all saved links
42+
- `POST /api/links` - Add a new link (JSON body: `{"link": "url", "name": "name"}`)
43+
- `GET /api/link-info?url=<url>` - Get metadata for a URL
44+
45+
**Note:** Both devices must be on the same Wi-Fi network.
2446

2547
## 🏗️ Tech Stack
2648

@@ -32,6 +54,7 @@ The application is built using modern Android development practices and librarie
3254
- **Database:** SQLDelight
3355
- **Dependency Injection:** Koin
3456
- **Asynchronous Operations:** Kotlin Coroutines
57+
- **HTTP Client & Server:** Ktor
3558

3659
## 📲 Download
3760
You can download from any of the sources mentioned below.

app/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
alias(libs.plugins.android.application)
33
alias(libs.plugins.kotlin.android)
44
alias(libs.plugins.kotlin.compose)
5+
alias(libs.plugins.kotlin.serialization)
56
alias(libs.plugins.sqldelight)
67
id("org.jmailen.kotlinter")
78
}
@@ -115,6 +116,10 @@ dependencies {
115116
implementation(libs.jsoup)
116117
implementation(libs.ktor.client.core)
117118
implementation(libs.ktor.client.cio)
119+
implementation(libs.ktor.server.core)
120+
implementation(libs.ktor.server.cio)
121+
implementation(libs.ktor.server.content.negotiation)
122+
implementation(libs.ktor.serialization.kotlinx.json)
118123
implementation(libs.coil.compose)
119124
implementation(libs.coil.network.ktor3)
120125
implementation(libs.ktor.client.android)

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919

2020
<uses-permission android:name="android.permission.INTERNET" />
21+
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
22+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2123

2224
<application
2325
android:name=".DeeprApplication"

app/src/main/assets/index.html

Lines changed: 752 additions & 0 deletions
Large diffs are not rendered by default.

app/src/main/java/com/yogeshpaliyal/deepr/DeeprApplication.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ import com.yogeshpaliyal.deepr.backup.ImportRepositoryImpl
1212
import com.yogeshpaliyal.deepr.data.HtmlParser
1313
import com.yogeshpaliyal.deepr.data.NetworkRepository
1414
import com.yogeshpaliyal.deepr.preference.AppPreferenceDataStore
15+
import com.yogeshpaliyal.deepr.server.LocalServerRepository
16+
import com.yogeshpaliyal.deepr.server.LocalServerRepositoryImpl
1517
import com.yogeshpaliyal.deepr.sync.SyncRepository
1618
import com.yogeshpaliyal.deepr.sync.SyncRepositoryImpl
1719
import com.yogeshpaliyal.deepr.viewmodel.AccountViewModel
20+
import com.yogeshpaliyal.deepr.viewmodel.LocalServerViewModel
1821
import io.ktor.client.HttpClient
1922
import io.ktor.client.engine.cio.CIO
2023
import org.koin.android.ext.koin.androidContext
@@ -74,6 +77,14 @@ class DeeprApplication : Application() {
7477
single {
7578
NetworkRepository(get(), get())
7679
}
80+
81+
single<LocalServerRepository> {
82+
LocalServerRepositoryImpl(androidContext(), get(), get(), get())
83+
}
84+
85+
viewModel {
86+
LocalServerViewModel(get())
87+
}
7788
}
7889

7990
startKoin {

app/src/main/java/com/yogeshpaliyal/deepr/MainActivity.kt

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.os.Bundle
66
import androidx.activity.ComponentActivity
77
import androidx.activity.compose.setContent
88
import androidx.activity.enableEdgeToEdge
9+
import androidx.compose.foundation.layout.Column
910
import androidx.compose.material3.ExperimentalMaterial3Api
1011
import androidx.compose.material3.Surface
1112
import androidx.compose.material3.Text
@@ -23,17 +24,17 @@ import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator
2324
import com.yogeshpaliyal.deepr.preference.AppPreferenceDataStore
2425
import com.yogeshpaliyal.deepr.ui.screens.AboutUs
2526
import com.yogeshpaliyal.deepr.ui.screens.AboutUsScreen
27+
import com.yogeshpaliyal.deepr.ui.screens.LocalNetworkServer
28+
import com.yogeshpaliyal.deepr.ui.screens.LocalNetworkServerScreen
2629
import com.yogeshpaliyal.deepr.ui.screens.Settings
2730
import com.yogeshpaliyal.deepr.ui.screens.SettingsScreen
2831
import com.yogeshpaliyal.deepr.ui.screens.home.Home
2932
import com.yogeshpaliyal.deepr.ui.screens.home.HomeScreen
3033
import com.yogeshpaliyal.deepr.ui.theme.DeeprTheme
3134
import com.yogeshpaliyal.deepr.util.LanguageUtil
32-
import com.yogeshpaliyal.deepr.viewmodel.AccountViewModel
3335
import kotlinx.coroutines.flow.MutableStateFlow
3436
import kotlinx.coroutines.flow.first
3537
import kotlinx.coroutines.runBlocking
36-
import org.koin.androidx.viewmodel.ext.android.viewModel
3738

3839
data class SharedLink(
3940
val url: String,
@@ -42,7 +43,6 @@ data class SharedLink(
4243

4344
class MainActivity : ComponentActivity() {
4445
val sharingLink = MutableStateFlow<SharedLink?>(null)
45-
private val accountViewModel: AccountViewModel by viewModel()
4646

4747
override fun attachBaseContext(newBase: Context?) {
4848
super.attachBaseContext(
@@ -58,7 +58,7 @@ class MainActivity : ComponentActivity() {
5858
} else {
5959
context
6060
}
61-
} catch (e: Exception) {
61+
} catch (_: Exception) {
6262
context
6363
}
6464
},
@@ -121,41 +121,47 @@ fun Dashboard(
121121
) {
122122
val backStack = remember(sharedText) { mutableStateListOf<Any>(Home) }
123123

124-
NavDisplay(
125-
backStack = backStack,
126-
modifier = modifier,
127-
entryDecorators =
128-
listOf(
129-
// Add the default decorators for managing scenes and saving state
130-
rememberSceneSetupNavEntryDecorator(),
131-
rememberSavedStateNavEntryDecorator(),
132-
// Then add the view model store decorator
133-
rememberViewModelStoreNavEntryDecorator(),
134-
),
135-
onBack = { backStack.removeLastOrNull() },
136-
entryProvider = { key ->
137-
when (key) {
138-
is Home ->
139-
NavEntry(key) {
140-
HomeScreen(
141-
backStack,
142-
sharedText = sharedText,
143-
resetSharedText = resetSharedText,
144-
)
145-
}
124+
Column(modifier = modifier) {
125+
NavDisplay(
126+
backStack = backStack,
127+
entryDecorators =
128+
listOf(
129+
// Add the default decorators for managing scenes and saving state
130+
rememberSceneSetupNavEntryDecorator(),
131+
rememberSavedStateNavEntryDecorator(),
132+
// Then add the view model store decorator
133+
rememberViewModelStoreNavEntryDecorator(),
134+
),
135+
onBack = { backStack.removeLastOrNull() },
136+
entryProvider = { key ->
137+
when (key) {
138+
is Home ->
139+
NavEntry(key) {
140+
HomeScreen(
141+
backStack,
142+
sharedText = sharedText,
143+
resetSharedText = resetSharedText,
144+
)
145+
}
146146

147-
is Settings ->
148-
NavEntry(key) {
149-
SettingsScreen(backStack)
150-
}
147+
is Settings ->
148+
NavEntry(key) {
149+
SettingsScreen(backStack)
150+
}
151151

152-
is AboutUs ->
153-
NavEntry(key) {
154-
AboutUsScreen(backStack)
155-
}
152+
is AboutUs ->
153+
NavEntry(key) {
154+
AboutUsScreen(backStack)
155+
}
156156

157-
else -> NavEntry(Unit) { Text("Unknown route") }
158-
}
159-
},
160-
)
157+
is LocalNetworkServer ->
158+
NavEntry(key) {
159+
LocalNetworkServerScreen(backStack)
160+
}
161+
162+
else -> NavEntry(Unit) { Text("Unknown route") }
163+
}
164+
},
165+
)
166+
}
161167
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.yogeshpaliyal.deepr.server
2+
3+
import kotlinx.coroutines.flow.StateFlow
4+
5+
interface LocalServerRepository {
6+
val isRunning: StateFlow<Boolean>
7+
val serverUrl: StateFlow<String?>
8+
9+
suspend fun startServer()
10+
11+
suspend fun stopServer()
12+
}

0 commit comments

Comments
 (0)