Commit 77d2def9 authored by Max Lv's avatar Max Lv

Allow importing profiles.json from URL

parent 3f25eace
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.subscription
import android.content.Context
import android.util.Log
import androidx.recyclerview.widget.SortedList
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.asIterable
import java.io.File
import java.io.IOException
import java.io.Reader
import java.net.URL
import java.net.URLConnection
class Subscription {
companion object {
const val SUBSCRIPTION = "subscription"
var instance: Subscription
get() {
val sub = Subscription()
val str = DataStore.publicStore.getString(SUBSCRIPTION)
if (str != null) sub.fromReader(str.reader())
return sub
}
set(value) = DataStore.publicStore.putString(SUBSCRIPTION, value.toString())
}
private abstract class BaseSorter<T> : SortedList.Callback<T>() {
override fun onInserted(position: Int, count: Int) { }
override fun areContentsTheSame(oldItem: T?, newItem: T?): Boolean = oldItem == newItem
override fun onMoved(fromPosition: Int, toPosition: Int) { }
override fun onChanged(position: Int, count: Int) { }
override fun onRemoved(position: Int, count: Int) { }
override fun areItemsTheSame(item1: T?, item2: T?): Boolean = item1 == item2
override fun compare(o1: T?, o2: T?): Int =
if (o1 == null) if (o2 == null) 0 else 1 else if (o2 == null) -1 else compareNonNull(o1, o2)
abstract fun compareNonNull(o1: T, o2: T): Int
}
private open class DefaultSorter<T : Comparable<T>> : BaseSorter<T>() {
override fun compareNonNull(o1: T, o2: T): Int = o1.compareTo(o2)
}
private object URLSorter : BaseSorter<URL>() {
private val ordering = compareBy<URL>({ it.host }, { it.port }, { it.file }, { it.protocol })
override fun compareNonNull(o1: URL, o2: URL): Int = ordering.compare(o1, o2)
}
val urls = SortedList(URL::class.java, URLSorter)
fun fromReader(reader: Reader): Subscription {
urls.clear()
reader.useLines {
for (line in it) {
urls.add(URL(line))
}
}
return this
}
override fun toString(): String {
val result = StringBuilder()
result.append(urls.asIterable().joinToString("\n"))
return result.toString()
}
}
...@@ -135,6 +135,12 @@ ...@@ -135,6 +135,12 @@
<string name="vpn_connected">Connected, tap to check connection</string> <string name="vpn_connected">Connected, tap to check connection</string>
<string name="not_connected">Not connected</string> <string name="not_connected">Not connected</string>
<!-- subscriptions -->
<string name="subscriptions">Subscriptions</string>
<string name="add_subscription">Add a subscription</string>
<string name="edit_subscription">Edit subscription</string>
<string name="update_subscription">Refresh servers from subscription</string>
<!-- acl --> <!-- acl -->
<string name="custom_rules">Custom rules</string> <string name="custom_rules">Custom rules</string>
<string name="action_add_rule">Add rule(s)…</string> <string name="action_add_rule">Add rule(s)…</string>
......
...@@ -48,6 +48,7 @@ import com.github.shadowsocks.aidl.TrafficStats ...@@ -48,6 +48,7 @@ import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.subscription.SubscriptionFragment
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.SingleInstanceActivity import com.github.shadowsocks.utils.SingleInstanceActivity
import com.github.shadowsocks.widget.ListHolderListener import com.github.shadowsocks.widget.ListHolderListener
...@@ -215,6 +216,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -215,6 +216,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
return true return true
} }
R.id.customRules -> displayFragment(CustomRulesFragment()) R.id.customRules -> displayFragment(CustomRulesFragment())
R.id.subscriptions -> displayFragment(SubscriptionFragment())
else -> return false else -> return false
} }
item.isChecked = true item.isChecked = true
......
...@@ -257,6 +257,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -257,6 +257,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
}.build().loadAd(AdRequest.Builder().apply { }.build().loadAd(AdRequest.Builder().apply {
addTestDevice("B08FC1764A7B250E91EA9D0D5EBEB208") addTestDevice("B08FC1764A7B250E91EA9D0D5EBEB208")
addTestDevice("7509D18EB8AF82F915874FEF53877A64") addTestDevice("7509D18EB8AF82F915874FEF53877A64")
addTestDevice("F58907F28184A828DD0DB6F8E38189C6")
}.build()) }.build())
} else if (nativeAd != null) populateUnifiedNativeAdView(nativeAd!!, nativeAdView!!) } else if (nativeAd != null) populateUnifiedNativeAdView(nativeAd!!, nativeAdView!!)
} }
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:inputType="textNoSuggestions|textMultiLine"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
<item android:id="@+id/profiles" <item android:id="@+id/profiles"
android:title="@string/profiles" android:title="@string/profiles"
android:icon="@drawable/ic_action_description"/> android:icon="@drawable/ic_action_description"/>
<item android:id="@+id/subscriptions"
android:title="@string/subscriptions"
android:icon="@drawable/ic_action_description"/>
<item android:id="@+id/customRules" <item android:id="@+id/customRules"
android:title="@string/custom_rules" android:title="@string/custom_rules"
android:icon="@drawable/ic_action_assignment"/> android:icon="@drawable/ic_action_assignment"/>
......
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:title="@string/add_subscription"
android:id="@+id/action_manual_settings"
android:icon="@drawable/ic_av_playlist_add"
android:alphabeticShortcut="n"
app:showAsAction="always"/>
<item android:title="@string/update_subscription"
android:id="@+id/action_update_subscription"
android:icon="@drawable/ic_action_done"
android:alphabeticShortcut="r"
app:showAsAction="ifRoom"/>
</menu>
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