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

Make subscription profiles partly immutable

parent 1362e8ce
...@@ -65,10 +65,20 @@ object ProfileManager { ...@@ -65,10 +65,20 @@ object ProfileManager {
if (replace) { if (replace) {
lazyClear.value 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 { profiles?.get(it.formattedAddress)?.apply {
it.tx = tx it.tx = tx
it.rx = rx 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 it.subscription = Profile.SubscriptionStatus.Active
createProfile(it) createProfile(it)
......
...@@ -118,6 +118,18 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -118,6 +118,18 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
initPlugins() initPlugins()
udpFallback = findPreference(Key.udpFallback)!! udpFallback = findPreference(Key.udpFallback)!!
DataStore.privateStore.registerChangeListener(this) 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
......
...@@ -91,6 +91,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -91,6 +91,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
* Is ProfilesFragment editable at all. * Is ProfilesFragment editable at all.
*/ */
private val isEnabled get() = (activity as MainActivity).state.let { it.canStop || it == BaseService.State.Stopped } private val isEnabled get() = (activity as MainActivity).state.let { it.canStop || it == BaseService.State.Stopped }
private fun isProfileEditable(id: Long) = private fun isProfileEditable(id: Long) =
(activity as MainActivity).state == BaseService.State.Stopped || id !in Core.activeProfileIds (activity as MainActivity).state == BaseService.State.Stopped || id !in Core.activeProfileIds
...@@ -258,6 +259,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -258,6 +259,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
addTestDevice("B08FC1764A7B250E91EA9D0D5EBEB208") addTestDevice("B08FC1764A7B250E91EA9D0D5EBEB208")
addTestDevice("7509D18EB8AF82F915874FEF53877A64") addTestDevice("7509D18EB8AF82F915874FEF53877A64")
addTestDevice("F58907F28184A828DD0DB6F8E38189C6") addTestDevice("F58907F28184A828DD0DB6F8E38189C6")
addTestDevice("FE983F496D7C5C1878AA163D9420CA97")
}.build()) }.build())
} else if (nativeAd != null) populateUnifiedNativeAdView(nativeAd!!, nativeAdView!!) } else if (nativeAd != null) populateUnifiedNativeAdView(nativeAd!!, nativeAdView!!)
} }
...@@ -298,6 +300,10 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -298,6 +300,10 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
itemView.isSelected = false itemView.isSelected = false
if (selectedItem === this) selectedItem = null 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?) { override fun onClick(v: View?) {
...@@ -338,6 +344,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -338,6 +344,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) = holder.bind(profiles[position]) override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) = holder.bind(profiles[position])
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder = ProfileViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder = ProfileViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.layout_profile, parent, false)) LayoutInflater.from(parent.context).inflate(R.layout.layout_profile, parent, false))
override fun getItemCount(): Int = profiles.size override fun getItemCount(): Int = profiles.size
override fun getItemId(position: Int): Long = profiles[position].id override fun getItemId(position: Int): Long = profiles[position].id
...@@ -366,6 +373,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -366,6 +373,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
updated.add(first) updated.add(first)
notifyItemMoved(from, to) notifyItemMoved(from, to)
} }
fun commitMove() { fun commitMove() {
updated.forEach { ProfileManager.updateProfile(it) } updated.forEach { ProfileManager.updateProfile(it) }
updated.clear() updated.clear()
...@@ -375,12 +383,14 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -375,12 +383,14 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
profiles.removeAt(pos) profiles.removeAt(pos)
notifyItemRemoved(pos) notifyItemRemoved(pos)
} }
fun undo(actions: List<Pair<Int, Profile>>) { fun undo(actions: List<Pair<Int, Profile>>) {
for ((index, item) in actions) { for ((index, item) in actions) {
profiles.add(index, item) profiles.add(index, item)
notifyItemInserted(index) notifyItemInserted(index)
} }
} }
fun commit(actions: List<Pair<Int, Profile>>) { fun commit(actions: List<Pair<Int, Profile>>) {
for ((_, item) in actions) ProfileManager.delProfile(item.id) for ((_, item) in actions) ProfileManager.delProfile(item.id)
} }
...@@ -389,12 +399,14 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -389,12 +399,14 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
val index = profiles.indexOfFirst { it.id == id } val index = profiles.indexOfFirst { it.id == id }
if (index >= 0) notifyItemChanged(index) if (index >= 0) notifyItemChanged(index)
} }
fun deepRefreshId(id: Long) { fun deepRefreshId(id: Long) {
val index = profiles.indexOfFirst { it.id == id } val index = profiles.indexOfFirst { it.id == id }
if (index < 0) return if (index < 0) return
profiles[index] = ProfileManager.getProfile(id)!! profiles[index] = ProfileManager.getProfile(id)!!
notifyItemChanged(index) notifyItemChanged(index)
} }
override fun onRemove(profileId: Long) { override fun onRemove(profileId: Long) {
val index = profiles.indexOfFirst { it.id == profileId } val index = profiles.indexOfFirst { it.id == profileId }
if (index < 0) return if (index < 0) return
...@@ -451,6 +463,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -451,6 +463,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int = override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int =
if (isProfileEditable((viewHolder as ProfileViewHolder).item.id)) if (isProfileEditable((viewHolder as ProfileViewHolder).item.id))
super.getSwipeDirs(recyclerView, viewHolder) else 0 super.getSwipeDirs(recyclerView, viewHolder) else 0
override fun getDragDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int = override fun getDragDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int =
if (isEnabled) super.getDragDirs(recyclerView, viewHolder) else 0 if (isEnabled) super.getDragDirs(recyclerView, viewHolder) else 0
...@@ -459,11 +472,13 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -459,11 +472,13 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
profilesAdapter.remove(index) profilesAdapter.remove(index)
undoManager.remove(Pair(index, (viewHolder as ProfileViewHolder).item)) undoManager.remove(Pair(index, (viewHolder as ProfileViewHolder).item))
} }
override fun onMove(recyclerView: RecyclerView, override fun onMove(recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
profilesAdapter.move(viewHolder.adapterPosition, target.adapterPosition) profilesAdapter.move(viewHolder.adapterPosition, target.adapterPosition)
return true return true
} }
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder) super.clearView(recyclerView, viewHolder)
profilesAdapter.commitMove() profilesAdapter.commitMove()
...@@ -538,7 +553,9 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -538,7 +553,9 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
try { try {
startActivityForResult(intent.addCategory(Intent.CATEGORY_OPENABLE), requestCode) startActivityForResult(intent.addCategory(Intent.CATEGORY_OPENABLE), requestCode)
return return
} catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { } } catch (_: ActivityNotFoundException) {
} catch (_: SecurityException) {
}
(activity as MainActivity).snackbar(getString(R.string.file_manager_missing)).show() (activity as MainActivity).snackbar(getString(R.string.file_manager_missing)).show()
} }
...@@ -586,17 +603,14 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -586,17 +603,14 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
profilesAdapter.refreshId(profileId) profilesAdapter.refreshId(profileId)
} }
} }
fun onTrafficPersisted(profileId: Long) { fun onTrafficPersisted(profileId: Long) {
statsCache.remove(profileId) statsCache.remove(profileId)
profilesAdapter.deepRefreshId(profileId) profilesAdapter.deepRefreshId(profileId)
} }
override fun onPause() {
undoManager.flush()
super.onPause()
}
override fun onDestroyView() { override fun onDestroyView() {
undoManager.flush()
nativeAd?.destroy() nativeAd?.destroy()
super.onDestroyView() super.onDestroyView()
} }
......
...@@ -46,6 +46,7 @@ import com.github.shadowsocks.database.ProfileManager ...@@ -46,6 +46,7 @@ import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.AlertDialogFragment import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.utils.asIterable import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.readableMessage import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.utils.useCancellable
import com.github.shadowsocks.widget.ListHolderListener import com.github.shadowsocks.widget.ListHolderListener
import com.github.shadowsocks.widget.MainListListener import com.github.shadowsocks.widget.MainListListener
import com.github.shadowsocks.widget.UndoSnackbarManager import com.github.shadowsocks.widget.UndoSnackbarManager
...@@ -236,8 +237,10 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener ...@@ -236,8 +237,10 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
for (url in subscription.urls.asIterable()) { for (url in subscription.urls.asIterable()) {
try { try {
val connection = url.openConnection() as HttpURLConnection val connection = url.openConnection() as HttpURLConnection
connection.useCancellable {
ProfileManager.createProfilesFromSubscription(sequenceOf(connection.inputStream), ProfileManager.createProfilesFromSubscription(sequenceOf(connection.inputStream),
replace, oldProfiles) replace, oldProfiles)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
activity.snackbar(e.readableMessage).show() activity.snackbar(e.readableMessage).show()
...@@ -261,6 +264,11 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener ...@@ -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? = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.layout_subscriptions, container, false) inflater.inflate(R.layout.layout_subscriptions, container, false)
......
...@@ -36,15 +36,18 @@ class UndoSnackbarManager<in T>(private val activity: MainActivity, private val ...@@ -36,15 +36,18 @@ class UndoSnackbarManager<in T>(private val activity: MainActivity, private val
private val recycleBin = ArrayList<Pair<Int, T>>() private val recycleBin = ArrayList<Pair<Int, T>>()
private val removedCallback = object : Snackbar.Callback() { private val removedCallback = object : Snackbar.Callback() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
if (last != null) {
when (event) { when (event) {
DISMISS_EVENT_SWIPE, DISMISS_EVENT_MANUAL, DISMISS_EVENT_TIMEOUT -> { DISMISS_EVENT_SWIPE, DISMISS_EVENT_MANUAL, DISMISS_EVENT_TIMEOUT -> {
commit?.invoke(recycleBin) commit?.invoke(recycleBin)
recycleBin.clear() recycleBin.clear()
} }
} }
}
last = null last = null
} }
} }
private var last: Snackbar? = null private var last: Snackbar? = null
fun remove(items: Collection<Pair<Int, T>>) { fun remove(items: Collection<Pair<Int, T>>) {
...@@ -59,6 +62,7 @@ class UndoSnackbarManager<in T>(private val activity: MainActivity, private val ...@@ -59,6 +62,7 @@ class UndoSnackbarManager<in T>(private val activity: MainActivity, private val
show() show()
} }
} }
fun remove(vararg items: Pair<Int, T>) = remove(items.toList()) fun remove(vararg items: Pair<Int, T>) = remove(items.toList())
fun flush() { 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 @@ ...@@ -19,6 +19,19 @@
android:layout_marginLeft="16dp" 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 <TextView
android:id="@android:id/text1" android:id="@android:id/text1"
android:layout_width="0dp" android:layout_width="0dp"
...@@ -34,27 +47,27 @@ ...@@ -34,27 +47,27 @@
tools:text="@string/profile_name"/> tools:text="@string/profile_name"/>
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/share" android:id="@+id/edit"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top" android:layout_gravity="top"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/share" android:contentDescription="@string/edit"
android:focusable="true" android:focusable="true"
android:nextFocusLeft="@+id/container"
android:padding="12dp" android:padding="12dp"
app:srcCompat="@drawable/ic_social_share"/> app:srcCompat="@drawable/ic_image_edit"/>
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/edit" android:id="@+id/share"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top" android:layout_gravity="top"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/edit" android:contentDescription="@string/share"
android:focusable="true" android:focusable="true"
android:nextFocusLeft="@+id/container"
android:padding="12dp" android:padding="12dp"
app:srcCompat="@drawable/ic_image_edit"/> app:srcCompat="@drawable/ic_social_share"/>
</LinearLayout> </LinearLayout>
<RelativeLayout <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