diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt index a68e8b605..70f4cf73a 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt @@ -232,6 +232,13 @@ fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) { } } + // maybe from matsuri vmess exoprt + if (this is VMessBean) { + url.queryParameter("encryption")?.let { + encryption = it + } + } + url.queryParameter("packetEncoding")?.let { when (it) { "packet" -> packetEncoding = 1 diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt index 49ed29e5f..14734fcf6 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt @@ -501,6 +501,7 @@ class ConfigurationFragment @JvmOverloads constructor( } } } + R.id.action_remove_duplicate -> { runOnDefaultDispatcher { val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId()) @@ -552,6 +553,7 @@ class ConfigurationFragment @JvmOverloads constructor( } } } + R.id.action_connection_icmp_ping -> { pingTest(true) } @@ -724,37 +726,38 @@ class ConfigurationFragment @JvmOverloads constructor( suspend fun update(profile: ProxyEntity) { fragment?.configurationListView?.post { + val context = context ?: return@post + if (!isAdded) return@post + var profileStatusText: String? = null var profileStatusColor = 0 when (profile.status) { -1 -> { profileStatusText = profile.error - profileStatusColor = - requireContext().getColorAttr(android.R.attr.textColorSecondary) + profileStatusColor = context.getColorAttr(android.R.attr.textColorSecondary) } 0 -> { profileStatusText = getString(R.string.connection_test_testing) - profileStatusColor = - requireContext().getColorAttr(android.R.attr.textColorSecondary) + profileStatusColor = context.getColorAttr(android.R.attr.textColorSecondary) } 1 -> { profileStatusText = getString(R.string.available, profile.ping) - profileStatusColor = requireContext().getColour(R.color.material_green_500) + profileStatusColor = context.getColour(R.color.material_green_500) } 2 -> { profileStatusText = profile.error - profileStatusColor = requireContext().getColour(R.color.material_red_500) + profileStatusColor = context.getColour(R.color.material_red_500) } 3 -> { val err = profile.error ?: "" val msg = Protocols.genFriendlyMsg(err) profileStatusText = if (msg != err) msg else getString(R.string.unavailable) - profileStatusColor = requireContext().getColour(R.color.material_red_500) + profileStatusColor = context.getColour(R.color.material_red_500) } } @@ -763,7 +766,7 @@ class ConfigurationFragment @JvmOverloads constructor( append("\n") append( profile.displayType(), - ForegroundColorSpan(requireContext().getProtocolColor(profile.type)), + ForegroundColorSpan(context.getProtocolColor(profile.type)), SPAN_EXCLUSIVE_EXCLUSIVE ) append(" ") @@ -1508,16 +1511,20 @@ class ConfigurationFragment @JvmOverloads constructor( } override suspend fun onUpdated(profileId: Long, trafficStats: TrafficStats) { - val index = configurationIdList.indexOf(profileId) - if (index != -1) { - val holder = layoutManager.findViewByPosition(index) - ?.let { configurationListView.getChildViewHolder(it) } as ConfigurationHolder? - if (holder != null) { - holder.entity.stats = trafficStats - onMainDispatcher { - holder.bind(holder.entity) + try { + val index = configurationIdList.indexOf(profileId) + if (index != -1) { + val holder = layoutManager.findViewByPosition(index) + ?.let { configurationListView.getChildViewHolder(it) } as ConfigurationHolder? + if (holder != null) { + holder.entity.stats = trafficStats + onMainDispatcher { + holder.bind(holder.entity) + } } } + } catch (e: Exception) { + Logs.w(e) } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt index ea7fa1c9c..a62202a24 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt @@ -25,6 +25,7 @@ import android.os.Parcelable import android.view.Menu import android.view.MenuItem import android.view.View +import android.widget.Toast import androidx.annotation.LayoutRes import androidx.appcompat.app.AlertDialog import androidx.core.view.ViewCompat @@ -36,9 +37,11 @@ import com.takisoft.preferencex.SimpleMenuPreference import io.nekohasekai.sagernet.GroupType import io.nekohasekai.sagernet.Key import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.SubscriptionType import io.nekohasekai.sagernet.database.* import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener +import io.nekohasekai.sagernet.ktx.Logs import io.nekohasekai.sagernet.ktx.applyDefaultValues import io.nekohasekai.sagernet.ktx.onMainDispatcher import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher @@ -118,7 +121,8 @@ class GroupSettingsActivity( val subscriptionType = findPreference(Key.SUBSCRIPTION_TYPE)!! val subscriptionLink = findPreference(Key.SUBSCRIPTION_LINK)!! val subscriptionToken = findPreference(Key.SUBSCRIPTION_TOKEN)!! - val subscriptionUserAgent = findPreference(Key.SUBSCRIPTION_USER_AGENT)!! + val subscriptionUserAgent = + findPreference(Key.SUBSCRIPTION_USER_AGENT)!! fun updateSubscriptionType(subscriptionType: Int = DataStore.subscriptionType) { val isRaw = subscriptionType == SubscriptionType.RAW @@ -135,8 +139,10 @@ class GroupSettingsActivity( true } - val subscriptionAutoUpdate = findPreference(Key.SUBSCRIPTION_AUTO_UPDATE)!! - val subscriptionAutoUpdateDelay = findPreference(Key.SUBSCRIPTION_AUTO_UPDATE_DELAY)!! + val subscriptionAutoUpdate = + findPreference(Key.SUBSCRIPTION_AUTO_UPDATE)!! + val subscriptionAutoUpdateDelay = + findPreference(Key.SUBSCRIPTION_AUTO_UPDATE_DELAY)!! subscriptionAutoUpdateDelay.isEnabled = subscriptionAutoUpdate.isChecked subscriptionAutoUpdateDelay.setOnPreferenceChangeListener { _, newValue -> val delay = (newValue as String).toIntOrNull() @@ -291,8 +297,17 @@ class GroupSettingsActivity( override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { preferenceManager.preferenceDataStore = DataStore.profileCacheStore - activity.apply { - createPreferences(savedInstanceState, rootKey) + try { + activity.apply { + createPreferences(savedInstanceState, rootKey) + } + } catch (e: Exception) { + Toast.makeText( + SagerNet.application, + "Error on createPreferences, please try again.", + Toast.LENGTH_SHORT + ).show() + Logs.e(e) } } @@ -318,12 +333,14 @@ class GroupSettingsActivity( } true } + R.id.action_apply -> { runOnDefaultDispatcher { activity.saveAndExit() } true } + else -> false } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt index 371aefcf8..3671b28c3 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt @@ -27,6 +27,7 @@ import android.os.Parcelable import android.view.Menu import android.view.MenuItem import android.view.View +import android.widget.Toast import androidx.activity.result.component1 import androidx.activity.result.component2 import androidx.activity.result.contract.ActivityResultContracts @@ -43,11 +44,13 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.takisoft.preferencex.PreferenceFragmentCompat import io.nekohasekai.sagernet.Key import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.database.DataStore import io.nekohasekai.sagernet.database.ProfileManager import io.nekohasekai.sagernet.database.RuleEntity import io.nekohasekai.sagernet.database.SagerDatabase import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener +import io.nekohasekai.sagernet.ktx.Logs import io.nekohasekai.sagernet.ktx.app import io.nekohasekai.sagernet.ktx.onMainDispatcher import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher @@ -355,8 +358,17 @@ class RouteSettingsActivity( override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { preferenceManager.preferenceDataStore = DataStore.profileCacheStore - activity.apply { - createPreferences(savedInstanceState, rootKey) + try { + activity.apply { + createPreferences(savedInstanceState, rootKey) + } + } catch (e: Exception) { + Toast.makeText( + SagerNet.application, + "Error on createPreferences, please try again.", + Toast.LENGTH_SHORT + ).show() + Logs.e(e) } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt index 0b4e5e7c7..4acb41f29 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt @@ -28,6 +28,8 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.LinearLayout +import android.widget.ScrollView +import android.widget.Toast import androidx.annotation.LayoutRes import androidx.appcompat.app.AlertDialog import androidx.core.content.pm.ShortcutInfoCompat @@ -50,10 +52,7 @@ import io.nekohasekai.sagernet.database.SagerDatabase import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener import io.nekohasekai.sagernet.databinding.LayoutGroupItemBinding import io.nekohasekai.sagernet.fmt.AbstractBean -import io.nekohasekai.sagernet.ktx.applyDefaultValues -import io.nekohasekai.sagernet.ktx.onMainDispatcher -import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher -import io.nekohasekai.sagernet.ktx.runOnMainDispatcher +import io.nekohasekai.sagernet.ktx.* import io.nekohasekai.sagernet.ui.ThemedActivity import io.nekohasekai.sagernet.widget.ListListener import kotlinx.parcelize.Parcelize @@ -229,12 +228,17 @@ abstract class ProfileSettingsActivity( override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { preferenceManager.preferenceDataStore = DataStore.profileCacheStore - activity.apply { - createPreferences(savedInstanceState, rootKey) - - if (isSubscription) { -// findPreference(Key.PROFILE_NAME)?.isEnabled = false + try { + activity.apply { + createPreferences(savedInstanceState, rootKey) } + } catch (e: Exception) { + Toast.makeText( + SagerNet.application, + "Error on createPreferences, please try again.", + Toast.LENGTH_SHORT + ).show() + Logs.e(e) } } @@ -267,12 +271,14 @@ abstract class ProfileSettingsActivity( } true } + R.id.action_apply -> { runOnDefaultDispatcher { activity.saveAndExit() } true } + R.id.action_create_shortcut -> { val ctx = requireContext() val ent = activity.proxyEntity!! @@ -297,6 +303,7 @@ abstract class ProfileSettingsActivity( .build() ShortcutManagerCompat.requestPinShortcut(ctx, shortcut, null) } + R.id.action_move -> { val view = LinearLayout(context).apply { val ent = activity.proxyEntity!! @@ -327,9 +334,13 @@ abstract class ProfileSettingsActivity( } } } - MaterialAlertDialogBuilder(activity).setView(view).show() + val scrollView = ScrollView(context).apply { + addView(view) + } + MaterialAlertDialogBuilder(activity).setView(scrollView).show() true } + else -> false } diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/OOCv1TokenPreference.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/OOCv1TokenPreference.kt index 424df5567..6ec192e4f 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/widget/OOCv1TokenPreference.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/OOCv1TokenPreference.kt @@ -45,7 +45,7 @@ class OOCv1TokenPreference : EditTextPreference { init { - dialogLayoutResource = R.layout.layout_link_dialog + dialogLayoutResource = R.layout.layout_urltest_preference_dialog setOnBindEditTextListener { editText -> editText.isSingleLine = false @@ -75,14 +75,17 @@ class OOCv1TokenPreference : EditTextPreference { linkLayout.error = "Missing field: baseUrl" isValid = false } + baseUrl.endsWith("/") -> { linkLayout.error = "baseUrl must not contain a trailing slash" isValid = false } + !baseUrl.startsWith("https://") -> { isValid = false linkLayout.error = "Protocol scheme must be https" } + else -> try { baseUrl.toHttpUrl() } catch (e: Exception) { @@ -105,7 +108,8 @@ class OOCv1TokenPreference : EditTextPreference { when { certSha256.length != 64 -> { isValid = false - linkLayout.error = "certSha256 must be a SHA-256 hexadecimal string" + linkLayout.error = + "certSha256 must be a SHA-256 hexadecimal string" } } } diff --git a/app/src/main/java/moe/matsuri/nb4a/ui/UrlTestPreference.kt b/app/src/main/java/moe/matsuri/nb4a/ui/UrlTestPreference.kt new file mode 100644 index 000000000..4f4f5d32e --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/ui/UrlTestPreference.kt @@ -0,0 +1,50 @@ +package moe.matsuri.nb4a.ui + +import android.content.Context +import android.util.AttributeSet +import android.widget.EditText +import android.widget.LinearLayout +import androidx.core.content.res.TypedArrayUtils +import androidx.core.view.isVisible +import androidx.preference.EditTextPreference +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore + +class UrlTestPreference +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = TypedArrayUtils.getAttr( + context, R.attr.editTextPreferenceStyle, + android.R.attr.editTextPreferenceStyle + ), + defStyleRes: Int = 0 +) : EditTextPreference(context, attrs, defStyleAttr, defStyleRes) { + + var concurrent: EditText? = null + + init { + dialogLayoutResource = R.layout.layout_urltest_preference_dialog + + setOnBindEditTextListener { + concurrent = it.rootView.findViewById(R.id.edit_concurrent) + concurrent?.apply { + setText(DataStore.connectionTestConcurrent.toString()) + } + it.rootView.findViewById(R.id.concurrent_layout)?.isVisible = true + } + + setOnPreferenceChangeListener { _, _ -> + concurrent?.apply { + var newConcurrent = text?.toString()?.toIntOrNull() + if (newConcurrent == null || newConcurrent <= 0) { + newConcurrent = 5 + } + DataStore.connectionTestConcurrent = newConcurrent + } + true + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nya/ui/DnsLinkPreference.kt b/app/src/main/java/moe/matsuri/nya/ui/DnsLinkPreference.kt index cbd83cb95..329187d1b 100644 --- a/app/src/main/java/moe/matsuri/nya/ui/DnsLinkPreference.kt +++ b/app/src/main/java/moe/matsuri/nya/ui/DnsLinkPreference.kt @@ -23,7 +23,7 @@ class DnsLinkPreference : EditTextPreference { init { - dialogLayoutResource = R.layout.layout_link_dialog + dialogLayoutResource = R.layout.layout_urltest_preference_dialog setOnBindEditTextListener { val linkLayout = it.rootView.findViewById(R.id.input_layout) diff --git a/app/src/main/res/layout/layout_urltest_preference_dialog.xml b/app/src/main/res/layout/layout_urltest_preference_dialog.xml index d1c2b9efd..6562b1009 100644 --- a/app/src/main/res/layout/layout_urltest_preference_dialog.xml +++ b/app/src/main/res/layout/layout_urltest_preference_dialog.xml @@ -31,8 +31,10 @@ android:typeface="monospace" /> + android:layout_height="wrap_content" + android:visibility="gone">