Commit 716dc410 authored by Max Lv's avatar Max Lv

Make subscription profiles partly immutable

parent 1362e8ce
......@@ -65,10 +65,20 @@ object ProfileManager {
if (replace) {
lazyClear.value
}
// if two profiles has the same address, treat them as the same profile and copy stats over
// if two profiles has the same address, treat them as the same profile and copy settings over
profiles?.get(it.formattedAddress)?.apply {
it.tx = tx
it.rx = rx
it.individual = individual
it.route = route
it.bypass = bypass
it.ipv6 = ipv6
it.metered = metered
it.proxyApps = proxyApps
it.remoteDns = remoteDns
it.udpdns = udpdns
it.udpFallback = udpFallback
}
it.subscription = Profile.SubscriptionStatus.Active
createProfile(it)
......
......@@ -118,6 +118,18 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
initPlugins()
udpFallback = findPreference(Key.udpFallback)!!
DataStore.privateStore.registerChangeListener(this)
val profile = ProfileManager.getProfile(profileId) ?: Profile()
if (profile.subscription == Profile.SubscriptionStatus.Active) {
findPreference<Preference>(Key.name)!!.isEnabled = false
findPreference<Preference>(Key.host)!!.isEnabled = false
findPreference<Preference>(Key.password)!!.isEnabled = false
findPreference<Preference>(Key.method)!!.isEnabled = false
findPreference<Preference>(Key.remotePort)!!.isEnabled = false
findPreference<Preference>(Key.plugin)!!.isEnabled = false
findPreference<Preference>(Key.pluginConfigure)!!.isEnabled = false
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
......
......@@ -91,6 +91,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
* Is ProfilesFragment editable at all.
*/
private val isEnabled get() = (activity as MainActivity).state.let { it.canStop || it == BaseService.State.Stopped }
private fun isProfileEditable(id: Long) =
(activity as MainActivity).state == BaseService.State.Stopped || id !in Core.activeProfileIds
......@@ -258,6 +259,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
addTestDevice("B08FC1764A7B250E91EA9D0D5EBEB208")
addTestDevice("7509D18EB8AF82F915874FEF53877A64")
addTestDevice("F58907F28184A828DD0DB6F8E38189C6")
addTestDevice("FE983F496D7C5C1878AA163D9420CA97")
}.build())
} else if (nativeAd != null) populateUnifiedNativeAdView(nativeAd!!, nativeAdView!!)
}
......@@ -298,6 +300,10 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
itemView.isSelected = false
if (selectedItem === this) selectedItem = null
}
if (item.subscription == Profile.SubscriptionStatus.Active) {
itemView.findViewById<View>(R.id.subscription).visibility = View.VISIBLE
}
}
override fun onClick(v: View?) {
......@@ -338,6 +344,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) = holder.bind(profiles[position])
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder = ProfileViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.layout_profile, parent, false))
override fun getItemCount(): Int = profiles.size
override fun getItemId(position: Int): Long = profiles[position].id
......@@ -366,6 +373,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
updated.add(first)
notifyItemMoved(from, to)
}
fun commitMove() {
updated.forEach { ProfileManager.updateProfile(it) }
updated.clear()
......@@ -375,12 +383,14 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
profiles.removeAt(pos)
notifyItemRemoved(pos)
}
fun undo(actions: List<Pair<Int, Profile>>) {
for ((index, item) in actions) {
profiles.add(index, item)
notifyItemInserted(index)
}
}
fun commit(actions: List<Pair<Int, Profile>>) {
for ((_, item) in actions) ProfileManager.delProfile(item.id)
}
......@@ -389,12 +399,14 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
val index = profiles.indexOfFirst { it.id == id }
if (index >= 0) notifyItemChanged(index)
}
fun deepRefreshId(id: Long) {
val index = profiles.indexOfFirst { it.id == id }
if (index < 0) return
profiles[index] = ProfileManager.getProfile(id)!!
notifyItemChanged(index)
}
override fun onRemove(profileId: Long) {
val index = profiles.indexOfFirst { it.id == profileId }
if (index < 0) return
......@@ -451,6 +463,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int =
if (isProfileEditable((viewHolder as ProfileViewHolder).item.id))
super.getSwipeDirs(recyclerView, viewHolder) else 0
override fun getDragDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int =
if (isEnabled) super.getDragDirs(recyclerView, viewHolder) else 0
......@@ -459,11 +472,13 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
profilesAdapter.remove(index)
undoManager.remove(Pair(index, (viewHolder as ProfileViewHolder).item))
}
override fun onMove(recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
profilesAdapter.move(viewHolder.adapterPosition, target.adapterPosition)
return true
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
profilesAdapter.commitMove()
......@@ -538,7 +553,9 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
try {
startActivityForResult(intent.addCategory(Intent.CATEGORY_OPENABLE), requestCode)
return
} catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { }
} catch (_: ActivityNotFoundException) {
} catch (_: SecurityException) {
}
(activity as MainActivity).snackbar(getString(R.string.file_manager_missing)).show()
}
......@@ -586,17 +603,14 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
profilesAdapter.refreshId(profileId)
}
}
fun onTrafficPersisted(profileId: Long) {
statsCache.remove(profileId)
profilesAdapter.deepRefreshId(profileId)
}
override fun onPause() {
undoManager.flush()
super.onPause()
}
override fun onDestroyView() {
undoManager.flush()
nativeAd?.destroy()
super.onDestroyView()
}
......
......@@ -46,6 +46,7 @@ import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.utils.useCancellable
import com.github.shadowsocks.widget.ListHolderListener
import com.github.shadowsocks.widget.MainListListener
import com.github.shadowsocks.widget.UndoSnackbarManager
......@@ -236,8 +237,10 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
for (url in subscription.urls.asIterable()) {
try {
val connection = url.openConnection() as HttpURLConnection
connection.useCancellable {
ProfileManager.createProfilesFromSubscription(sequenceOf(connection.inputStream),
replace, oldProfiles)
}
} catch (e: Exception) {
e.printStackTrace()
activity.snackbar(e.readableMessage).show()
......@@ -261,6 +264,11 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
}
}
override fun onPause() {
fetchJob?.cancel()
super.onPause()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.layout_subscriptions, container, false)
......
......@@ -36,15 +36,18 @@ class UndoSnackbarManager<in T>(private val activity: MainActivity, private val
private val recycleBin = ArrayList<Pair<Int, T>>()
private val removedCallback = object : Snackbar.Callback() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
if (last != null) {
when (event) {
DISMISS_EVENT_SWIPE, DISMISS_EVENT_MANUAL, DISMISS_EVENT_TIMEOUT -> {
commit?.invoke(recycleBin)
recycleBin.clear()
}
}
}
last = null
}
}
private var last: Snackbar? = null
fun remove(items: Collection<Pair<Int, T>>) {
......@@ -59,6 +62,7 @@ class UndoSnackbarManager<in T>(private val activity: MainActivity, private val
show()
}
}
fun remove(vararg items: Pair<Int, T>) = remove(items.toList())
fun flush() {
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM19,18H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h0.71C7.37,7.69 9.48,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3s-1.34,3 -3,3z"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM19,18H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h0.71C7.37,7.69 9.48,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3s-1.34,3 -3,3z"/>
</vector>
......@@ -19,6 +19,19 @@
android:layout_marginLeft="16dp"
>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/subscription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/subscriptions"
android:focusable="true"
android:paddingTop="15dp"
android:paddingEnd="4dp"
android:visibility="visible"
app:srcCompat="@drawable/ic_cloud_queue"/>
<TextView
android:id="@android:id/text1"
android:layout_width="0dp"
......@@ -34,27 +47,27 @@
tools:text="@string/profile_name"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/share"
android:id="@+id/edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/share"
android:contentDescription="@string/edit"
android:focusable="true"
android:nextFocusLeft="@+id/container"
android:padding="12dp"
app:srcCompat="@drawable/ic_social_share"/>
app:srcCompat="@drawable/ic_image_edit"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/edit"
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/edit"
android:contentDescription="@string/share"
android:focusable="true"
android:nextFocusLeft="@+id/container"
android:padding="12dp"
app:srcCompat="@drawable/ic_image_edit"/>
app:srcCompat="@drawable/ic_social_share"/>
</LinearLayout>
<RelativeLayout
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment