Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ dependencies {
implementation(libs.ktor.server.cio)
implementation(libs.ktor.server.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.coil.compose)
implementation(libs.coil.network.ktor3)
implementation(libs.ktor.client.android)
Expand Down
19 changes: 11 additions & 8 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,6 @@
</intent>
</queries>

<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="*" />
</intent>
</queries>


<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Expand Down Expand Up @@ -67,6 +59,17 @@
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Local network server for managing deeplinks" />
</service>

<service
android:name=".server.TransferLinkLocalServerService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Local network server for transferring deeplinks" />
</service>

</application>

</manifest>
27 changes: 26 additions & 1 deletion app/src/main/java/com/yogeshpaliyal/deepr/DeeprApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ import com.yogeshpaliyal.deepr.data.NetworkRepository
import com.yogeshpaliyal.deepr.preference.AppPreferenceDataStore
import com.yogeshpaliyal.deepr.server.LocalServerRepository
import com.yogeshpaliyal.deepr.server.LocalServerRepositoryImpl
import com.yogeshpaliyal.deepr.server.TransferLinkLocalServerRepository
import com.yogeshpaliyal.deepr.server.TransferLinkLocalServerRepositoryImpl
import com.yogeshpaliyal.deepr.sync.SyncRepository
import com.yogeshpaliyal.deepr.sync.SyncRepositoryImpl
import com.yogeshpaliyal.deepr.viewmodel.AccountViewModel
import com.yogeshpaliyal.deepr.viewmodel.LocalServerViewModel
import com.yogeshpaliyal.deepr.viewmodel.TransferLinkLocalServerViewModel
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import org.koin.core.module.dsl.viewModel
Expand Down Expand Up @@ -68,7 +74,18 @@ class DeeprApplication : Application() {
single<AutoBackupWorker> { AutoBackupWorker(androidContext(), get(), get()) }

single {
HttpClient(CIO)
HttpClient(CIO) {
install(ContentNegotiation) {
// FIX: Explicitly call the Json function from kotlinx.serialization.json
json(
Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
},
)
}
}
}

viewModel { AccountViewModel(get(), get(), get(), get(), get(), get()) }
Expand All @@ -85,9 +102,17 @@ class DeeprApplication : Application() {
LocalServerRepositoryImpl(androidContext(), get(), get(), get())
}

single<TransferLinkLocalServerRepository> {
TransferLinkLocalServerRepositoryImpl(androidContext(), get(), get())
}

viewModel {
LocalServerViewModel(get())
}

viewModel {
TransferLinkLocalServerViewModel(get())
}
}

startKoin {
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/yogeshpaliyal/deepr/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import com.yogeshpaliyal.deepr.ui.screens.LocalNetworkServer
import com.yogeshpaliyal.deepr.ui.screens.LocalNetworkServerScreen
import com.yogeshpaliyal.deepr.ui.screens.Settings
import com.yogeshpaliyal.deepr.ui.screens.SettingsScreen
import com.yogeshpaliyal.deepr.ui.screens.TransferLinkLocalNetworkServer
import com.yogeshpaliyal.deepr.ui.screens.TransferLinkLocalServerScreen
import com.yogeshpaliyal.deepr.ui.screens.home.Home
import com.yogeshpaliyal.deepr.ui.screens.home.HomeScreen
import com.yogeshpaliyal.deepr.ui.theme.DeeprTheme
Expand Down Expand Up @@ -160,6 +162,11 @@ fun Dashboard(
LocalNetworkServerScreen(backStack)
}

is TransferLinkLocalNetworkServer ->
NavEntry(key) {
TransferLinkLocalServerScreen(backStack)
}

else -> NavEntry(Unit) { Text("Unknown route") }
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.net.wifi.WifiManager
import android.util.Log
import com.yogeshpaliyal.deepr.DeeprQueries
import com.yogeshpaliyal.deepr.Tags
import com.yogeshpaliyal.deepr.data.NetworkRepository
import com.yogeshpaliyal.deepr.viewmodel.AccountViewModel
import io.ktor.http.ContentType
Expand All @@ -24,6 +25,7 @@ import io.ktor.server.routing.routing
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.net.NetworkInterface
Expand All @@ -35,7 +37,8 @@ class LocalServerRepositoryImpl(
private val accountViewModel: AccountViewModel,
private val networkRepository: NetworkRepository,
) : LocalServerRepository {
private var server: EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration>? = null
private var server: EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration>? =
null
private val _isRunning = MutableStateFlow(false)
override val isRunning: StateFlow<Boolean> = _isRunning.asStateFlow()

Expand All @@ -45,7 +48,7 @@ class LocalServerRepositoryImpl(
private val port = 8080

override suspend fun startServer() {
if (_isRunning.value) {
if (isRunning.value) {
Log.d("LocalServer", "Server is already running")
return
}
Expand Down Expand Up @@ -121,13 +124,19 @@ class LocalServerRepositoryImpl(
createdAt = link.createdAt,
openedCount = link.openedCount,
notes = link.notes,
tags = link.tagsNames?.split(", ")?.filter { it.isNotEmpty() } ?: emptyList(),
tags =
link.tagsNames
?.split(", ")
?.filter { it.isNotEmpty() } ?: emptyList(),
)
}
call.respond(HttpStatusCode.OK, response)
} catch (e: Exception) {
Log.e("LocalServer", "Error getting links", e)
call.respond(HttpStatusCode.InternalServerError, ErrorResponse("Error getting links: ${e.message}"))
call.respond(
HttpStatusCode.InternalServerError,
ErrorResponse("Error getting links: ${e.message}"),
)
}
}

Expand All @@ -142,10 +151,16 @@ class LocalServerRepositoryImpl(
request.tags.map { it.toDbTag() },
request.notes,
)
call.respond(HttpStatusCode.Created, SuccessResponse("Link added successfully"))
call.respond(
HttpStatusCode.Created,
SuccessResponse("Link added successfully"),
)
} catch (e: Exception) {
Log.e("LocalServer", "Error adding link", e)
call.respond(HttpStatusCode.InternalServerError, ErrorResponse("Error adding link: ${e.message}"))
call.respond(
HttpStatusCode.InternalServerError,
ErrorResponse("Error adding link: ${e.message}"),
)
}
}

Expand Down Expand Up @@ -182,15 +197,21 @@ class LocalServerRepositoryImpl(
call.respond(HttpStatusCode.OK, response)
} catch (e: Exception) {
Log.e("LocalServer", "Error getting tags", e)
call.respond(HttpStatusCode.InternalServerError, ErrorResponse("Error getting tags: ${e.message}"))
call.respond(
HttpStatusCode.InternalServerError,
ErrorResponse("Error getting tags: ${e.message}"),
)
}
}

get("/api/link-info") {
try {
val url = call.request.queryParameters["url"]
if (url.isNullOrBlank()) {
call.respond(HttpStatusCode.BadRequest, ErrorResponse("URL parameter is required"))
call.respond(
HttpStatusCode.BadRequest,
ErrorResponse("URL parameter is required"),
)
return@get
}

Expand All @@ -212,29 +233,32 @@ class LocalServerRepositoryImpl(
}
} catch (e: Exception) {
Log.e("LocalServer", "Error getting link info", e)
call.respond(HttpStatusCode.InternalServerError, ErrorResponse("Error getting link info: ${e.message}"))
call.respond(
HttpStatusCode.InternalServerError,
ErrorResponse("Error getting link info: ${e.message}"),
)
}
}
}
}

server?.start(wait = false)
_isRunning.value = true
_serverUrl.value = "http://$ipAddress:$port"
_isRunning.update { true }
_serverUrl.update { "http://$ipAddress:$port" }
Log.d("LocalServer", "Server started at ${_serverUrl.value}")
} catch (e: Exception) {
Log.e("LocalServer", "Error starting server", e)
_isRunning.value = false
_serverUrl.value = null
_isRunning.update { false }
_serverUrl.update { null }
}
}

override suspend fun stopServer() {
try {
server?.stop(1000, 2000)
server = null
_isRunning.value = false
_serverUrl.value = null
_isRunning.update { false }
_serverUrl.update { null }
Log.d("LocalServer", "Server stopped")
} catch (e: Exception) {
Log.e("LocalServer", "Error stopping server", e)
Expand All @@ -244,7 +268,8 @@ class LocalServerRepositoryImpl(
private fun getIpAddress(): String? {
try {
// Try to get WiFi IP first
val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager
val wifiManager =
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager
wifiManager?.connectionInfo?.ipAddress?.let { ipInt ->
if (ipInt != 0) {
return String.format(
Expand Down Expand Up @@ -293,7 +318,7 @@ data class TagData(
val id: Long,
val name: String,
) {
fun toDbTag() = com.yogeshpaliyal.deepr.Tags(id, name)
fun toDbTag() = Tags(id, name)
}

@Serializable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.yogeshpaliyal.deepr.server

import kotlinx.coroutines.flow.StateFlow

interface TransferLinkLocalServerRepository {
val isRunning: StateFlow<Boolean>
val serverUrl: StateFlow<String?>
val qrCodeData: StateFlow<String?>

suspend fun startServer()

suspend fun stopServer()

suspend fun fetchAndImportFromSender(qrTransferInfo: QRTransferInfo): Result<Unit>
}
Loading