Compare commits

...

73 Commits

Author SHA1 Message Date
2dust 2341eceb65 up 1.8.8 2023-09-25 11:32:57 +08:00
2dust ec5f7245bf up 1.8.7 2023-09-25 11:20:21 +08:00
2dust a46b0d58eb Merge pull request #2500 from MrIbrahem/master
Update arrays.xml to support Arabic language
2023-09-06 07:21:10 +08:00
ibrahem Qasim d1262d169b Update arrays.xml 2023-09-01 17:39:16 +03:00
2dust 4a85c95b02 Merge pull request #2485 from y67h41/fix-/0-CIDR
Make /0 CIDR parsed as IP
2023-09-01 22:33:13 +08:00
2dust a368927b9f Merge pull request #2475 from user09283/master-1
Update strings.xml
2023-09-01 22:31:51 +08:00
2dust 80feb69af5 Merge pull request #2460 from MrIbrahem/master
Create arabic translations
2023-09-01 22:31:38 +08:00
2dust c09f0d5787 Merge pull request #2478 from yuhan6665/rprx-ui
A freshing new UI
2023-09-01 22:31:13 +08:00
y67h41 ff667546b8 Update ExampleUnitTest.kt 2023-08-24 17:55:12 +08:00
y67h41 6a255cdfa4 Make /0 CIDR be parsed as IP 2023-08-24 17:36:49 +08:00
yuhan6665 2b5784df6f Update UI: status bar icon color 2023-08-20 22:09:30 -04:00
user09283 a9665dd2d8 Update strings.xml 2023-08-19 13:11:14 +07:00
user09283 1e52877e93 Update strings.xml
Update Vietnamese language
2023-08-19 13:10:08 +07:00
yuhan6665 a7a70b448f Update UI: tweak fab color 2023-08-15 20:14:43 -04:00
yuhan6665 3e52aeb804 Update UI: custom font 2023-08-13 23:10:20 -04:00
yuhan6665 4b4f5d145b Update UI: a concept for launching icon 2023-08-13 15:01:07 -04:00
ibrahem Qasim 34d8329c8a Delete plurals.xml 2023-08-10 21:12:02 +03:00
ibrahem Qasim 2e7ae732aa Update strings.xml 2023-08-10 21:10:10 +03:00
ibrahem Qasim 424287e258 Rename s.xml to strings.xml 2023-08-10 13:26:25 +03:00
ibrahem Qasim 40d03bbb96 Delete strings.xml 2023-08-10 13:26:05 +03:00
ibrahem Qasim d768694445 Update s.xml 2023-08-10 13:18:58 +03:00
ibrahem Qasim 56c7f2ef69 Update s.xml 2023-08-10 13:13:15 +03:00
ibrahem Qasim 3839b0c59c Update s.xml 2023-08-10 13:07:36 +03:00
ibrahem Qasim 77cab14ae8 Create s.xml 2023-08-10 12:58:37 +03:00
ibrahem Qasim b0dbd4c7ca Create plurals.xml 2023-08-10 12:38:18 +03:00
ibrahem Qasim 50ca2e0e69 Create strings.xml 2023-08-10 12:36:43 +03:00
yuhan6665 0bf8beda94 Update UI: tweak after review 2023-08-09 21:38:20 -04:00
yuhan6665 4d334929c9 Update UI: complete change for dark mode 2023-08-07 14:32:41 -04:00
yuhan6665 ccfdf096f9 Update UI dark mode 2023-08-06 22:44:57 -04:00
yuhan6665 b4f2af2778 Update UI: header image 2023-08-06 21:53:14 -04:00
yuhan6665 32b9e4855c Update UI: clean up theme 2023-08-06 18:41:36 -04:00
yuhan6665 bdbce5147e Update UI: menu icons 2023-08-06 17:37:24 -04:00
yuhan6665 a4833506ae Update UI: launching icon 2023-08-06 16:55:29 -04:00
yuhan6665 d4e8072248 Update UI for white theme 2023-08-06 16:09:14 -04:00
yuhan6665 207ca93735 Fix lint by adding missing translation 2023-08-06 10:31:37 -04:00
2dust 99307ab8f0 up 1.8.6 2023-07-30 11:05:31 +08:00
2dust 1ec23a7b39 Merge pull request #2302 from dep4/fix/package-name
AppConfig: use current package name to receive broadcasts
2023-05-23 06:15:07 +08:00
Bruce Mills 685f9e220c AppConfig: use current package name to receive broadcasts 2023-05-21 15:54:25 -04:00
2dust e77b7eb52e Merge pull request #2290 from CUMOON/master
Correction of Persian translation
2023-05-21 08:35:38 +08:00
2dust 29014704e0 Merge pull request #2288 from hadi-norouzi/feature/tv
feature: add leanback for android tv compatiblity
2023-05-21 08:35:13 +08:00
Moon b3570d9c0b Correction of Persian translation 2023-05-17 12:49:41 +03:30
Hadi Norouzi 6e427cee82 feature: add leanback for android tv compatiblity 2023-05-16 11:33:29 +03:30
2dust 0a1b6d00d9 Merge pull request #2284 from solokot/master
RU: Improvements for google translation
2023-05-15 20:24:19 +08:00
solokot 00ffe66f36 RU: Improvements for google translation 2023-05-15 09:13:11 +03:00
2dust 63661fbdaa Merge pull request #2273 from hadi-norouzi/master
[Feature] separate package names for pre release and dev builds
2023-05-13 19:25:53 +08:00
Hadi Norouzi 4d4a4543c5 feature: import subscription from v2rayng://install-sub deeplink 2023-05-11 20:42:15 +03:30
Hadi Norouzi 2349805968 feature: separate package names for pre release and dev builds 2023-05-10 20:16:11 +03:30
2dust bdbd0b154e up 1.8.5 2023-05-07 15:35:42 +08:00
2dust 653137ec58 bug fixes 2023-05-07 15:34:11 +08:00
2dust 39f65850be Merge pull request #2258 from Amir-yazdanmanesh/master
Fix persian translation
2023-05-07 15:19:33 +08:00
2dust 2d8db97417 Merge branch 'master' into master 2023-05-07 15:19:25 +08:00
2dust 17a6f80798 Merge pull request #2255 from hadi-norouzi/feature/share_sub
Feature/share sub
2023-05-07 15:18:28 +08:00
2dust 381fe859ff Merge pull request #2254 from hadi-norouzi/master
Fix status bar and navigation bar, remove FooterView TouchHelper
2023-05-07 15:14:04 +08:00
am.yazdanmanesh e2bdf17b82 Fix persian translation 2023-05-05 21:54:18 +03:30
Hadi Norouzi d20afc6801 Share subscription implemented 2023-05-04 18:03:50 +03:30
Hadi Norouzi 54046a27e6 Fix fa strings and test layout direction 2023-05-04 17:04:35 +03:30
Hadi Norouzi 36c000d18a Fix status bar and navigation bar, remove FooterView TouchHelper 2023-05-04 16:34:41 +03:30
2dust 2f3c2cf4d5 up 1.8.4 2023-04-29 20:46:28 +08:00
2dust 892358d5d8 Merge pull request #2227 from AlirezaIvaz/master
Add themed icon support
2023-04-27 09:40:08 +08:00
Alireza Ivaz 6dced903cd Add themed icon support 2023-04-26 17:05:11 +03:30
2dust f8a98a426e Merge pull request #2213 from solokot/master
Updated Russian translation
2023-04-22 14:40:57 +08:00
solokot 011506e99f Updated Russian translation 2023-04-21 21:33:22 +03:00
2dust 966151d3fe bug fixes 2023-04-19 19:45:11 +08:00
2dust 247f4db77e up 1.8.3 2023-04-18 09:51:49 +08:00
2dust 9a661bc401 Use Google code scanner 2023-04-17 10:28:34 +08:00
2dust f5f1b3816c Refactor createQRCode 2023-04-16 19:58:51 +08:00
2dust 3971c9badc use Exception 2023-04-16 19:56:44 +08:00
2dust 390fbf046b Merge pull request #2130 from pouriaksrvi/master
[New Feature] UX enhancement - Reversed the order of server list
2023-04-06 08:28:54 +08:00
Programmer ec7ba59528 Bug Fix - Fixed the order of subscription import
Subscription server list should not be reversed when it is being imported. Fixed this problem.
2023-04-03 04:12:01 +03:30
Programmer 838941c6a4 Merge branch '2dust:master' into master 2023-04-03 04:00:52 +03:30
2dust a0ec764b64 Merge pull request #2142 from solokot/master
Updated Russian translation
2023-04-02 09:32:29 +08:00
solokot 4cfe3394b1 Updated Russian translation 2023-03-29 09:35:28 +03:00
Programmer ae9aa75ba0 New Feature
Fixed incorrect default order of server list as described in the issue https://github.com/2dust/v2rayNG/issues/2116
The order was problematic for users who add new servers more frequently
2023-03-26 06:27:52 +00:00
129 changed files with 1152 additions and 343 deletions
+28 -12
View File
@@ -8,8 +8,8 @@ android {
buildToolsVersion "$buildToolsVer" buildToolsVersion "$buildToolsVer"
compileOptions { compileOptions {
targetCompatibility = "8" sourceCompatibility = JavaVersion.VERSION_17
sourceCompatibility = "8" targetCompatibility = JavaVersion.VERSION_17
} }
defaultConfig { defaultConfig {
@@ -17,8 +17,8 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion Integer.parseInt("$targetSdkVer") targetSdkVersion Integer.parseInt("$targetSdkVer")
multiDexEnabled true multiDexEnabled true
versionCode 510 versionCode 522
versionName "1.8.2" versionName "1.8.8"
} }
buildTypes { buildTypes {
@@ -36,6 +36,21 @@ android {
} }
} }
// flavorDimensions "versions"
//
// productFlavors {
// dev {
// applicationIdSuffix = ".dev"
// versionNameSuffix = "-dev"
// }
// pre_release {
// applicationIdSuffix = ".pre"
// versionNameSuffix = "-pre-release"
// }
// prod {
// }
// }
sourceSets { sourceSets {
main { main {
jniLibs.srcDirs = ['libs'] jniLibs.srcDirs = ['libs']
@@ -72,6 +87,7 @@ android {
buildFeatures { buildFeatures {
viewBinding true viewBinding true
buildConfig true
} }
namespace 'com.v2ray.ang' namespace 'com.v2ray.ang'
testNamespace 'com.v2ray.angTest' testNamespace 'com.v2ray.angTest'
@@ -85,16 +101,16 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0' implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0' implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.fragment:fragment-ktx:1.5.6' implementation 'androidx.fragment:fragment-ktx:1.5.7'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
// Androidx ktx // Androidx ktx
implementation 'androidx.activity:activity-ktx:1.7.0' implementation 'androidx.activity:activity-ktx:1.7.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
@@ -109,11 +125,11 @@ dependencies {
implementation 'io.reactivex:rxjava:1.3.8' implementation 'io.reactivex:rxjava:1.3.8'
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar' implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
implementation 'me.dm7.barcodescanner:core:1.9.8'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar' implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
implementation 'me.drakeet.support:toastcompat:1.1.0' implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation 'com.blacksquircle.ui:editorkit:2.1.1' implementation 'com.blacksquircle.ui:editorkit:2.8.0'
implementation 'com.blacksquircle.ui:language-base:2.1.1' implementation 'com.blacksquircle.ui:language-base:2.8.0'
implementation 'com.blacksquircle.ui:language-json:2.1.1' implementation 'com.blacksquircle.ui:language-json:2.8.0'
implementation 'io.github.g00fy2.quickie:quickie-bundled:1.6.0'
implementation 'com.google.zxing:core:3.5.1'
} }
@@ -1,13 +0,0 @@
package com.v2ray.ang;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="app_name" type="string">v2rayNG (DEV)</item>
</resources>
+9 -2
View File
@@ -9,8 +9,12 @@
android:largeScreens="true" android:largeScreens="true"
android:xlargeScreens="true"/> android:xlargeScreens="true"/>
<uses-sdk android:minSdkVersion="21" tools:overrideLibrary="com.blacksquircle.ui.editorkit"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/> <uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility --> <!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
@@ -31,6 +35,7 @@
android:name=".AngApplication" android:name=".AngApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:banner="@mipmap/ic_banner"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppThemeDayNight" android:theme="@style/AppThemeDayNight"
@@ -45,6 +50,7 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" /> <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
@@ -109,8 +115,9 @@
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="v2rayng" <data android:scheme="v2rayng"/>
android:host="install-config" /> <data android:host="install-config"/>
<data android:host="install-sub"/>
</intent-filter> </intent-filter>
</activity> </activity>
@@ -5,7 +5,7 @@ package com.v2ray.ang
* App Config Const * App Config Const
*/ */
object AppConfig { object AppConfig {
const val ANG_PACKAGE = "com.v2ray.ang" const val ANG_PACKAGE = BuildConfig.APPLICATION_ID
const val DIR_ASSETS = "assets" const val DIR_ASSETS = "assets"
// legacy // legacy
@@ -39,6 +39,7 @@ object AppConfig {
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set" const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
const val PREF_BYPASS_APPS = "pref_bypass_apps" const val PREF_BYPASS_APPS = "pref_bypass_apps"
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove" const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
const val HTTP_PROTOCOL: String = "http://" const val HTTP_PROTOCOL: String = "http://"
const val HTTPS_PROTOCOL: String = "https://" const val HTTPS_PROTOCOL: String = "https://"
@@ -4,7 +4,6 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.text.TextUtils import android.text.TextUtils
import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.service.V2RayServiceManager
@@ -34,7 +33,7 @@ class TaskerReceiver : BroadcastReceiver() {
} else { } else {
Utils.stopVService(context) Utils.stopVService(context)
} }
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }
@@ -38,12 +38,18 @@ class WidgetProvider : AppWidgetProvider() {
}) })
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent) remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
if (isRunning) { if (isRunning) {
if (!Utils.getDarkModeStatus(context)) {
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stat_name)
}
remoteViews.setInt( remoteViews.setInt(
R.id.layout_switch, R.id.layout_switch,
"setBackgroundResource", "setBackgroundResource",
R.drawable.ic_rounded_corner_theme R.drawable.ic_rounded_corner_active
) )
} else { } else {
if (!Utils.getDarkModeStatus(context)) {
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_stat_name_black)
}
remoteViews.setInt( remoteViews.setInt(
R.id.layout_switch, R.id.layout_switch,
"setBackgroundResource", "setBackgroundResource",
@@ -25,6 +25,7 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
@@ -65,6 +66,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
setContentView(view) setContentView(view)
title = getString(R.string.title_server) title = getString(R.string.title_server)
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
if (!Utils.getDarkModeStatus(this)) {
WindowCompat.getInsetsController(window, window.decorView).apply {
isAppearanceLightStatusBars = true
}
}
binding.fab.setOnClickListener { binding.fab.setOnClickListener {
if (mainViewModel.isRunning.value == true) { if (mainViewModel.isRunning.value == true) {
@@ -131,11 +137,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
mainViewModel.isRunning.observe(this) { isRunning -> mainViewModel.isRunning.observe(this) { isRunning ->
adapter.isRunning = isRunning adapter.isRunning = isRunning
if (isRunning) { if (isRunning) {
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorSelected)) if (!Utils.getDarkModeStatus(this)) {
binding.fab.setImageResource(R.drawable.ic_stat_name)
}
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_orange))
setTestState(getString(R.string.connection_connected)) setTestState(getString(R.string.connection_connected))
binding.layoutTest.isFocusable = true binding.layoutTest.isFocusable = true
} else { } else {
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorUnselected)) if (!Utils.getDarkModeStatus(this)) {
binding.fab.setImageResource(R.drawable.ic_stat_name)
}
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_grey))
setTestState(getString(R.string.connection_not_connected)) setTestState(getString(R.string.connection_not_connected))
binding.layoutTest.isFocusable = false binding.layoutTest.isFocusable = false
} }
@@ -558,8 +570,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
* read content from uri * read content from uri
*/ */
private fun readContentFromUri(uri: Uri) { private fun readContentFromUri(uri: Uri) {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this) RxPermissions(this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE) .request(permission)
.subscribe { .subscribe {
if (it) { if (it) {
try { try {
@@ -608,13 +625,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// } // }
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B) {
moveTaskToBack(false) moveTaskToBack(false)
return true return true
} }
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)
} }
fun showCircle() { fun showCircle() {
binding.fabProgressCircle.show() binding.fabProgressCircle.show()
} }
@@ -228,7 +228,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder
class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) : class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) :
BaseViewHolder(itemFooterBinding.root), ItemTouchHelperViewHolder BaseViewHolder(itemFooterBinding.root)
override fun onItemDismiss(position: Int) { override fun onItemDismiss(position: Int) {
val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return
@@ -3,58 +3,62 @@ package com.v2ray.ang.ui
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.os.Bundle import android.os.Bundle
import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Build import android.os.Build
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import com.google.zxing.BarcodeFormat
import com.tbruyelle.rxpermissions.RxPermissions import com.tbruyelle.rxpermissions.RxPermissions
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.QRCodeDecoder import com.v2ray.ang.util.QRCodeDecoder
import io.github.g00fy2.quickie.QRResult
import io.github.g00fy2.quickie.ScanCustomCode
import io.github.g00fy2.quickie.config.ScannerConfig
class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler { class ScannerActivity : BaseActivity(){
private var mScannerView: ZXingScannerView? = null private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
mScannerView?.setAutoFocus(true) if (settingsStorage?.decodeBool(AppConfig.PREF_START_SCAN_IMMEDIATE) == true) {
val formats = ArrayList<BarcodeFormat>() launchScan()
formats.add(BarcodeFormat.QR_CODE) }
mScannerView?.setFormats(formats)
setContentView(mScannerView) // Set the scanner view as the content view
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
} }
public override fun onResume() { public override fun onResume() {
super.onResume() super.onResume()
mScannerView!!.setResultHandler(this) // Register ourselves as a handler for scan results.
mScannerView!!.startCamera() // Start camera on resume
} }
public override fun onPause() { public override fun onPause() {
super.onPause() super.onPause()
mScannerView!!.stopCamera() // Stop camera on pause
} }
override fun handleResult(rawResult: Result) { private fun launchScan(){
// Do something with the result here scanQrCode.launch(
// Log.v(FragmentActivity.TAG, rawResult.text) // Prints scan results ScannerConfig.build {
// Log.v(FragmentActivity.TAG, rawResult.barcodeFormat.toString()) // Prints the scan format (qrcode, pdf417 etc.) setHapticSuccessFeedback(true) // enable (default) or disable haptic feedback when a barcode was detected
setShowTorchToggle(true) // show or hide (default) torch/flashlight toggle button
setShowCloseButton(true) // show or hide (default) close button
}
)
}
finished(rawResult.text) private fun handleResult(result: QRResult) {
if (result is QRResult.QRSuccess ) {
// If you would like to resume scanning, call this method below: finished(result.content.rawValue)
// mScannerView!!.resumeCameraPreview(this) } else {
finish()
}
} }
private fun finished(text: String) { private fun finished(text: String) {
@@ -70,6 +74,10 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
} }
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.scan_code -> {
launchScan()
true
}
R.id.select_photo -> { R.id.select_photo -> {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES Manifest.permission.READ_MEDIA_IMAGES
@@ -6,6 +6,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.blacksquircle.ui.editorkit.utils.EditorTheme
import com.blacksquircle.ui.language.json.JsonLanguage import com.blacksquircle.ui.language.json.JsonLanguage
import com.google.gson.* import com.google.gson.*
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
@@ -38,6 +39,9 @@ class ServerCustomConfigActivity : BaseActivity() {
setContentView(view) setContentView(view)
title = getString(R.string.title_server) title = getString(R.string.title_server)
if (!Utils.getDarkModeStatus(this)) {
binding.editor.colorScheme = EditorTheme.INTELLIJ_LIGHT
}
binding.editor.language = JsonLanguage() binding.editor.language = JsonLanguage()
val config = MmkvManager.decodeServerConfig(editGuid) val config = MmkvManager.decodeServerConfig(editGuid)
if (config != null) { if (config != null) {
@@ -2,20 +2,34 @@ package com.v2ray.ang.ui
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.google.gson.Gson import com.google.gson.Gson
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ItemQrcodeBinding
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.QRCodeDecoder
import com.v2ray.ang.util.Utils
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() { class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() {
private var mActivity: SubSettingActivity = activity private var mActivity: SubSettingActivity = activity
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) } private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
private val share_method: Array<out String> by lazy {
mActivity.resources.getStringArray(R.array.share_sub_method)
}
override fun getItemCount() = mActivity.subscriptions.size override fun getItemCount() = mActivity.subscriptions.size
override fun onBindViewHolder(holder: MainViewHolder, position: Int) { override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
@@ -31,7 +45,8 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
holder.itemView.setBackgroundColor(Color.TRANSPARENT) holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.itemSubSettingBinding.layoutEdit.setOnClickListener { holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java) mActivity.startActivity(
Intent(mActivity, SubEditActivity::class.java)
.putExtra("subId", subId) .putExtra("subId", subId)
) )
} }
@@ -40,11 +55,51 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
subStorage?.encode(subId, Gson().toJson(subItem)) subStorage?.encode(subId, Gson().toJson(subItem))
notifyItemChanged(position) notifyItemChanged(position)
} }
if (TextUtils.isEmpty(subItem.url)) {
holder.itemSubSettingBinding.layoutShare.visibility = View.INVISIBLE
} else {
holder.itemSubSettingBinding.layoutShare.setOnClickListener {
AlertDialog.Builder(mActivity)
.setItems(share_method.asList().toTypedArray()) { _, i ->
try {
when (i) {
0 -> {
val ivBinding =
ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
ivBinding.ivQcode.setImageBitmap(
QRCodeDecoder.createQRCode(
subItem.url
)
)
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
}
1 -> {
Utils.setClipboard(mActivity, subItem.url)
}
else -> mActivity.toast("else")
}
} catch (e: Exception) {
e.printStackTrace()
}
}.show()
}
}
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
return MainViewHolder(ItemRecyclerSubSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)) return MainViewHolder(
ItemRecyclerSubSettingBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
} }
class MainViewHolder(val itemSubSettingBinding: ItemRecyclerSubSettingBinding) : RecyclerView.ViewHolder(itemSubSettingBinding.root) class MainViewHolder(val itemSubSettingBinding: ItemRecyclerSubSettingBinding) :
RecyclerView.ViewHolder(itemSubSettingBinding.root)
} }
@@ -11,7 +11,6 @@ import android.content.Intent
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.databinding.ActivityTaskerBinding import com.v2ray.ang.databinding.ActivityTaskerBinding
@@ -65,7 +64,7 @@ class TaskerActivity : BaseActivity() {
listview?.setItemChecked(pos, true) listview?.setItemChecked(pos, true)
} }
} }
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -3,11 +3,12 @@ package com.v2ray.ang.ui
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import com.google.zxing.WriterException import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityLogcatBinding import com.v2ray.ang.databinding.ActivityLogcatBinding
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import java.net.URLDecoder
class UrlSchemeActivity : BaseActivity() { class UrlSchemeActivity : BaseActivity() {
private lateinit var binding: ActivityLogcatBinding private lateinit var binding: ActivityLogcatBinding
@@ -18,34 +19,68 @@ class UrlSchemeActivity : BaseActivity() {
val view = binding.root val view = binding.root
setContentView(view) setContentView(view)
var shareUrl: String = ""
try { try {
intent?.apply { intent.apply {
when (action) { if (action == Intent.ACTION_SEND) {
Intent.ACTION_SEND -> { if ("text/plain" == type) {
if ("text/plain" == type) { intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { val uri = Uri.parse(it)
shareUrl = it if (uri.scheme?.startsWith(AppConfig.HTTPS_PROTOCOL) == true || uri.scheme?.startsWith(
AppConfig.HTTP_PROTOCOL
) == true
) {
val name = uri.getQueryParameter("name") ?: "Subscription"
importSubscription(it, name)
} else {
importConfig(it)
} }
} }
} }
Intent.ACTION_VIEW -> { } else if (action == Intent.ACTION_VIEW) {
val uri: Uri? = intent.data when (data?.host) {
shareUrl = uri?.getQueryParameter("url")!! "install-config" -> {
val uri: Uri? = intent.data
val shareUrl: String = uri?.getQueryParameter("url")!!
toast(shareUrl)
importConfig(shareUrl)
}
"install-sub" -> {
val uri: Uri? = intent.data
val url = uri?.getQueryParameter("url")!!
val name = uri.getQueryParameter("name") ?: "Subscription"
importSubscription(url, name)
}
else -> {
toast(R.string.toast_failure)
}
} }
} }
} }
toast(shareUrl)
val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
if (count > 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
startActivity(Intent(this, MainActivity::class.java)) startActivity(Intent(this, MainActivity::class.java))
finish() finish()
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }
private fun importSubscription(url: String, name: String) {
val decodedUrl = URLDecoder.decode(url, "UTF-8")
val check = AngConfigManager.importSubscription(name, decodedUrl)
if (check) toast(R.string.import_subscription_success) else toast(R.string.import_subscription_failure)
}
private fun importConfig(shareUrl: String) {
val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
if (count > 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
}
} }
@@ -4,6 +4,7 @@ import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.util.Log import android.util.Log
@@ -75,7 +76,14 @@ class UserAssetActivity : BaseActivity() {
} }
private fun showFileChooser() { private fun showFileChooser() {
RxPermissions(this).request(Manifest.permission.READ_EXTERNAL_STORAGE).subscribe { val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this)
.request(permission)
.subscribe {
if (it) { if (it) {
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*" intent.type = "*/*"
@@ -91,7 +99,8 @@ class UserAssetActivity : BaseActivity() {
} catch (ex: android.content.ActivityNotFoundException) { } catch (ex: android.content.ActivityNotFoundException) {
toast(R.string.toast_require_file_manager) toast(R.string.toast_require_file_manager)
} }
} } else
toast(R.string.toast_permission_denied)
} }
} }
@@ -20,11 +20,27 @@ import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
import java.net.URI import java.net.URI
import java.util.* import java.util.*
import com.v2ray.ang.extension.idnHost import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.extension.toast
object AngConfigManager { object AngConfigManager {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) } private val mainStorage by lazy {
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) } MMKV.mmkvWithID(
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) } MmkvManager.ID_MAIN,
MMKV.MULTI_PROCESS_MODE
)
}
private val serverRawStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SERVER_RAW,
MMKV.MULTI_PROCESS_MODE
)
}
private val settingsStorage by lazy {
MMKV.mmkvWithID(
MmkvManager.ID_SETTING,
MMKV.MULTI_PROCESS_MODE
)
}
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) } private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
/** /**
@@ -82,8 +98,14 @@ object AngConfigManager {
).forEach { key -> ).forEach { key ->
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false)) settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
} }
settingsStorage?.encode(AppConfig.PREF_SNIFFING_ENABLED, sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)) settingsStorage?.encode(
settingsStorage?.encode(AppConfig.PREF_PER_APP_PROXY_SET, sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf())) AppConfig.PREF_SNIFFING_ENABLED,
sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true)
)
settingsStorage?.encode(
AppConfig.PREF_PER_APP_PROXY_SET,
sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf())
)
} }
private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) { private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) {
@@ -125,7 +147,8 @@ object AngConfigManager {
if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) { if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) {
server.users = null server.users = null
} else { } else {
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean() val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = vmessBean.security socksUsersBean.user = vmessBean.security
socksUsersBean.pass = vmessBean.id socksUsersBean.pass = vmessBean.id
server.users = listOf(socksUsersBean) server.users = listOf(socksUsersBean)
@@ -135,17 +158,27 @@ object AngConfigManager {
} }
} }
config.outboundBean?.streamSettings?.let { streamSetting -> config.outboundBean?.streamSettings?.let { streamSetting ->
val sni = streamSetting.populateTransportSettings(vmessBean.network, vmessBean.headerType, val sni = streamSetting.populateTransportSettings(
vmessBean.requestHost, vmessBean.path, vmessBean.path, vmessBean.requestHost, vmessBean.path, vmessBean.network,
vmessBean.headerType, vmessBean.path) vmessBean.headerType,
vmessBean.requestHost,
vmessBean.path,
vmessBean.path,
vmessBean.requestHost,
vmessBean.path,
vmessBean.headerType,
vmessBean.path
)
val allowInsecure = if (vmessBean.allowInsecure.isBlank()) { val allowInsecure = if (vmessBean.allowInsecure.isBlank()) {
settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
} else { } else {
vmessBean.allowInsecure.toBoolean() vmessBean.allowInsecure.toBoolean()
} }
var fingerprint = streamSetting.tlsSettings?.fingerprint var fingerprint = streamSetting.tlsSettings?.fingerprint
streamSetting.populateTlsSettings(vmessBean.streamSecurity, allowInsecure, streamSetting.populateTlsSettings(
vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null) vmessBean.streamSecurity, allowInsecure,
vmessBean.sni.ifBlank { sni }, fingerprint, null, null, null, null
)
} }
} }
val key = MmkvManager.encodeServerConfig(vmessBean.guid, config) val key = MmkvManager.encodeServerConfig(vmessBean.guid, config)
@@ -168,14 +201,21 @@ object AngConfigManager {
/** /**
* import config form qrcode or... * import config form qrcode or...
*/ */
private fun importConfig(str: String?, subid: String, removedSelectedServer: ServerConfig?): Int { private fun importConfig(
str: String?,
subid: String,
removedSelectedServer: ServerConfig?
): Int {
try { try {
if (str == null || TextUtils.isEmpty(str)) { if (str == null || TextUtils.isEmpty(str)) {
return R.string.toast_none_data return R.string.toast_none_data
} }
//maybe sub //maybe sub
if (TextUtils.isEmpty(subid) && (str.startsWith(HTTP_PROTOCOL) || str.startsWith(HTTPS_PROTOCOL))) { if (TextUtils.isEmpty(subid) && (str.startsWith(HTTP_PROTOCOL) || str.startsWith(
HTTPS_PROTOCOL
))
) {
MmkvManager.importUrlAsSubscription(str) MmkvManager.importUrlAsSubscription(str)
return 0 return 0
} }
@@ -201,9 +241,9 @@ object AngConfigManager {
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java) val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)
// Although VmessQRCode fields are non null, looks like Gson may still create null fields // Although VmessQRCode fields are non null, looks like Gson may still create null fields
if (TextUtils.isEmpty(vmessQRCode.add) if (TextUtils.isEmpty(vmessQRCode.add)
|| TextUtils.isEmpty(vmessQRCode.port) || TextUtils.isEmpty(vmessQRCode.port)
|| TextUtils.isEmpty(vmessQRCode.id) || TextUtils.isEmpty(vmessQRCode.id)
|| TextUtils.isEmpty(vmessQRCode.net) || TextUtils.isEmpty(vmessQRCode.net)
) { ) {
return R.string.toast_incorrect_protocol return R.string.toast_incorrect_protocol
} }
@@ -213,16 +253,28 @@ object AngConfigManager {
vnext.address = vmessQRCode.add vnext.address = vmessQRCode.add
vnext.port = Utils.parseInt(vmessQRCode.port) vnext.port = Utils.parseInt(vmessQRCode.port)
vnext.users[0].id = vmessQRCode.id vnext.users[0].id = vmessQRCode.id
vnext.users[0].security = if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy vnext.users[0].security =
if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid) vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
} }
val sni = streamSetting.populateTransportSettings(vmessQRCode.net, vmessQRCode.type, vmessQRCode.host, val sni = streamSetting.populateTransportSettings(
vmessQRCode.path, vmessQRCode.path, vmessQRCode.host, vmessQRCode.path, vmessQRCode.type, vmessQRCode.path) vmessQRCode.net,
vmessQRCode.type,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.path,
vmessQRCode.host,
vmessQRCode.path,
vmessQRCode.type,
vmessQRCode.path
)
val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint val fingerprint = vmessQRCode.fp ?: streamSetting.tlsSettings?.fingerprint
streamSetting.populateTlsSettings(vmessQRCode.tls, allowInsecure, streamSetting.populateTlsSettings(
if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni, vmessQRCode.tls, allowInsecure,
fingerprint, vmessQRCode.alpn, null, null, null) if (TextUtils.isEmpty(vmessQRCode.sni)) sni else vmessQRCode.sni,
fingerprint, vmessQRCode.alpn, null, null, null
)
} }
} }
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) { } else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
@@ -232,7 +284,8 @@ object AngConfigManager {
val indexSplit = result.indexOf("#") val indexSplit = result.indexOf("#")
if (indexSplit > 0) { if (indexSplit > 0) {
try { try {
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length)) config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -243,13 +296,17 @@ object AngConfigManager {
//part decode //part decode
val indexS = result.indexOf("@") val indexS = result.indexOf("@")
result = if (indexS > 0) { result = if (indexS > 0) {
Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length) Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else { } else {
Utils.decode(result) Utils.decode(result)
} }
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex() val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
val match = legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol val match = legacyPattern.matchEntire(result)
?: return R.string.toast_incorrect_protocol
config.outboundBean?.settings?.servers?.get(0)?.let { server -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]") server.address = match.groupValues[3].removeSurrounding("[", "]")
@@ -264,7 +321,8 @@ object AngConfigManager {
config = ServerConfig.create(EConfigType.SOCKS) config = ServerConfig.create(EConfigType.SOCKS)
if (indexSplit > 0) { if (indexSplit > 0) {
try { try {
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length)) config.remarks =
Utils.urlDecode(result.substring(indexSplit + 1, result.length))
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -275,18 +333,23 @@ object AngConfigManager {
//part decode //part decode
val indexS = result.indexOf("@") val indexS = result.indexOf("@")
if (indexS > 0) { if (indexS > 0) {
result = Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length) result = Utils.decode(result.substring(0, indexS)) + result.substring(
indexS,
result.length
)
} else { } else {
result = Utils.decode(result) result = Utils.decode(result)
} }
val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex() val legacyPattern = "^(.*):(.*)@(.+?):(\\d+?)$".toRegex()
val match = legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol val match =
legacyPattern.matchEntire(result) ?: return R.string.toast_incorrect_protocol
config.outboundBean?.settings?.servers?.get(0)?.let { server -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = match.groupValues[3].removeSurrounding("[", "]") server.address = match.groupValues[3].removeSurrounding("[", "]")
server.port = match.groupValues[4].toInt() server.port = match.groupValues[4].toInt()
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean() val socksUsersBean =
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = match.groupValues[1].lowercase() socksUsersBean.user = match.groupValues[1].lowercase()
socksUsersBean.pass = match.groupValues[2] socksUsersBean.pass = match.groupValues[2]
server.users = listOf(socksUsersBean) server.users = listOf(socksUsersBean)
@@ -302,17 +365,29 @@ object AngConfigManager {
val queryParam = uri.rawQuery.split("&") val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } } .associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"], val sni = config.outboundBean?.streamSettings?.populateTransportSettings(
queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"], queryParam["type"] ?: "tcp",
queryParam["mode"], queryParam["serviceName"]) queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"]
)
fingerprint = queryParam["fp"] ?: "" fingerprint = queryParam["fp"] ?: ""
config.outboundBean?.streamSettings?.populateTlsSettings(queryParam["security"] ?: TLS, config.outboundBean?.streamSettings?.populateTlsSettings(
allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"], queryParam["security"] ?: TLS,
null, null, null) allowInsecure, queryParam["sni"] ?: sni!!, fingerprint, queryParam["alpn"],
null, null, null
)
flow = queryParam["flow"] ?: "" flow = queryParam["flow"] ?: ""
} else { } else {
config.outboundBean?.streamSettings?.populateTlsSettings(TLS, allowInsecure, "", config.outboundBean?.streamSettings?.populateTlsSettings(
fingerprint, null, null, null, null) TLS, allowInsecure, "",
fingerprint, null, null, null, null
)
} }
config.outboundBean?.settings?.servers?.get(0)?.let { server -> config.outboundBean?.settings?.servers?.get(0)?.let { server ->
@@ -335,27 +410,41 @@ object AngConfigManager {
vnext.port = uri.port vnext.port = uri.port
vnext.users[0].id = uri.userInfo vnext.users[0].id = uri.userInfo
vnext.users[0].encryption = queryParam["encryption"] ?: "none" vnext.users[0].encryption = queryParam["encryption"] ?: "none"
vnext.users[0].flow =queryParam["flow"] ?: "" vnext.users[0].flow = queryParam["flow"] ?: ""
} }
val sni = streamSetting.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"], val sni = streamSetting.populateTransportSettings(
queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"], queryParam["type"] ?: "tcp",
queryParam["mode"], queryParam["serviceName"]) queryParam["headerType"],
queryParam["host"],
queryParam["path"],
queryParam["seed"],
queryParam["quicSecurity"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"]
)
fingerprint = queryParam["fp"] ?: "" fingerprint = queryParam["fp"] ?: ""
val pbk = queryParam["pbk"] ?: "" val pbk = queryParam["pbk"] ?: ""
val sid = queryParam["sid"] ?: "" val sid = queryParam["sid"] ?: ""
val spx = Utils.urlDecode(queryParam["spx"] ?: "") val spx = Utils.urlDecode(queryParam["spx"] ?: "")
streamSetting.populateTlsSettings(queryParam["security"] ?: "", allowInsecure, streamSetting.populateTlsSettings(
queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"], pbk, sid, spx) queryParam["security"] ?: "", allowInsecure,
queryParam["sni"] ?: sni, fingerprint, queryParam["alpn"], pbk, sid, spx
)
} }
if (config == null){ if (config == null) {
return R.string.toast_incorrect_protocol return R.string.toast_incorrect_protocol
} }
config.subscriptionId = subid config.subscriptionId = subid
val guid = MmkvManager.encodeServerConfig("", config) val guid = MmkvManager.encodeServerConfig("", config)
if (removedSelectedServer != null && if (removedSelectedServer != null &&
config.getProxyOutbound()?.getServerAddress() == removedSelectedServer.getProxyOutbound()?.getServerAddress() && config.getProxyOutbound()
config.getProxyOutbound()?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()) { ?.getServerAddress() == removedSelectedServer.getProxyOutbound()
?.getServerAddress() &&
config.getProxyOutbound()
?.getServerPort() == removedSelectedServer.getProxyOutbound()?.getServerPort()
) {
mainStorage?.encode(KEY_SELECTED_SERVER, guid) mainStorage?.encode(KEY_SELECTED_SERVER, guid)
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -365,14 +454,18 @@ object AngConfigManager {
return 0 return 0
} }
private fun tryParseNewVmess(uriString: String, config: ServerConfig, allowInsecure: Boolean): Boolean { private fun tryParseNewVmess(
uriString: String,
config: ServerConfig,
allowInsecure: Boolean
): Boolean {
return runCatching { return runCatching {
val uri = URI(uriString) val uri = URI(uriString)
check(uri.scheme == "vmess") check(uri.scheme == "vmess")
val (_, protocol, tlsStr, uuid, alterId) = val (_, protocol, tlsStr, uuid, alterId) =
Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})") Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
.matchEntire(uri.userInfo)?.groupValues .matchEntire(uri.userInfo)?.groupValues
?: error("parse user info fail.") ?: error("parse user info fail.")
val tls = tlsStr.isNotBlank() val tls = tlsStr.isNotBlank()
val queryParam = uri.rawQuery.split("&") val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } } .associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
@@ -387,12 +480,19 @@ object AngConfigManager {
vnext.users[0].alterId = alterId.toInt() vnext.users[0].alterId = alterId.toInt()
} }
var fingerprint = streamSetting.tlsSettings?.fingerprint var fingerprint = streamSetting.tlsSettings?.fingerprint
val sni = streamSetting.populateTransportSettings(protocol, queryParam["type"], val sni = streamSetting.populateTransportSettings(protocol,
queryParam["host"]?.split("|")?.get(0) ?: "", queryParam["type"],
queryParam["path"]?.takeIf { it.trim() != "/" } ?: "", queryParam["seed"], queryParam["security"], queryParam["host"]?.split("|")?.get(0) ?: "",
queryParam["key"], queryParam["mode"], queryParam["serviceName"]) queryParam["path"]?.takeIf { it.trim() != "/" } ?: "",
streamSetting.populateTlsSettings(if (tls) TLS else "", allowInsecure, sni, fingerprint, null, queryParam["seed"],
null, null, null) queryParam["security"],
queryParam["key"],
queryParam["mode"],
queryParam["serviceName"])
streamSetting.populateTlsSettings(
if (tls) TLS else "", allowInsecure, sni, fingerprint, null,
null, null, null
)
true true
}.getOrElse { false } }.getOrElse { false }
} }
@@ -480,12 +580,16 @@ object AngConfigManager {
vmessQRCode.add = outbound.getServerAddress().orEmpty() vmessQRCode.add = outbound.getServerAddress().orEmpty()
vmessQRCode.port = outbound.getServerPort().toString() vmessQRCode.port = outbound.getServerPort().toString()
vmessQRCode.id = outbound.getPassword().orEmpty() vmessQRCode.id = outbound.getPassword().orEmpty()
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString() vmessQRCode.aid =
vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString() outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
vmessQRCode.scy =
outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
vmessQRCode.net = streamSetting.network vmessQRCode.net = streamSetting.network
vmessQRCode.tls = streamSetting.security vmessQRCode.tls = streamSetting.security
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty() vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
vmessQRCode.alpn = Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString()).orEmpty() vmessQRCode.alpn =
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString())
.orEmpty()
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty() vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
outbound.getTransportSettingDetails()?.let { transportDetails -> outbound.getTransportSettingDetails()?.let { transportDetails ->
vmessQRCode.type = transportDetails[0] vmessQRCode.type = transportDetails[0]
@@ -495,25 +599,34 @@ object AngConfigManager {
val json = Gson().toJson(vmessQRCode) val json = Gson().toJson(vmessQRCode)
Utils.encode(json) Utils.encode(json)
} }
EConfigType.CUSTOM, EConfigType.WIREGUARD -> "" EConfigType.CUSTOM, EConfigType.WIREGUARD -> ""
EConfigType.SHADOWSOCKS -> { EConfigType.SHADOWSOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks) val remark = "#" + Utils.urlEncode(config.remarks)
val pw = Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}") val pw =
val url = String.format("%s@%s:%s", Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
pw, val url = String.format(
Utils.getIpv6Address(outbound.getServerAddress()!!), "%s@%s:%s",
outbound.getServerPort())
url + remark
}
EConfigType.SOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks)
val pw = Utils.encode("${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}")
val url = String.format("%s@%s:%s",
pw, pw,
Utils.getIpv6Address(outbound.getServerAddress()!!), Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()) outbound.getServerPort()
)
url + remark url + remark
} }
EConfigType.SOCKS -> {
val remark = "#" + Utils.urlEncode(config.remarks)
val pw =
Utils.encode("${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}")
val url = String.format(
"%s@%s:%s",
pw,
Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
url + remark
}
EConfigType.VLESS, EConfigType.VLESS,
EConfigType.TROJAN -> { EConfigType.TROJAN -> {
val remark = "#" + Utils.urlEncode(config.remarks) val remark = "#" + Utils.urlEncode(config.remarks)
@@ -537,12 +650,14 @@ object AngConfigManager {
} }
dicQuery["security"] = streamSetting.security.ifEmpty { "none" } dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings?: streamSetting.realitySettings)?.let { tlsSetting -> (streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) { if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName dicQuery["sni"] = tlsSetting.serverName
} }
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) { if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] = Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty() dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
} }
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) { if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint!! dicQuery["fp"] = tlsSetting.fingerprint!!
@@ -567,12 +682,14 @@ object AngConfigManager {
dicQuery["host"] = Utils.urlEncode(transportDetails[1]) dicQuery["host"] = Utils.urlEncode(transportDetails[1])
} }
} }
"kcp" -> { "kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" } dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) { if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2]) dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
} }
} }
"ws" -> { "ws" -> {
if (!TextUtils.isEmpty(transportDetails[1])) { if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1]) dicQuery["host"] = Utils.urlEncode(transportDetails[1])
@@ -581,6 +698,7 @@ object AngConfigManager {
dicQuery["path"] = Utils.urlEncode(transportDetails[2]) dicQuery["path"] = Utils.urlEncode(transportDetails[2])
} }
} }
"http", "h2" -> { "http", "h2" -> {
dicQuery["type"] = "http" dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) { if (!TextUtils.isEmpty(transportDetails[1])) {
@@ -590,11 +708,13 @@ object AngConfigManager {
dicQuery["path"] = Utils.urlEncode(transportDetails[2]) dicQuery["path"] = Utils.urlEncode(transportDetails[2])
} }
} }
"quic" -> { "quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" } dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1]) dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2]) dicQuery["key"] = Utils.urlEncode(transportDetails[2])
} }
"grpc" -> { "grpc" -> {
dicQuery["mode"] = transportDetails[0] dicQuery["mode"] = transportDetails[0]
dicQuery["serviceName"] = transportDetails[2] dicQuery["serviceName"] = transportDetails[2]
@@ -602,13 +722,15 @@ object AngConfigManager {
} }
} }
val query = "?" + dicQuery.toList().joinToString( val query = "?" + dicQuery.toList().joinToString(
separator = "&", separator = "&",
transform = { it.first + "=" + it.second }) transform = { it.first + "=" + it.second })
val url = String.format("%s@%s:%s", val url = String.format(
outbound.getPassword(), "%s@%s:%s",
Utils.getIpv6Address(outbound.getServerAddress()!!), outbound.getPassword(),
outbound.getServerPort()) Utils.getIpv6Address(outbound.getServerAddress()!!),
outbound.getServerPort()
)
url + query + remark url + query + remark
} }
} }
@@ -670,7 +792,7 @@ object AngConfigManager {
if (TextUtils.isEmpty(conf)) { if (TextUtils.isEmpty(conf)) {
return null return null
} }
return Utils.createQRCode(conf) return QRCodeDecoder.createQRCode(conf)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -756,16 +878,34 @@ object AngConfigManager {
var count = 0 var count = 0
servers.lines() servers.lines()
.forEach { .reversed()
val resId = importConfig(it, subid, removedSelectedServer) .forEach {
if (resId == 0) { val resId = importConfig(it, subid, removedSelectedServer)
count++ if (resId == 0) {
} count++
} }
}
return count return count
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
return 0 return 0
} }
fun importSubscription(remark: String, url: String, enabled: Boolean = true): Boolean {
val subId = Utils.getUuid()
val subItem = SubscriptionItem()
subItem.remarks = remark
subItem.url = url
subItem.enabled = enabled
if (TextUtils.isEmpty(subItem.remarks) || TextUtils.isEmpty(subItem.url)) {
return false
}
subStorage?.encode(subId, Gson().toJson(subItem))
return true
}
} }
@@ -46,7 +46,7 @@ object MmkvManager {
serverStorage?.encode(key, Gson().toJson(config)) serverStorage?.encode(key, Gson().toJson(config))
val serverList = decodeServerList() val serverList = decodeServerList()
if (!serverList.contains(key)) { if (!serverList.contains(key)) {
serverList.add(key) serverList.add(0, key)
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList)) mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) { if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) {
mainStorage?.encode(KEY_SELECTED_SERVER, key) mainStorage?.encode(KEY_SELECTED_SERVER, key)
@@ -5,6 +5,7 @@ import android.graphics.BitmapFactory
import com.google.zxing.* import com.google.zxing.*
import com.google.zxing.common.GlobalHistogramBinarizer import com.google.zxing.common.GlobalHistogramBinarizer
import com.google.zxing.common.HybridBinarizer import com.google.zxing.common.HybridBinarizer
import com.google.zxing.qrcode.QRCodeWriter
import java.util.* import java.util.*
/** /**
@@ -13,6 +14,36 @@ import java.util.*
object QRCodeDecoder { object QRCodeDecoder {
val HINTS: MutableMap<DecodeHintType, Any?> = EnumMap(DecodeHintType::class.java) val HINTS: MutableMap<DecodeHintType, Any?> = EnumMap(DecodeHintType::class.java)
/**
* create qrcode using zxing
*/
fun createQRCode(text: String, size: Int = 800): Bitmap? {
try {
val hints = HashMap<EncodeHintType, String>()
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
val bitMatrix = QRCodeWriter().encode(text,
BarcodeFormat.QR_CODE, size, size, hints)
val pixels = IntArray(size * size)
for (y in 0 until size) {
for (x in 0 until size) {
if (bitMatrix.get(x, y)) {
pixels[y * size + x] = 0xff000000.toInt()
} else {
pixels[y * size + x] = 0xffffffff.toInt()
}
}
}
val bitmap = Bitmap.createBitmap(size, size,
Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
return bitmap
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
/** /**
* 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。 * 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。
* *
@@ -4,17 +4,12 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.text.Editable import android.text.Editable
import android.util.Base64 import android.util.Base64
import com.google.zxing.WriterException
import android.graphics.Bitmap
import com.google.zxing.BarcodeFormat
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.EncodeHintType
import java.util.* import java.util.*
import kotlin.collections.HashMap
import android.content.ClipData import android.content.ClipData
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration.UI_MODE_NIGHT_MASK import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.LocaleList import android.os.LocaleList
@@ -171,36 +166,6 @@ object Utils {
return ret return ret
} }
/**
* create qrcode using zxing
*/
fun createQRCode(text: String, size: Int = 800): Bitmap? {
try {
val hints = HashMap<EncodeHintType, String>()
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
val bitMatrix = QRCodeWriter().encode(text,
BarcodeFormat.QR_CODE, size, size, hints)
val pixels = IntArray(size * size)
for (y in 0 until size) {
for (x in 0 until size) {
if (bitMatrix.get(x, y)) {
pixels[y * size + x] = 0xff000000.toInt()
} else {
pixels[y * size + x] = 0xffffffff.toInt()
}
}
}
val bitmap = Bitmap.createBitmap(size, size,
Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
return bitmap
} catch (e: WriterException) {
e.printStackTrace()
return null
}
}
/** /**
* is ip address * is ip address
*/ */
@@ -213,7 +178,7 @@ object Utils {
//CIDR //CIDR
if (addr.indexOf("/") > 0) { if (addr.indexOf("/") > 0) {
val arr = addr.split("/") val arr = addr.split("/")
if (arr.count() == 2 && Integer.parseInt(arr[1]) > 0) { if (arr.count() == 2 && Integer.parseInt(arr[1]) > -1) {
addr = arr[0] addr = arr[0]
} }
} }
@@ -274,7 +239,7 @@ object Utils {
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) { if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
return true return true
} }
} catch (e: WriterException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return false return false
} }
@@ -391,7 +356,7 @@ object Utils {
fun getDarkModeStatus(context: Context): Boolean { fun getDarkModeStatus(context: Context): Boolean {
val mode = context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK val mode = context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK
return mode == UI_MODE_NIGHT_YES return mode != UI_MODE_NIGHT_NO
} }
fun getIpv6Address(address: String): String { fun getIpv6Address(address: String): String {
@@ -435,5 +400,9 @@ object Utils {
return URL(url.protocol, IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED), url.port, url.file) return URL(url.protocol, IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED), url.port, url.file)
.toExternalForm() .toExternalForm()
} }
fun isTv(context: Context): Boolean =
context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
} }
@@ -74,8 +74,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java) config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
val key = MmkvManager.encodeServerConfig("", config) val key = MmkvManager.encodeServerConfig("", config)
serverRawStorage?.encode(key, server) serverRawStorage?.encode(key, server)
serverList.add(key) serverList.add(0, key)
serversCache.add(ServersCache(key,config)) serversCache.add(0, ServersCache(key,config))
} }
fun swapServer(fromPosition: Int, toPosition: Int) { fun swapServer(fromPosition: Int, toPosition: Int) {
@@ -50,7 +50,8 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_PREFER_IPV6, AppConfig.PREF_PREFER_IPV6,
AppConfig.PREF_PER_APP_PROXY, AppConfig.PREF_PER_APP_PROXY,
AppConfig.PREF_BYPASS_APPS, AppConfig.PREF_BYPASS_APPS,
AppConfig.PREF_CONFIRM_REMOVE, -> { AppConfig.PREF_CONFIRM_REMOVE,
AppConfig.PREF_START_SCAN_IMMEDIATE, -> {
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false)) settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
} }
AppConfig.PREF_SNIFFING_ENABLED -> { AppConfig.PREF_SNIFFING_ENABLED -> {
Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#fff"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FFFFFFFF" android:fillColor="#FF000000"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/> android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
</vector> </vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<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.96zM17,13l-5,5 -5,-5h3V9h4v4h3z" />
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>
@@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FFF" android:fillColor="#000"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" /> android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector> </vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,14h-2v-2h2v2zM13,10h-2L11,6h2v4z"/>
</vector>
@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11,23h8a3,3 0,0 0,3 -3V8L15,1H7A3,3 0,0 0,4 4V9"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M15,5a3,3 0,0 0,3 3h4L15,1Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M2,17L10,17"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M6,13L6,21"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
</vector>
@@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#fff" android:fillColor="#000"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/> android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector> </vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,6h10l-5.01,6.3L7,6zM4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
</vector>
@@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@color/colorBg"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
<path
android:fillColor="@color/colorText"
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
<path
android:fillColor="@color/colorText"
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" />
</vector>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/ic_shortcut_background"
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp" />
<item
android:drawable="@drawable/ic_stat_name_black"
android:left="12dp"
android:top="12dp"
android:right="12dp"
android:bottom="12dp" />
</layer-list>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#009963"/> <solid android:color="@color/color_fab_orange"/>
<corners android:radius="20dp"/> <corners android:radius="20dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" /> <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape> </shape>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/colorUnselected"/> <solid android:color="@color/color_switch_fab_grey"/>
<corners android:radius="20dp"/> <corners android:radius="20dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" /> <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape> </shape>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
</vector>
@@ -5,14 +5,14 @@
android:viewportHeight="48"> android:viewportHeight="48">
<path <path
android:fillColor="#999999" android:fillColor="#050505"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" /> android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
<path <path
android:fillColor="#ffffff" android:fillColor="#FFFFFF"
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2 android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" /> C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
<path <path
android:fillColor="#ffffff" android:fillColor="#FFFFFF"
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33 android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30 A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" /> A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" />
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp">
<path
android:fillColor="#000000"
android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z" />
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
</vector>
@@ -2,6 +2,6 @@
<shape <shape
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"> android:shape="oval">
<solid android:color="@color/colorUnselected" /> <solid android:color="@color/colorBg" />
<size android:width="44dp" android:height="44dp" /> <size android:width="44dp" android:height="44dp" />
</shape> </shape>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.
@@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="10dp" android:paddingStart="10dp"
android:paddingEnd="10dp"
tools:context=".ui.MainActivity"> tools:context=".ui.MainActivity">
<ProgressBar <ProgressBar
@@ -13,8 +13,7 @@
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
@@ -50,7 +49,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/connection_test_height" android:layout_height="@dimen/connection_test_height"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:gravity="center|left" android:gravity="center|start"
android:nextFocusRight="@+id/fab" android:nextFocusRight="@+id/fab"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
@@ -65,7 +64,7 @@
android:paddingStart="16dp" android:paddingStart="16dp"
android:text="@string/connection_test_pending" android:text="@string/connection_test_pending"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorWhite" /> android:textColor="@color/colorText" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -106,6 +105,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start"
app:headerLayout="@layout/nav_header" app:headerLayout="@layout/nav_header"
app:itemIconTint="@color/colorAccent"
app:menu="@menu/menu_drawer" > app:menu="@menu/menu_drawer" >
<LinearLayout <LinearLayout
@@ -21,7 +21,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/server_lab_need_inbound" android:text="@string/server_lab_need_inbound"
android:textAppearance="@style/TextAppearance.AppCompat.Tooltip" android:textAppearance="@style/TextAppearance.AppCompat.Tooltip"
android:textColor="@color/fab_orange_dark" /> android:textColor="@color/color_secondary" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -16,7 +16,8 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
tools:listitem="@layout/item_recycler_sub_setting"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout> </RelativeLayout>
@@ -19,7 +19,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/routing_settings_tips" android:text="@string/routing_settings_tips"
android:textColor="@color/fab_orange_dark" /> android:textColor="@color/color_secondary" />
<EditText <EditText
android:id="@+id/et_routing_content" android:id="@+id/et_routing_content"
@@ -3,7 +3,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:background="?android:attr/selectableItemBackground" android:background="@color/colorPrimary"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
@@ -26,7 +26,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/summary_pref_promotion" android:text="@string/summary_pref_promotion"
android:textAppearance="@style/TextAppearance.AppCompat.Tooltip" android:textAppearance="@style/TextAppearance.AppCompat.Tooltip"
android:textColor="@color/fab_orange_dark" /> android:textColor="@color/color_secondary" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -19,7 +19,8 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:nextFocusRight="@+id/layout_share" android:nextFocusRight="@+id/layout_share"
android:background="?android:attr/selectableItemBackground" android:background="@color/colorPrimary"
android:foreground="?android:attr/selectableItemBackground"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/server_height" android:layout_height="@dimen/server_height"
android:layout_gravity="center" android:layout_gravity="center"
@@ -28,7 +29,7 @@
<LinearLayout <LinearLayout
android:id="@+id/layout_indicator" android:id="@+id/layout_indicator"
android:layout_width="10dp" android:layout_width="6dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:orientation="vertical" /> android:orientation="vertical" />
@@ -38,7 +39,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="5dp"> android:paddingStart="9dp">
<TextView <TextView
android:id="@+id/tv_name" android:id="@+id/tv_name"
@@ -84,7 +85,7 @@
android:layout_weight="1" android:layout_weight="1"
android:lines="1" android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorSubscription" android:textColor="@color/color_secondary"
android:textSize="10sp" android:textSize="10sp"
tools:text="Sub" /> tools:text="Sub" />
@@ -121,7 +122,7 @@
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="@dimen/png_height" android:layout_height="@dimen/png_height"
android:src="@drawable/ic_share_black_24dp" android:src="@drawable/ic_share_24dp"
app:tint="?attr/colorMainText" /> app:tint="?attr/colorMainText" />
</LinearLayout> </LinearLayout>
@@ -140,7 +141,7 @@
<ImageView <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
android:layout_height="@dimen/png_height" android:layout_height="@dimen/png_height"
android:src="@drawable/ic_edit_black_24dp" android:src="@drawable/ic_edit_24dp"
app:tint="?attr/colorMainText" /> app:tint="?attr/colorMainText" />
</LinearLayout> </LinearLayout>
@@ -159,7 +160,7 @@
<ImageView <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
android:layout_height="@dimen/png_height" android:layout_height="@dimen/png_height"
android:src="@drawable/ic_delete_black_24dp" android:src="@drawable/ic_delete_24dp"
app:tint="?attr/colorMainText" /> app:tint="?attr/colorMainText" />
</LinearLayout> </LinearLayout>
@@ -178,7 +179,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:lines="1" android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/fab_orange_dark" android:textColor="@color/color_fab_orange"
android:textSize="10sp" /> android:textSize="10sp" />
</LinearLayout> </LinearLayout>
@@ -18,7 +18,8 @@
android:id="@+id/info_container" android:id="@+id/info_container"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:background="?android:attr/selectableItemBackground" android:background="@color/colorPrimary"
android:foreground="?android:attr/selectableItemBackground"
android:nextFocusRight="@+id/layout_edit" android:nextFocusRight="@+id/layout_edit"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/server_height" android:layout_height="@dimen/server_height"
@@ -28,7 +29,7 @@
<LinearLayout <LinearLayout
android:id="@+id/chk_enable" android:id="@+id/chk_enable"
android:layout_width="10dp" android:layout_width="6dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:orientation="vertical" /> android:orientation="vertical" />
@@ -37,7 +38,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:paddingStart="5dp" android:paddingStart="9dp"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
@@ -73,12 +74,13 @@
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/layout_margin_spacing" android:padding="@dimen/layout_margin_spacing"
android:visibility="invisible"> >
<ImageView <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
android:layout_height="@dimen/png_height" android:layout_height="@dimen/png_height"
android:src="@drawable/ic_share_black_24dp" /> android:src="@drawable/ic_share_24dp"
app:tint="?attr/colorMainText"/>
</LinearLayout> </LinearLayout>
@@ -97,7 +99,7 @@
<ImageView <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
android:layout_height="@dimen/png_height" android:layout_height="@dimen/png_height"
android:src="@drawable/ic_edit_black_24dp" android:src="@drawable/ic_edit_24dp"
app:tint="?attr/colorMainText" /> app:tint="?attr/colorMainText" />
</LinearLayout> </LinearLayout>
@@ -16,6 +16,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center" android:gravity="center"
android:background="@color/colorPrimary"
android:foreground="?android:attr/selectableItemBackground"
android:padding="@dimen/nav_header_vertical_spacing"> android:padding="@dimen/nav_header_vertical_spacing">
<LinearLayout <LinearLayout
@@ -52,7 +54,7 @@
<ImageView <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
android:layout_height="@dimen/png_height" android:layout_height="@dimen/png_height"
android:src="@drawable/ic_delete_black_24dp" android:src="@drawable/ic_delete_24dp"
app:tint="?attr/colorMainText" /> app:tint="?attr/colorMainText" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -14,12 +14,13 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center" android:layout_gravity="center"
android:fontFamily="@font/montserrat_thin"
android:gravity="center"
android:paddingTop="@dimen/nav_header_vertical_spacing" android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="@string/app_name" android:text="@string/app_name"
android:textColor="@color/white" android:textAppearance="@style/TextAppearance.AppCompat.Display1"
android:textAppearance="@style/TextAppearance.AppCompat.Display1" /> android:textColor="@color/colorText" />
</LinearLayout> </LinearLayout>
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|enterAlways" />
</com.google.android.material.appbar.AppBarLayout>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.navigation.NavigationView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/menu_drawer" />
@@ -8,6 +8,7 @@
android:orientation="vertical"> android:orientation="vertical">
<ImageView <ImageView
android:id="@+id/image_switch"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:padding="10dp" android:padding="10dp"
@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/del_config" android:id="@+id/del_config"
android:icon="@drawable/ic_delete_white_24dp" android:icon="@drawable/ic_delete_24dp"
android:title="@string/menu_item_del_config" android:title="@string/menu_item_del_config"
app:showAsAction="always" /> app:showAsAction="always" />
<item <item

Some files were not shown because too many files have changed in this diff Show More