Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ object AppConfig {
const val MSG_STATE_STOP_SUCCESS = 41
const val MSG_STATE_RESTART = 5
const val MSG_MEASURE_DELAY = 6
const val MSG_MEASURE_DELAY_SUCCESS = 61
const val MSG_MEASURE_DELAY_RESULT = 61
const val MSG_MEASURE_CONFIG_START = 7
const val MSG_MEASURE_CONFIG_CANCEL = 71
const val MSG_MEASURE_CONFIG_SUCCESS = 72
Expand Down
28 changes: 17 additions & 11 deletions V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import android.os.ParcelFileDescriptor
import android.system.OsConstants
import androidx.core.content.ContextCompat
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.contracts.IDialerService
import com.v2ray.ang.contracts.ServiceControl
import com.v2ray.ang.dto.ConnectionTestResult
import com.v2ray.ang.dto.OutboundTrafficStat
import com.v2ray.ang.dto.entities.ProfileItem
import com.v2ray.ang.enums.BrowserDialerMode
Expand Down Expand Up @@ -321,28 +321,34 @@ object CoreServiceManager {
time = coreController.measureDelay(SettingsManager.getDelayTestUrl())
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e)
errorStr = e.message?.substringAfter("\":") ?: "empty message"
errorStr = e.message?.substringAfter("\":").orEmpty()
}
if (time == -1L) {
try {
time = coreController.measureDelay(SettingsManager.getDelayTestUrl(true))
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e)
errorStr = e.message?.substringAfter("\":") ?: "empty message"
errorStr = e.message?.substringAfter("\":").orEmpty()
}
}

val result = if (time >= 0) {
service.getString(R.string.connection_test_available, time)
} else {
service.getString(R.string.connection_test_error, errorStr)
}
MessageHelper.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, result)
val result = ConnectionTestResult(
delayMillis = time,
errorMessage = errorStr,
)
MessageHelper.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_RESULT, result)

// Only fetch IP info if the delay test was successful
if (time >= 0) {
SpeedtestManager.getRemoteIPInfo()?.let { ip ->
MessageHelper.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, "$result\n$ip")
MessageHelper.sendMsg2UI(
service,
AppConfig.MSG_MEASURE_DELAY_RESULT,
result.copy(
country = ip.country,
ipAddress = ip.ipAddress,
),
)
}
}
}
Expand Down Expand Up @@ -492,4 +498,4 @@ object CoreServiceManager {
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.v2ray.ang.dto

import java.io.Serializable

/** Locale-neutral result sent from the daemon for presentation by the UI process. */
data class ConnectionTestResult(
val delayMillis: Long,
val errorMessage: String = "",
val country: String? = null,
val ipAddress: String? = null,
) : Serializable
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
package com.v2ray.ang.dto.entities

data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) {
fun getTestDelayString(): String {
if (testDelayMillis == 0L) {
return ""
}
return testDelayMillis.toString() + "ms"
}
}
data class ServerAffiliationInfo(var testDelayMillis: Long = 0L)
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ data class ServersCache(
val guid: String,
val profile: ProfileItem,
val testDelayMillis: Long = 0L,
val testDelayString: String = "",
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import java.net.UnknownHostException

object SpeedtestManager {

data class RemoteEndpointInfo(
val country: String?,
val ipAddress: String?,
)

/**
* Measures the time taken to establish a TCP connection to a given URL and port.
*
Expand Down Expand Up @@ -48,7 +53,7 @@ object SpeedtestManager {
return -1
}

fun getRemoteIPInfo(): String? {
fun getRemoteIPInfo(): RemoteEndpointInfo? {
val url = MmkvManager.decodeSettingsString(AppConfig.PREF_IP_API_URL)
.takeIf { !it.isNullOrBlank() } ?: AppConfig.IP_API_URL

Expand Down Expand Up @@ -81,6 +86,9 @@ object SpeedtestManager {
ipInfo.location?.country_code
).firstOrNull { !it.isNullOrBlank() }

return "(${country ?: "unknown"}) ${ip ?: "unknown"}"
return RemoteEndpointInfo(
country = country,
ipAddress = ip,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,13 @@ class CheckUpdateViewModel(application: Application) : BaseViewModel(application
toastSuccess(R.string.update_already_latest_version)
}
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to check for updates: ${e.message}")
if (e.message == null) {
toastError(R.string.toast_failure)
} else {
toastError(e.message.orEmpty())
}
LogUtil.e(AppConfig.TAG, "Failed to check for updates", e)
toastError(R.string.toast_failure)
}
}
}

fun dismissUpdateDialog() {
_showUpdateDialog.value = false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ fun FormDropdownField(
editable: Boolean = false,
enabled: Boolean = true,
placeholder: String? = null,
supportingText: String? = null,
) {
var expanded by rememberSaveable { mutableStateOf(false) }
val menuScrollState = rememberScrollState()
Expand All @@ -104,6 +105,7 @@ fun FormDropdownField(
enabled = enabled,
label = { Text(label) },
placeholder = { if (placeholder != null) Text(placeholder) },
supportingText = supportingText?.let { { Text(it) } },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastError
import com.v2ray.ang.ui.base.BaseComponentActivity
import com.v2ray.ang.ui.compose.AppTopBar
import com.v2ray.ang.ui.compose.ItemDivider
import com.v2ray.ang.ui.compose.verticalScrollbar
import com.v2ray.ang.util.LogUtil
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -91,8 +93,9 @@ class LogcatActivity : BaseComponentActivity() {

uri to logFile.name
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to share Logcat", e)
withContext(Dispatchers.Main) {
toast(e.localizedMessage ?: e.toString())
toastError(R.string.toast_failure)
}
return@launch
}
Expand Down
12 changes: 11 additions & 1 deletion V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainContract.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package com.v2ray.ang.ui.main

import com.v2ray.ang.dto.ConnectionTestResult
import com.v2ray.ang.dto.GroupMapItem
import com.v2ray.ang.dto.LocateTarget

/** Locale-neutral state formatted only when it reaches the main UI. */
sealed interface MainStatus {
data object Disconnected : MainStatus
data object Connected : MainStatus
data object Testing : MainStatus
data class TestProgress(val progress: String) : MainStatus
data class ConnectionTest(val result: ConnectionTestResult) : MainStatus
}

/**
* Main UI state
*/
Expand All @@ -12,7 +22,7 @@ data class MainUiState(
val selectedGuid: String? = null,
val isRunning: Boolean = false,
val isTesting: Boolean = false,
val statusText: String = "",
val status: MainStatus = MainStatus.Disconnected,
val locateTarget: LocateTarget? = null,
val confirmRemove: Boolean = false,
val doubleColumnDisplay: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import androidx.core.content.ContextCompat
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.ConnectionTestResult
import com.v2ray.ang.dto.SubscriptionUpdateResult
import com.v2ray.ang.dto.TestServiceMessage
import com.v2ray.ang.dto.entities.ProfileItem
import com.v2ray.ang.dto.entities.ServerAffiliationInfo
import com.v2ray.ang.dto.entities.SubscriptionCache
import com.v2ray.ang.dto.entities.SubscriptionItem
import com.v2ray.ang.extension.serializable
import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.AppLocaleManager
import com.v2ray.ang.handler.MmkvManager
Expand Down Expand Up @@ -52,14 +54,12 @@ class MainRepository(
AppConfig.MSG_STATE_RUNNING -> MainServiceEvent.StateRunning
AppConfig.MSG_STATE_NOT_RUNNING -> MainServiceEvent.StateNotRunning
AppConfig.MSG_STATE_START_SUCCESS -> MainServiceEvent.StateStartSuccess
AppConfig.MSG_STATE_START_FAILURE -> MainServiceEvent.StateStartFailure(
safeIntent.getStringExtra("content").orEmpty()
)
AppConfig.MSG_STATE_START_FAILURE -> MainServiceEvent.StateStartFailure

AppConfig.MSG_STATE_STOP_SUCCESS -> MainServiceEvent.StateStopSuccess
AppConfig.MSG_MEASURE_DELAY_SUCCESS -> MainServiceEvent.MeasureDelaySuccess(
safeIntent.getStringExtra("content").orEmpty()
)
AppConfig.MSG_MEASURE_DELAY_RESULT -> safeIntent
.serializable<ConnectionTestResult>("content")
?.let { MainServiceEvent.MeasureDelayResult(it) }

AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> MainServiceEvent.MeasureConfigSuccess
AppConfig.MSG_MEASURE_CONFIG_NOTIFY -> MainServiceEvent.MeasureConfigNotify(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fun MainScreen(
val groups = uiState.groups
val isLoading by mainViewModel.isLoading.collectAsStateWithLifecycle()
val isRunning = uiState.isRunning
val displayText = uiState.statusText
val displayText = mainViewModel.formatStatus(uiState.status)
val selectedGuid = uiState.selectedGuid
val doubleColumnDisplay = uiState.doubleColumnDisplay
val confirmRemove = uiState.confirmRemove
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.LineBreak
import androidx.compose.ui.text.style.TextOverflow
Expand Down Expand Up @@ -249,7 +250,6 @@ private fun ServerItemRow(
statistics = profile.description.nullIfBlank()
?: AngConfigManager.generateDescription(profile),
typeDescription = getProtocolDescription(profile),
testResult = serverCache.testDelayString,
testDelayMillis = serverCache.testDelayMillis,
isSelected = serverCache.guid == selectedGuid,
subscriptionRemarks = subRemarks,
Expand Down Expand Up @@ -283,7 +283,6 @@ private fun ServerItemColumn(
remarks = profile.remarks,
statistics = profile.description.nullIfBlank() ?: AngConfigManager.generateDescription(profile),
typeDescription = getProtocolDescription(profile),
testResult = serverCache.testDelayString,
testDelayMillis = serverCache.testDelayMillis,
isSelected = serverCache.guid == selectedGuid,
subscriptionRemarks = subRemarks,
Expand All @@ -303,7 +302,6 @@ fun ServerListItem(
remarks: String,
statistics: String,
typeDescription: String,
testResult: String,
testDelayMillis: Long,
isSelected: Boolean,
subscriptionRemarks: String,
Expand All @@ -316,6 +314,12 @@ fun ServerListItem(
modifier: Modifier = Modifier,
dragModifier: Modifier = Modifier
) {
val testResult = if (testDelayMillis == 0L) {
""
} else {
stringResource(R.string.server_test_delay_value, testDelayMillis)
}

Row(
modifier = modifier
.fillMaxWidth()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.v2ray.ang.ui.main

import com.v2ray.ang.dto.ConnectionTestResult

sealed class MainServiceEvent {
data object StateRunning : MainServiceEvent()
data object StateNotRunning : MainServiceEvent()
data object StateStartSuccess : MainServiceEvent()
data class StateStartFailure(val errorMessage: String) : MainServiceEvent()
data object StateStartFailure : MainServiceEvent()
data object StateStopSuccess : MainServiceEvent()
data class MeasureDelaySuccess(val content: String) : MainServiceEvent()
data class MeasureDelayResult(val result: ConnectionTestResult) : MainServiceEvent()
data object MeasureConfigSuccess : MainServiceEvent()
data class MeasureConfigNotify(val progress: String) : MainServiceEvent()
data class MeasureConfigFinish(val finishedCount: String?) : MainServiceEvent()
Expand Down
Loading