Merge pull request #2834 from hamidrezahy/thirdparty_asset_update
Automatic update of third party dat files - fix #2821
This commit is contained in:
@@ -93,6 +93,9 @@
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.UserAssetActivity" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.UserAssetUrlActivity" />
|
||||
|
||||
<activity
|
||||
android:exported="false"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class AssetUrlItem(
|
||||
var remarks: String = "",
|
||||
var url: String = "",
|
||||
val addedTime: Long = System.currentTimeMillis(),
|
||||
var lastUpdated: Long = -1
|
||||
)
|
||||
@@ -8,4 +8,5 @@ data class SubscriptionItem(
|
||||
var lastUpdated: Long = -1,
|
||||
var autoUpdate: Boolean = false,
|
||||
val updateInterval: Int? = null,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -15,12 +15,14 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.extension.toTrafficString
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
@@ -39,9 +41,11 @@ import java.util.*
|
||||
class UserAssetActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivitySubSettingBinding
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val assetStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
val extDir by lazy { File(Utils.userAssetPath(this)) }
|
||||
val geofiles = arrayOf("geosite.dat", "geoip.dat")
|
||||
val builtInGeoFiles = arrayOf("geosite.dat", "geoip.dat")
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -55,6 +59,11 @@ class UserAssetActivity : BaseActivity() {
|
||||
binding.recyclerView.adapter = UserAssetAdapter()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.recyclerView.adapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_asset, menu)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
@@ -66,6 +75,11 @@ class UserAssetActivity : BaseActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.add_url -> {
|
||||
val intent = Intent(this, UserAssetUrlActivity::class.java)
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
R.id.download_file -> {
|
||||
downloadGeoFiles()
|
||||
true
|
||||
@@ -104,13 +118,27 @@ class UserAssetActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private val chooseFile =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { it ->
|
||||
val uri = it.data?.data
|
||||
if (it.resultCode == RESULT_OK && uri != null) {
|
||||
val assetId = Utils.getUuid()
|
||||
try {
|
||||
val assetItem = AssetUrlItem(
|
||||
getCursorName(uri) ?: uri.toString(),
|
||||
"file"
|
||||
)
|
||||
|
||||
// check remarks unique
|
||||
val assetList = MmkvManager.decodeAssetUrls()
|
||||
if (assetList.any { it.second.remarks == assetItem.remarks && it.first != assetId }) {
|
||||
toast(R.string.msg_remark_is_duplicate)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
assetStorage?.encode(assetId, Gson().toJson(assetItem))
|
||||
copyFile(uri)
|
||||
} catch (e: Exception) {
|
||||
toast(R.string.toast_asset_copy_failed)
|
||||
MmkvManager.removeAssetUrl(assetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,31 +171,33 @@ class UserAssetActivity : BaseActivity() {
|
||||
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
||||
|
||||
toast(R.string.msg_downloading_content)
|
||||
geofiles.forEach {
|
||||
var assets = MmkvManager.decodeAssetUrls()
|
||||
assets = addBuiltInGeoItems(assets)
|
||||
|
||||
assets.forEach {
|
||||
//toast(getString(R.string.msg_downloading_content) + it)
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val result = downloadGeo(it, 60000, httpPort)
|
||||
val result = downloadGeo(it.second, 60000, httpPort)
|
||||
launch(Dispatchers.Main) {
|
||||
if (result) {
|
||||
toast(getString(R.string.toast_success) + " " + it)
|
||||
toast(getString(R.string.toast_success) + " " + it.second.remarks)
|
||||
binding.recyclerView.adapter?.notifyDataSetChanged()
|
||||
} else {
|
||||
toast(getString(R.string.toast_failure) + " " + it)
|
||||
toast(getString(R.string.toast_failure) + " " + it.second.remarks)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadGeo(name: String, timeout: Int, httpPort: Int): Boolean {
|
||||
val url = AppConfig.geoUrl + name
|
||||
val targetTemp = File(extDir, name + "_temp")
|
||||
val target = File(extDir, name)
|
||||
private fun downloadGeo(item: AssetUrlItem, timeout: Int, httpPort: Int): Boolean {
|
||||
val targetTemp = File(extDir, item.remarks + "_temp")
|
||||
val target = File(extDir, item.remarks)
|
||||
var conn: HttpURLConnection? = null
|
||||
//Log.d(AppConfig.ANG_PACKAGE, url)
|
||||
|
||||
try {
|
||||
conn = URL(url).openConnection(
|
||||
conn = URL(item.url).openConnection(
|
||||
Proxy(
|
||||
Proxy.Type.HTTP,
|
||||
InetSocketAddress("127.0.0.1", httpPort)
|
||||
@@ -192,33 +222,74 @@ class UserAssetActivity : BaseActivity() {
|
||||
conn?.disconnect()
|
||||
}
|
||||
}
|
||||
private fun addBuiltInGeoItems(assets: List<Pair<String, AssetUrlItem>>): List<Pair<String, AssetUrlItem>> {
|
||||
val list = mutableListOf<Pair<String, AssetUrlItem>>()
|
||||
builtInGeoFiles.forEach {
|
||||
list.add(Utils.getUuid() to AssetUrlItem(
|
||||
it,
|
||||
AppConfig.geoUrl + it
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return list + assets
|
||||
}
|
||||
|
||||
inner class UserAssetAdapter : RecyclerView.Adapter<UserAssetViewHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserAssetViewHolder {
|
||||
return UserAssetViewHolder(ItemRecyclerUserAssetBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
return UserAssetViewHolder(
|
||||
ItemRecyclerUserAssetBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false)
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: UserAssetViewHolder, position: Int) {
|
||||
val file = extDir.listFiles()?.getOrNull(position) ?: return
|
||||
holder.itemUserAssetBinding.assetName.text = file.name
|
||||
val dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)
|
||||
holder.itemUserAssetBinding.assetProperties.text = "${file.length().toTrafficString()} • ${dateFormat.format(Date(file.lastModified()))}"
|
||||
if (file.name in geofiles) {
|
||||
var assets = MmkvManager.decodeAssetUrls();
|
||||
assets = addBuiltInGeoItems(assets);
|
||||
val item = assets.getOrNull(position) ?: return
|
||||
// file with name == item.second.remarks
|
||||
val file = extDir.listFiles()?.find { it.name == item.second.remarks }
|
||||
|
||||
holder.itemUserAssetBinding.assetName.text = item.second.remarks
|
||||
|
||||
if (file != null) {
|
||||
val dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)
|
||||
holder.itemUserAssetBinding.assetProperties.text =
|
||||
"${file.length().toTrafficString()} • ${dateFormat.format(Date(file.lastModified()))}"
|
||||
} else {
|
||||
holder.itemUserAssetBinding.assetProperties.text = getString(R.string.msg_file_not_found)
|
||||
}
|
||||
|
||||
if (item.second.remarks in builtInGeoFiles) {
|
||||
holder.itemUserAssetBinding.layoutEdit.visibility = GONE
|
||||
holder.itemUserAssetBinding.layoutRemove.visibility = GONE
|
||||
} else {
|
||||
holder.itemUserAssetBinding.layoutEdit.visibility = item.second.url.let { if (it == "file") GONE else VISIBLE }
|
||||
holder.itemUserAssetBinding.layoutRemove.visibility = VISIBLE
|
||||
}
|
||||
|
||||
holder.itemUserAssetBinding.layoutEdit.setOnClickListener {
|
||||
val intent = Intent(this@UserAssetActivity, UserAssetUrlActivity::class.java)
|
||||
intent.putExtra("assetId", item.first)
|
||||
startActivity(intent)
|
||||
}
|
||||
holder.itemUserAssetBinding.layoutRemove.setOnClickListener {
|
||||
file.delete()
|
||||
file?.delete()
|
||||
MmkvManager.removeAssetUrl(item.first)
|
||||
binding.recyclerView.adapter?.notifyItemRemoved(position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return extDir.listFiles()?.size ?: 0
|
||||
var assets = MmkvManager.decodeAssetUrls();
|
||||
assets = addBuiltInGeoItems(assets);
|
||||
return assets.size
|
||||
}
|
||||
}
|
||||
|
||||
class UserAssetViewHolder(val itemUserAssetBinding: ItemRecyclerUserAssetBinding) : RecyclerView.ViewHolder(itemUserAssetBinding.root)
|
||||
class UserAssetViewHolder(val itemUserAssetBinding: ItemRecyclerUserAssetBinding) :
|
||||
RecyclerView.ViewHolder(itemUserAssetBinding.root)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityUserAssetUrlBinding
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.io.File
|
||||
|
||||
class UserAssetUrlActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityUserAssetUrlBinding
|
||||
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
|
||||
val extDir by lazy { File(Utils.userAssetPath(this)) }
|
||||
private val assetStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val editAssetId by lazy { intent.getStringExtra("assetId").orEmpty() }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityUserAssetUrlBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
title = getString(R.string.title_user_asset_add_url)
|
||||
|
||||
val json = assetStorage?.decodeString(editAssetId)
|
||||
if (!json.isNullOrBlank()) {
|
||||
bindingAsset(Gson().fromJson(json, AssetUrlItem::class.java))
|
||||
} else {
|
||||
clearAsset()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* bingding seleced asset config
|
||||
*/
|
||||
private fun bindingAsset(assetItem: AssetUrlItem): Boolean {
|
||||
binding.etRemarks.text = Utils.getEditable(assetItem.remarks)
|
||||
binding.etUrl.text = Utils.getEditable(assetItem.url)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* clear or init asset config
|
||||
*/
|
||||
private fun clearAsset(): Boolean {
|
||||
binding.etRemarks.text = null
|
||||
binding.etUrl.text = null
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save asset config
|
||||
*/
|
||||
private fun saveServer(): Boolean {
|
||||
val assetItem: AssetUrlItem
|
||||
val json = assetStorage?.decodeString(editAssetId)
|
||||
var assetId = editAssetId
|
||||
if (!json.isNullOrBlank()) {
|
||||
assetItem = Gson().fromJson(json, AssetUrlItem::class.java)
|
||||
|
||||
// remove file associated with the asset
|
||||
val file = extDir.resolve(assetItem.remarks)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
} else {
|
||||
assetId = Utils.getUuid()
|
||||
assetItem = AssetUrlItem()
|
||||
}
|
||||
|
||||
assetItem.remarks = binding.etRemarks.text.toString()
|
||||
assetItem.url = binding.etUrl.text.toString()
|
||||
|
||||
// check remarks unique
|
||||
val assetList = MmkvManager.decodeAssetUrls()
|
||||
if (assetList.any { it.second.remarks == assetItem.remarks && it.first != assetId }) {
|
||||
toast(R.string.msg_remark_is_duplicate)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
if (TextUtils.isEmpty(assetItem.remarks)) {
|
||||
toast(R.string.sub_setting_remarks)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(assetItem.url)) {
|
||||
toast(R.string.title_url)
|
||||
return false
|
||||
}
|
||||
|
||||
assetStorage?.encode(assetId, Gson().toJson(assetItem))
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
private fun deleteServer(): Boolean {
|
||||
if (editAssetId.isNotEmpty()) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
MmkvManager.removeAssetUrl(editAssetId)
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.action_server, menu)
|
||||
del_config = menu.findItem(R.id.del_config)
|
||||
save_config = menu.findItem(R.id.save_config)
|
||||
|
||||
if (editAssetId.isEmpty()) {
|
||||
del_config?.isVisible = false
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.del_config -> {
|
||||
deleteServer()
|
||||
true
|
||||
}
|
||||
R.id.save_config -> {
|
||||
saveServer()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.v2ray.ang.util
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.dto.ServerAffiliationInfo
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
@@ -12,6 +13,7 @@ object MmkvManager {
|
||||
const val ID_SERVER_RAW = "SERVER_RAW"
|
||||
const val ID_SERVER_AFF = "SERVER_AFF"
|
||||
const val ID_SUB = "SUB"
|
||||
const val ID_ASSET = "ASSET"
|
||||
const val ID_SETTING = "SETTING"
|
||||
const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
|
||||
const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
|
||||
@@ -20,6 +22,7 @@ object MmkvManager {
|
||||
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val assetStorage by lazy { MMKV.mmkvWithID(ID_ASSET, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
fun decodeServerList(): MutableList<String> {
|
||||
val json = mainStorage?.decodeString(KEY_ANG_CONFIGS)
|
||||
@@ -142,6 +145,22 @@ object MmkvManager {
|
||||
removeServerViaSubid(subid)
|
||||
}
|
||||
|
||||
fun decodeAssetUrls(): List<Pair<String, AssetUrlItem>> {
|
||||
val assetUrlItems = mutableListOf<Pair<String, AssetUrlItem>>()
|
||||
assetStorage?.allKeys()?.forEach { key ->
|
||||
val json = assetStorage?.decodeString(key)
|
||||
if (!json.isNullOrBlank()) {
|
||||
assetUrlItems.add(Pair(key, Gson().fromJson(json, AssetUrlItem::class.java)))
|
||||
}
|
||||
}
|
||||
assetUrlItems.sortedBy { (_, value) -> value.addedTime }
|
||||
return assetUrlItems
|
||||
}
|
||||
|
||||
fun removeAssetUrl(assetid: String) {
|
||||
assetStorage?.remove(assetid)
|
||||
}
|
||||
|
||||
fun removeAllServer() {
|
||||
mainStorage?.clearAll()
|
||||
serverStorage?.clearAll()
|
||||
|
||||
86
V2rayNG/app/src/main/res/layout/activity_user_asset_url.xml
Normal file
86
V2rayNG/app/src/main/res/layout/activity_user_asset_url.xml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.UserAssetUrlActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/layout_margin_top_height">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/menu_item_add_asset"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sub_setting_remarks" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_remarks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/edit_height"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_url" />
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:gravity="top"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLines="10"
|
||||
android:minLines="5"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -19,27 +19,61 @@
|
||||
android:background="@color/colorPrimary"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:padding="@dimen/nav_header_vertical_spacing">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="9dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<TextView
|
||||
android:id="@+id/asset_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:minLines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
|
||||
tools:text="Placeholder.dat" />
|
||||
|
||||
<TextView
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/asset_properties"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="textEnd"
|
||||
android:lines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textSize="12sp"
|
||||
tools:text="1MB . 2020.01.01" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_edit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/nav_header_vertical_spacing">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
android:layout_height="@dimen/png_height"
|
||||
app:srcCompat="@drawable/ic_edit_24dp"
|
||||
app:tint="?attr/colorMainText" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -2,10 +2,20 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/add_file"
|
||||
android:icon="@drawable/ic_add_24dp"
|
||||
android:title="@string/menu_item_add_file"
|
||||
app:showAsAction="ifRoom" />
|
||||
android:title="@string/menu_item_add_asset"
|
||||
app:showAsAction="ifRoom" >
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/add_file"
|
||||
android:title="@string/menu_item_add_file"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/add_url"
|
||||
android:title="@string/menu_item_add_url"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
</item>
|
||||
<item
|
||||
android:id="@+id/download_file"
|
||||
android:icon="@drawable/ic_cloud_download_24dp"
|
||||
|
||||
@@ -57,9 +57,6 @@
|
||||
<string name="server_lab_security4">المستخدم (اختياري)</string>
|
||||
<string name="server_lab_encryption">التشفير</string>
|
||||
<string name="server_lab_flow">التدفق</string>
|
||||
<string name="server_lab_public_key" translatable="false">مفتاح عام</string>
|
||||
<string name="server_lab_short_id" translatable="false">ShortId</string>
|
||||
<string name="server_lab_spider_x" translatable="false">SpiderX</string>
|
||||
<string name="server_lab_reserved">Reserved (اختياري)</string>
|
||||
<string name="server_lab_local_address">العنوان المحلي IPv4(اختياري)</string>
|
||||
<string name="server_lab_local_mtu">Mtu(optional, default 1420)</string>
|
||||
@@ -82,6 +79,9 @@
|
||||
<string name="menu_item_add_file">إضافة ملفات</string>
|
||||
<string name="menu_item_download_file">تحميل الملفات</string>
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="title_user_asset_add_url">أضف عنوان URL للأصل</string>
|
||||
<string name="msg_file_not_found">لم يتم العثور على الملف</string>
|
||||
<string name="msg_remark_is_duplicate">الملاحظات موجودة بالفعل</string>
|
||||
<string name="msg_dialog_progress">جار التحميل</string>
|
||||
<string name="menu_item_search">بحث</string>
|
||||
<string name="menu_item_select_all">تحديد الكل</string>
|
||||
@@ -221,4 +221,6 @@
|
||||
<item>VPN</item>
|
||||
<item>الوكيل فقط</item>
|
||||
</string-array>
|
||||
<string name="menu_item_add_asset">يضيف</string>
|
||||
<string name="menu_item_add_url">إضافة رابط</string>
|
||||
</resources>
|
||||
|
||||
@@ -82,6 +82,9 @@
|
||||
<string name="menu_item_download_file">دانلود فایلها</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="title_user_asset_add_url">URL را اضافه کنید</string>
|
||||
<string name="msg_file_not_found">فایل پیدا نشد</string>
|
||||
<string name="msg_remark_is_duplicate">نام قبلاً وجود دارد</string>
|
||||
<string name="msg_dialog_progress">بارگذاری</string>
|
||||
<string name="menu_item_search">جستجو</string>
|
||||
<string name="menu_item_select_all">انتخاب همه</string>
|
||||
@@ -253,5 +256,7 @@
|
||||
<item>VPN</item>
|
||||
<item>فقط پروکسی</item>
|
||||
</string-array>
|
||||
<string name="menu_item_add_asset">افزودن</string>
|
||||
<string name="menu_item_add_url">افزودن لینک</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -49,8 +49,6 @@
|
||||
<string name="server_lab_request_host">Запрос узла (WS/H2) / Шифрование QUIC</string>
|
||||
<string name="server_lab_path">Путь (WS/H2) / Ключ QUIC / Сид KCP / Сервис gRPC</string>
|
||||
<string name="server_lab_stream_security">TLS</string>
|
||||
<string name="server_lab_stream_fingerprint" translatable="false">Fingerprint</string>
|
||||
<string name="server_lab_stream_alpn" translatable="false">Alpn</string>
|
||||
<string name="server_lab_allow_insecure">Разрешать небезопасные</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">Адрес</string>
|
||||
@@ -84,6 +82,9 @@
|
||||
<string name="menu_item_download_file">Загрузить файлы</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="title_user_asset_add_url">Добавить URL ресурса</string>
|
||||
<string name="msg_file_not_found">Файл не найден</string>
|
||||
<string name="msg_remark_is_duplicate">Замечания уже есть</string>
|
||||
<string name="msg_dialog_progress">Загрузка…</string>
|
||||
<string name="menu_item_search">Поиск</string>
|
||||
<string name="menu_item_select_all">Выбрать все</string>
|
||||
@@ -256,5 +257,7 @@
|
||||
<item>VPN</item>
|
||||
<item>Только прокси</item>
|
||||
</string-array>
|
||||
<string name="menu_item_add_asset">Добавлять</string>
|
||||
<string name="menu_item_add_url">Добавить ссылку</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -82,6 +82,9 @@
|
||||
<string name="menu_item_download_file">Tải xuống tệp tin</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="title_user_asset_add_url">Thêm URL nội dung</string>
|
||||
<string name="msg_file_not_found">Không tìm thấy tập tin</string>
|
||||
<string name="msg_remark_is_duplicate">Nhận xét đã tồn tại</string>
|
||||
<string name="msg_dialog_progress">Đang tải...</string>
|
||||
<string name="menu_item_search">Tìm kiếm</string>
|
||||
<string name="menu_item_select_all">Chọn tất cả</string>
|
||||
@@ -255,5 +258,7 @@
|
||||
<item>Chế độ VPN</item>
|
||||
<item>Chế độ Proxy</item>
|
||||
</string-array>
|
||||
<string name="menu_item_add_asset">Thêm vào</string>
|
||||
<string name="menu_item_add_url">Thêm liên kết</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -83,6 +83,9 @@
|
||||
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="title_user_asset_add_url">添加资产网址</string>
|
||||
<string name="msg_file_not_found">文件未找到</string>
|
||||
<string name="msg_remark_is_duplicate">备注已经存在</string>
|
||||
<string name="msg_dialog_progress">正在加载</string>
|
||||
<string name="menu_item_search">搜索</string>
|
||||
<string name="menu_item_select_all">全选</string>
|
||||
@@ -254,5 +257,7 @@
|
||||
</string-array>
|
||||
<string name="import_subscription_success">订阅导入成功</string>
|
||||
<string name="import_subscription_failure">导入订阅失败</string>
|
||||
<string name="menu_item_add_asset">添加</string>
|
||||
<string name="menu_item_add_url">添加链接</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -82,6 +82,9 @@
|
||||
<string name="menu_item_download_file">下載檔案</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="title_user_asset_add_url">新增資產網址</string>
|
||||
<string name="msg_file_not_found">文件未找到</string>
|
||||
<string name="msg_remark_is_duplicate">備註已經存在</string>
|
||||
<string name="msg_dialog_progress">載入</string>
|
||||
<string name="menu_item_search">搜尋</string>
|
||||
<string name="menu_item_select_all">全選</string>
|
||||
@@ -253,5 +256,7 @@
|
||||
</string-array>
|
||||
<string name="import_subscription_success">訂閱導入成功</string>
|
||||
<string name="import_subscription_failure">導入訂閱失敗</string>
|
||||
<string name="menu_item_add_asset">添加</string>
|
||||
<string name="menu_item_add_url">添加連結</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -85,8 +85,15 @@
|
||||
<string name="toast_malformed_josn">Config malformed</string>
|
||||
<string name="server_lab_request_host6">Host(SNI)(Optional)</string>
|
||||
<string name="toast_asset_copy_failed">File copy failed, please use File Manager</string>
|
||||
<string name="menu_item_add_asset">Add asset</string>
|
||||
<string name="menu_item_add_file">Add files</string>
|
||||
<string name="menu_item_add_url">Add URL</string>
|
||||
<string name="title_url" translatable="false">URL</string>
|
||||
<string name="menu_item_download_file">Download files</string>
|
||||
<string name="title_user_asset_add_url">Add asset URL</string>
|
||||
<string name="msg_file_not_found">File not found</string>
|
||||
<string name="msg_remark_is_duplicate">The remarks already exists</string>
|
||||
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">Loading</string>
|
||||
|
||||
Reference in New Issue
Block a user