Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ class GroupSettingsActivity(

DataStore.frontProxy = frontProxy
DataStore.landingProxy = landingProxy
DataStore.frontProxyTmp = if (frontProxy >= 0) 3 else 0
DataStore.landingProxyTmp = if (landingProxy >= 0) 3 else 0
DataStore.frontProxyTmp =
if (frontProxy >= 0) OutboundPreference.VALUE_SELECT_PROFILE.toInt() else 0
DataStore.landingProxyTmp =
if (landingProxy >= 0) OutboundPreference.VALUE_SELECT_PROFILE.toInt() else 0

val subscription = subscription ?: SubscriptionBean().applyDefaultValues()
DataStore.subscriptionLink = subscription.link
Expand All @@ -72,8 +74,18 @@ class GroupSettingsActivity(
order = DataStore.groupOrder
isSelector = DataStore.groupIsSelector

frontProxy = if (DataStore.frontProxyTmp == 3) DataStore.frontProxy else -1
landingProxy = if (DataStore.landingProxyTmp == 3) DataStore.landingProxy else -1
frontProxy =
if (DataStore.frontProxyTmp == OutboundPreference.VALUE_SELECT_PROFILE.toInt()) {
DataStore.frontProxy
} else {
-1
}
landingProxy =
if (DataStore.landingProxyTmp == OutboundPreference.VALUE_SELECT_PROFILE.toInt()) {
DataStore.landingProxy
} else {
-1
}

val isSubscription = type == GroupType.SUBSCRIPTION
if (isSubscription) {
Expand Down Expand Up @@ -108,9 +120,15 @@ class GroupSettingsActivity(
setEntries(R.array.front_proxy_entry)
setEntryValues(R.array.front_proxy_value)
setOnPreferenceChangeListener { _, newValue ->
if (newValue.toString() == "3") {
if (newValue.toString() == OutboundPreference.VALUE_SELECT_PROFILE) {
selectProfileForAddFront.launch(
Intent(this@GroupSettingsActivity, ProfileSelectActivity::class.java)
Intent(
this@GroupSettingsActivity, ProfileSelectActivity::class.java
).apply {
ProfileManager.getProfile(DataStore.frontProxy)?.let {
putExtra(ProfileSelectActivity.EXTRA_SELECTED, it)
}
}
)
false
} else {
Expand All @@ -123,9 +141,15 @@ class GroupSettingsActivity(
setEntries(R.array.front_proxy_entry)
setEntryValues(R.array.front_proxy_value)
setOnPreferenceChangeListener { _, newValue ->
if (newValue.toString() == "3") {
if (newValue.toString() == OutboundPreference.VALUE_SELECT_PROFILE) {
selectProfileForAddLanding.launch(
Intent(this@GroupSettingsActivity, ProfileSelectActivity::class.java)
Intent(
this@GroupSettingsActivity, ProfileSelectActivity::class.java
).apply {
ProfileManager.getProfile(DataStore.landingProxy)?.let {
putExtra(ProfileSelectActivity.EXTRA_SELECTED, it)
}
}
)
false
} else {
Expand Down Expand Up @@ -401,7 +425,7 @@ class GroupSettingsActivity(
) ?: return@runOnDefaultDispatcher
DataStore.frontProxy = profile.id
onMainDispatcher {
frontProxyPreference.value = "3"
frontProxyPreference.value = OutboundPreference.VALUE_SELECT_PROFILE
}
}
}
Expand All @@ -415,7 +439,7 @@ class GroupSettingsActivity(
) ?: return@runOnDefaultDispatcher
DataStore.landingProxy = profile.id
onMainDispatcher {
landingProxyPreference.value = "3"
landingProxyPreference.value = OutboundPreference.VALUE_SELECT_PROFILE
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class RouteSettingsActivity(
0L -> 0
-1L -> 1
-2L -> 2
else -> 3
else -> OutboundPreference.VALUE_SELECT_PROFILE.toInt()
}
DataStore.routePackages = packages.joinToString("\n")
}
Expand Down Expand Up @@ -135,7 +135,7 @@ class RouteSettingsActivity(
) ?: return@runOnDefaultDispatcher
DataStore.routeOutboundRule = profile.id
onMainDispatcher {
outbound.value = "3"
outbound.value = OutboundPreference.VALUE_SELECT_PROFILE
}
}
}
Expand All @@ -154,11 +154,15 @@ class RouteSettingsActivity(
apps = findPreference(Key.ROUTE_PACKAGES)!!

outbound.setOnPreferenceChangeListener { _, newValue ->
if (newValue.toString() == "3") {
if (newValue.toString() == OutboundPreference.VALUE_SELECT_PROFILE) {
selectProfileForAdd.launch(
Intent(
this@RouteSettingsActivity, ProfileSelectActivity::class.java
)
).apply {
ProfileManager.getProfile(DataStore.routeOutboundRule)?.let {
putExtra(ProfileSelectActivity.EXTRA_SELECTED, it)
}
}
)
false
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package io.nekohasekai.sagernet.widget

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.AdapterView
import android.widget.Spinner
import androidx.preference.PreferenceViewHolder
import io.nekohasekai.sagernet.R
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.database.ProfileManager
Expand All @@ -12,13 +16,64 @@ class OutboundPreference
context: Context, attrs: AttributeSet? = null, defStyle: Int = R.attr.dropdownPreferenceStyle
) : SimpleMenuPreference(context, attrs, defStyle, 0) {

companion object {
const val VALUE_SELECT_PROFILE = "3"
}

init {
setEntries(R.array.outbound_entry)
setEntryValues(R.array.outbound_value)
layoutResource = R.layout.preference_dropdown_reselectable
}

override fun setValue(value: String?) {
val oldValue = this.value
super.setValue(value)
if (oldValue == value) {
notifyChanged()
}
}

private var dropdownOpened = false

override fun onClick() {
dropdownOpened = true
super.onClick()
}

override fun onBindViewHolder(holder: PreferenceViewHolder) {
dropdownOpened = false
super.onBindViewHolder(holder)

val spinner = holder.itemView.findViewById<Spinner>(R.id.spinner)
(spinner as? ReselectableSpinner)?.onPopupClosed = { dropdownOpened = false }
var selectionReady = false
holder.itemView.post { selectionReady = true }
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long,
) {
if (!selectionReady || position < 0) return
val newValue = entryValues?.getOrNull(position)?.toString() ?: return
val reselectedProfile =
dropdownOpened && newValue == value && newValue == VALUE_SELECT_PROFILE
if ((newValue != value || reselectedProfile) && callChangeListener(newValue)) {
value = newValue
}
dropdownOpened = false
}

override fun onNothingSelected(parent: AdapterView<*>?) {
dropdownOpened = false
}
}
}

override fun getSummary(): CharSequence? {
if (value == "3") {
if (value == VALUE_SELECT_PROFILE) {
val routeOutbound = DataStore.profileCacheStore.getLong(key + "Long") ?: 0
if (routeOutbound > 0) {
ProfileManager.getProfile(routeOutbound)?.displayName()?.let {
Expand All @@ -29,4 +84,4 @@ class OutboundPreference
return super.getSummary()
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.nekohasekai.sagernet.widget

import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatSpinner

class ReselectableSpinner @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : AppCompatSpinner(context, attrs) {

var onPopupClosed: (() -> Unit)? = null

/**
* Android's [Spinner] has no official "popup dismissed" callback, so we use
* the window regaining focus as a proxy: when the dropdown popup closes,
* focus returns to this spinner's window. This is intentionally a best-effort
* signal, not a precise one — [onPopupClosed] may also fire for unrelated
* focus changes (app foregrounding, keyboard/IME show-hide, system dialogs,
* permission prompts, orientation changes, etc.). Callers must therefore
* treat it only as a hint and stay correct if it fires spuriously (the
* OutboundPreference reselect logic does, by re-checking value/position).
*/
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
super.onWindowFocusChanged(hasWindowFocus)
if (hasWindowFocus) onPopupClosed?.invoke()
}
Comment thread
hawkff marked this conversation as resolved.

override fun setSelection(position: Int) {
val reselected = position == selectedItemPosition
super.setSelection(position)
if (reselected) notifyReselected(position)
}

override fun setSelection(position: Int, animate: Boolean) {
val reselected = position == selectedItemPosition
super.setSelection(position, animate)
if (reselected) notifyReselected(position)
}
Comment thread
hawkff marked this conversation as resolved.

private fun notifyReselected(position: Int) {
if (position < 0) return
onItemSelectedListener?.onItemSelected(
this, selectedView, position, adapter?.getItemId(position) ?: 0L
)
}
}
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_baseline_folder_open_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,6h-8.17l-2,-2H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2zM20,18H4V8h16v10z" />
</vector>
25 changes: 25 additions & 0 deletions app/src/main/res/layout/preference_dropdown_reselectable.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Mirror of androidx's preference_dropdown_material.xml, with the anchor spinner
swapped for ReselectableSpinner so re-selecting the current item still fires a
selection callback. The (invisible) spinner is only the popup anchor; the
visible row comes from the standard @layout/preference_material include, so the
appearance is identical to a normal dropdown preference.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<io.nekohasekai.sagernet.widget.ReselectableSpinner
android:id="@+id/spinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/preference_dropdown_padding_start"
android:layout_marginLeft="@dimen/preference_dropdown_padding_start"
android:spinnerMode="dropdown"
android:visibility="invisible" />

<include layout="@layout/preference_material" />

</FrameLayout>
5 changes: 5 additions & 0 deletions app/src/main/res/xml/webdav_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,36 @@

<EditTextPreference
android:key="webdavServer"
app:icon="@drawable/ic_file_cloud_queue"
android:title="@string/webdav_server"
android:singleLine="true"
android:summary="%s" />

<EditTextPreference
android:key="webdavUsername"
app:icon="@drawable/ic_baseline_person_24"
android:title="@string/webdav_username"
android:singleLine="true"
android:summary="%s" />

<EditTextPreference
android:key="webdavPassword"
app:icon="@drawable/ic_settings_password"
android:title="@string/webdav_password"
android:singleLine="true"
app:dialogLayout="@layout/layout_password_dialog" />

<EditTextPreference
android:key="webdavPath"
app:icon="@drawable/ic_baseline_folder_open_24"
android:title="@string/webdav_path"
android:singleLine="true"
android:defaultValue="NekoBox"
android:summary="%s" />

<Preference
android:key="webdavTest"
app:icon="@drawable/ic_baseline_cast_connected_24"
android:title="@string/webdav_test" />

</PreferenceScreen>
Loading