Skip to content

Commit 0ada142

Browse files
committed
Local domain
1 parent 12d20b4 commit 0ada142

File tree

33 files changed

+320
-100
lines changed

33 files changed

+320
-100
lines changed

app/build.gradle.kts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ android {
4141
else -> 0
4242
}
4343

44-
val vCode = 403
44+
val vCode = 406
4545
versionCode = vCode - singleAbiNum
46-
versionName = "2.1.7"
46+
versionName = "2.1.8"
4747

4848
ndk {
4949
//noinspection ChromeOsAbiSupport
@@ -221,4 +221,7 @@ dependencies {
221221

222222
// Google Tink for cryptography (Ed25519 support on all Android versions)
223223
implementation(libs.tink.android)
224+
225+
// JmDNS for mDNS service discovery
226+
implementation(libs.jmdns)
224227
}

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
1616
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
1717
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
18+
<uses-permission android:name="android.permission.MULTICAST" />
1819
<uses-permission
1920
android:name="android.permission.WRITE_SETTINGS"
2021
tools:ignore="ProtectedPermissions" />

app/src/main/java/com/ismartcoding/plain/MainApp.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.ismartcoding.plain.preferences.HttpPortPreference
2323
import com.ismartcoding.plain.preferences.HttpsPortPreference
2424
import com.ismartcoding.plain.preferences.HttpsPreference
2525
import com.ismartcoding.plain.preferences.KeyStorePasswordPreference
26+
import com.ismartcoding.plain.preferences.MdnsHostnamePreference
2627
import com.ismartcoding.plain.preferences.PasswordPreference
2728
import com.ismartcoding.plain.preferences.SignatureKeyPreference
2829
import com.ismartcoding.plain.preferences.UrlTokenPreference
@@ -62,6 +63,7 @@ class MainApp : Application() {
6263
KeyStorePasswordPreference.ensureValueAsync(instance, preferences)
6364
UrlTokenPreference.ensureValueAsync(instance, preferences)
6465
SignatureKeyPreference.ensureKeyPairAsync(instance, preferences)
66+
MdnsHostnamePreference.ensureValueAsync(preferences)
6567

6668
DarkThemePreference.setDarkMode(DarkTheme.parse(DarkThemePreference.get(preferences)))
6769
if (PlugInControlReceiver.isUSBConnected(this@MainApp)) {

app/src/main/java/com/ismartcoding/plain/TempData.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ object TempData {
1010
var httpPort: Int = 8080
1111
var httpsPort: Int = 8443
1212
var urlToken = "" // use to encrypt or decrypt params in url
13+
var mdnsHostname = "plainapp.local" // mDNS hostname for local network discovery
1314
val notifications = mutableListOf<DNotification>()
1415
var audioPlayMode = MediaPlayMode.REPEAT
1516

app/src/main/java/com/ismartcoding/plain/api/HttpClientManager.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.ismartcoding.plain.api
22

33
import android.util.Base64
44
import com.ismartcoding.lib.helpers.CryptoHelper
5+
import com.ismartcoding.lib.helpers.NetworkHelper
56
import com.ismartcoding.lib.logcat.LogCat
67
import com.ismartcoding.plain.MainApp
78
import com.ismartcoding.plain.TempData
@@ -125,6 +126,24 @@ object HttpClientManager {
125126
.build()
126127
}
127128

129+
fun createUnsafeOkHttpClient(): OkHttpClient {
130+
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
131+
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
132+
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
133+
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
134+
})
135+
val sslContext = SSLContext.getInstance("TLS")
136+
sslContext.init(null, trustAllCerts, SecureRandom())
137+
val sslSocketFactory = sslContext.socketFactory
138+
139+
return OkHttpClient.Builder()
140+
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
141+
.hostnameVerifier { hostname, _ ->
142+
NetworkHelper.isLocalNetworkAddress(hostname)
143+
}
144+
.build()
145+
}
146+
128147
private fun bodyToString(request: RequestBody): String {
129148
val buffer = okio.Buffer()
130149
request.writeTo(buffer)

app/src/main/java/com/ismartcoding/plain/chat/PeerFileDownloader.kt

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,19 @@ package com.ismartcoding.plain.chat
22

33
import android.content.Context
44
import com.ismartcoding.lib.extensions.scanFileByConnection
5-
import com.ismartcoding.lib.helpers.NetworkHelper
65
import com.ismartcoding.lib.logcat.LogCat
76
import com.ismartcoding.plain.MainApp
7+
import com.ismartcoding.plain.api.HttpClientManager
88
import com.ismartcoding.plain.db.AppDatabase
99
import com.ismartcoding.plain.db.ChatItemDataUpdate
1010
import com.ismartcoding.plain.db.DMessageFiles
1111
import com.ismartcoding.plain.db.DMessageImages
1212
import com.ismartcoding.plain.helpers.ChatFileSaveHelper
1313
import com.ismartcoding.plain.preferences.ChatFilesSaveFolderPreference
14-
import kotlinx.coroutines.flow.MutableStateFlow
15-
import okhttp3.OkHttpClient
1614
import okhttp3.Request
1715
import java.io.File
18-
import java.security.SecureRandom
19-
import java.security.cert.X509Certificate
20-
import javax.net.ssl.SSLContext
21-
import javax.net.ssl.TrustManager
22-
import javax.net.ssl.X509TrustManager
2316

2417
object PeerFileDownloader {
25-
private val _downloadProgress = MutableStateFlow<Map<String, DownloadProgress>>(emptyMap())
26-
27-
private fun createUnsafeOkHttpClient(): OkHttpClient {
28-
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
29-
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
30-
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
31-
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
32-
})
33-
val sslContext = SSLContext.getInstance("TLS")
34-
sslContext.init(null, trustAllCerts, SecureRandom())
35-
val sslSocketFactory = sslContext.socketFactory
36-
37-
return OkHttpClient.Builder()
38-
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
39-
.hostnameVerifier { hostname, _ ->
40-
NetworkHelper.isLocalNetworkAddress(hostname)
41-
}
42-
.build()
43-
}
44-
4518
suspend fun downloadAsync(
4619
context: Context,
4720
task: DownloadTask
@@ -63,7 +36,7 @@ object PeerFileDownloader {
6336

6437

6538
var downloadedBytes = 0L
66-
val client = createUnsafeOkHttpClient()
39+
val client = HttpClientManager.createUnsafeOkHttpClient()
6740
task.httpClient = client
6841

6942
val response = client.newCall(Request.Builder().url(downloadUrl).build()).execute()
@@ -161,10 +134,4 @@ object PeerFileDownloader {
161134
AppDatabase.instance.chatDao().updateData(ChatItemDataUpdate(messageId, content))
162135
LogCat.d("Updated message file URI: $originalUri -> $newLocalPath")
163136
}
164-
165-
private fun updateProgress(fileId: String, fileName: String, downloaded: Long, total: Long, status: DownloadStatus) {
166-
val currentProgress = _downloadProgress.value.toMutableMap()
167-
currentProgress[fileId] = DownloadProgress(fileId, fileName, downloaded, total, status)
168-
_downloadProgress.value = currentProgress
169-
}
170137
}

app/src/main/java/com/ismartcoding/plain/preferences/Preferences.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,3 +757,14 @@ object NearbyDiscoverablePreference : BasePreference<Boolean>() {
757757
override val default = false
758758
override val key = booleanPreferencesKey("nearby_discoverable")
759759
}
760+
761+
object MdnsHostnamePreference : BasePreference<String>() {
762+
override val default = "plainapp.local"
763+
override val key = stringPreferencesKey("mdns_hostname")
764+
765+
suspend fun ensureValueAsync(
766+
preferences: Preferences,
767+
) {
768+
TempData.mdnsHostname = get(preferences).ifEmpty { default }
769+
}
770+
}

app/src/main/java/com/ismartcoding/plain/services/HttpServerService.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.ismartcoding.plain.features.locale.LocaleHelper
2323
import com.ismartcoding.plain.helpers.NotificationHelper
2424
import com.ismartcoding.plain.helpers.UrlHelper
2525
import com.ismartcoding.plain.web.HttpServerManager
26+
import com.ismartcoding.plain.web.NsdHelper
2627
import io.ktor.client.request.get
2728
import io.ktor.http.HttpStatusCode
2829
import kotlinx.coroutines.delay
@@ -113,6 +114,7 @@ class HttpServerService : LifecycleService() {
113114
if (checkResult.websocket && checkResult.http) {
114115
HttpServerManager.httpServerError = ""
115116
HttpServerManager.portsInUse.clear()
117+
NsdHelper.registerService(this, TempData.httpPort)
116118
sendEvent(HttpServerStateChangedEvent(HttpServerState.ON))
117119
} else {
118120
if (!checkResult.http) {
@@ -145,12 +147,17 @@ class HttpServerService : LifecycleService() {
145147

146148
override fun onDestroy() {
147149
super.onDestroy()
150+
// Ensure NSD service is unregistered
151+
NsdHelper.unregisterService()
148152
stopForeground(STOP_FOREGROUND_REMOVE)
149153
}
150154

151155
private suspend fun stopHttpServerAsync() {
152156
LogCat.d("stopHttpServer")
153157
try {
158+
// Unregister NSD service
159+
NsdHelper.unregisterService()
160+
154161
val client = HttpClientManager.httpClient()
155162
val r = client.get(UrlHelper.getShutdownUrl())
156163
if (r.status == HttpStatusCode.Gone) {

app/src/main/java/com/ismartcoding/plain/ui/base/TextFieldDialog.kt

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import androidx.compose.material3.MaterialTheme
99
import androidx.compose.material3.Text
1010
import androidx.compose.material3.TextButton
1111
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.getValue
13+
import androidx.compose.runtime.mutableStateOf
14+
import androidx.compose.runtime.remember
15+
import androidx.compose.runtime.setValue
1216
import androidx.compose.ui.Modifier
1317
import androidx.compose.ui.graphics.vector.ImageVector
1418
import androidx.compose.ui.platform.LocalFocusManager
@@ -30,6 +34,8 @@ fun TextFieldDialog(
3034
errorText: String = "",
3135
dismissText: String = stringResource(R.string.cancel),
3236
confirmText: String = stringResource(R.string.confirm),
37+
validator: (String) -> Boolean = { true },
38+
validationErrorText: String = "",
3339
onValueChange: (String) -> Unit = {},
3440
onDismissRequest: () -> Unit = {},
3541
onConfirm: (String) -> Unit = {},
@@ -39,6 +45,10 @@ fun TextFieldDialog(
3945
),
4046
) {
4147
val focusManager = LocalFocusManager.current
48+
var currentValue by remember { mutableStateOf(value) }
49+
var showValidationError by remember { mutableStateOf(false) }
50+
val displayErrorText = if (showValidationError) validationErrorText else errorText
51+
4252
AlertDialog(
4353
modifier = modifier.fillMaxWidth(),
4454
containerColor = MaterialTheme.colorScheme.surface,
@@ -61,24 +71,38 @@ fun TextFieldDialog(
6171
ClipboardTextField(
6272
modifier = modifier,
6373
readOnly = readOnly,
64-
value = value,
74+
value = currentValue,
6575
singleLine = singleLine,
66-
onValueChange = onValueChange,
76+
onValueChange = {
77+
currentValue = it
78+
showValidationError = false
79+
onValueChange(it)
80+
},
6781
placeholder = placeholder,
6882
isPassword = isPassword,
69-
errorText = errorText,
83+
errorText = displayErrorText,
7084
keyboardOptions = keyboardOptions,
7185
focusManager = focusManager,
7286
requestFocus = true,
73-
onConfirm = onConfirm,
87+
onConfirm = {
88+
if (validator(it)) {
89+
onConfirm(it)
90+
} else {
91+
showValidationError = true
92+
}
93+
},
7494
)
7595
},
7696
confirmButton = {
7797
Button(
78-
enabled = value.isNotBlank(),
98+
enabled = currentValue.isNotBlank(),
7999
onClick = {
80-
focusManager.clearFocus()
81-
onConfirm(value)
100+
if (validator(currentValue)) {
101+
focusManager.clearFocus()
102+
onConfirm(currentValue)
103+
} else {
104+
showValidationError = true
105+
}
82106
},
83107
) {
84108
Text(confirmText)

app/src/main/java/com/ismartcoding/plain/ui/base/coil/ImageLoader.kt

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,12 @@ import coil3.request.crossfade
1212
import coil3.svg.SvgDecoder
1313
import coil3.util.DebugLogger
1414
import com.ismartcoding.plain.activityManager
15-
import okhttp3.OkHttpClient
16-
import java.security.SecureRandom
17-
import java.security.cert.X509Certificate
18-
import javax.net.ssl.SSLContext
19-
import javax.net.ssl.TrustManager
20-
import javax.net.ssl.X509TrustManager
15+
import com.ismartcoding.plain.api.HttpClientManager
2116

2217
fun newImageLoader(context: PlatformContext): ImageLoader {
2318
val memoryPercent = if (activityManager.isLowRamDevice) 0.25 else 0.75
2419

25-
// Create unsafe OkHttp client for SSL bypass
26-
val unsafeOkHttpClient = createUnsafeOkHttpClient()
20+
val unsafeOkHttpClient = HttpClientManager.createUnsafeOkHttpClient()
2721

2822
return ImageLoader.Builder(context)
2923
.components {
@@ -48,48 +42,3 @@ fun newImageLoader(context: PlatformContext): ImageLoader {
4842
.logger(DebugLogger())
4943
.build()
5044
}
51-
52-
private fun createUnsafeOkHttpClient(): OkHttpClient {
53-
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
54-
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
55-
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
56-
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
57-
})
58-
val sslContext = SSLContext.getInstance("TLS")
59-
sslContext.init(null, trustAllCerts, SecureRandom())
60-
val sslSocketFactory = sslContext.socketFactory
61-
62-
return OkHttpClient.Builder()
63-
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
64-
.hostnameVerifier { hostname, _ ->
65-
// 只对局域网 IP 跳过 SSL 验证
66-
isLocalNetworkAddress(hostname)
67-
}
68-
.build()
69-
}
70-
71-
private fun isLocalNetworkAddress(hostname: String): Boolean {
72-
return when {
73-
hostname == "localhost" -> true
74-
hostname == "127.0.0.1" -> true
75-
hostname.startsWith("192.168.") -> true
76-
hostname.startsWith("10.") -> true
77-
hostname.startsWith("172.16.") -> true
78-
hostname.startsWith("172.17.") -> true
79-
hostname.startsWith("172.18.") -> true
80-
hostname.startsWith("172.19.") -> true
81-
hostname.startsWith("172.20.") -> true
82-
hostname.startsWith("172.21.") -> true
83-
hostname.startsWith("172.22.") -> true
84-
hostname.startsWith("172.23.") -> true
85-
hostname.startsWith("172.24.") -> true
86-
hostname.startsWith("172.25.") -> true
87-
hostname.startsWith("172.26.") -> true
88-
hostname.startsWith("172.27.") -> true
89-
hostname.startsWith("172.28.") -> true
90-
hostname.startsWith("172.29.") -> true
91-
hostname.startsWith("172.30.") -> true
92-
hostname.startsWith("172.31.") -> true
93-
else -> false
94-
}
95-
}

0 commit comments

Comments
 (0)