Compare commits

..

44 Commits
1.9.8 ... 1.9.9

Author SHA1 Message Date
2dust
e60eb703c6 up 1.9.9 2024-10-28 13:54:49 +08:00
2dust
cdede639f2 Adjustment of preset rule sets 2024-10-28 13:45:00 +08:00
2dust
23264d71f0 Test connection after updating subscription 2024-10-28 12:05:17 +08:00
2dust
9caafc4303 Add toast insecure protocol 2024-10-27 20:44:57 +08:00
Tamim Hossain
48a3690a39 refactor steam visiblity (#3786)
refactor steam visiblity
2024-10-25 20:07:43 +08:00
Tamim Hossain
b66a8ca44d Refactor QRCodeDecoder for readability and performance (#3782)
- Improved the `createQRCode` function by replacing manual loops with Kotlin idioms and using `runCatching` for safer error handling.
- Refactored `syncDecodeQRCode` to simplify control flow and avoid redundant null checks.
- Enhanced error handling and logging by using `runCatching` and more concise exception handling.
2024-10-25 19:19:22 +08:00
Tamim Hossain
82087e1187 Refactor UserAssetActivity for readability and efficiency (#3781)
- Refactored the onOptionsItemSelected function to use `when` and `let` for cleaner conditional logic.
- Simplified the `chooseFile` function with `runCatching` for better error handling.
- Extracted repeated code into reusable functions to improve maintainability.
- Improved coroutine handling by ensuring file I/O happens on the IO thread.
2024-10-25 19:17:35 +08:00
Tamim Hossain
fdfd0438c3 Refactor updateMuxConcurrency for Null Safety and Code Simplification (#3780)
Refactored the updateMuxConcurrency function by removing redundant null checks and improving code readability. The concurrency value now defaults to 8 if null or invalid input is provided.
2024-10-25 19:14:28 +08:00
Tamim Hossain
28027b5288 Fix Comment and Improve Null Safety in ServerCustomConfigActivity (#3779)
Fixed the comment typo in ServerCustomConfigActivity and improved null safety in the bindingServer function by ensuring proper fallback for raw content. Simplified logic for setting editor content.
2024-10-25 19:13:34 +08:00
Tamim Hossain
ba54005753 Improve Permission Handling and QR Code Import Logic in ScScannerActivity (#3777)
Refactored permission request handling in ScScannerActivity to improve readability and simplify the flow. Improved QR code import logic to ensure better handling of the scanned result and enhance user feedback.
2024-10-25 19:10:14 +08:00
Tamim Hossain
c6758b11b5 Improve Permission Request and QR Code Decoding in ScannerActivity (#3776)
Streamlined the permission request process using RxPermissions and improved error handling for file selection and QR code decoding in ScannerActivity. Enhanced user feedback for decoding failures and file processing errors.
2024-10-25 19:09:41 +08:00
Tamim Hossain
6c29e5e9a4 Optimize refreshData in RoutingSettingActivity (#3775)
Optimized the refreshData method by clearing the existing rulesets list and adding the new items to avoid unnecessary reallocation and improve memory management.
2024-10-25 19:07:55 +08:00
Tamim Hossain
17e0db2ffc Refactor Import Rulesets from Clipboard (#3774)
Refactored the import_rulesets_from_clipboard method in RoutingSettingActivity. Improved error handling by isolating the clipboard fetching inside a try-catch block and ensuring the routing ruleset reset logic is handled more cleanly.
2024-10-25 19:06:44 +08:00
Tamim Hossain
490ea59499 Refactor saveServer Method to Improve Null-Safety and Readability (#3773)
Refactored the saveServer method in RoutingEditActivity to improve null-safety and readability by using apply and takeIf. This ensures cleaner and more concise code while processing user inputs like domain, ip, protocol, and network.
2024-10-25 19:02:52 +08:00
Tamim Hossain
f4db6bcf63 Refactor Binding Logic in AppViewHolder of PerAppProxyAdapter (#3772)
Simplified the binding logic in AppViewHolder by improving readability and removing redundant code. Consolidated logic for setting the app name and handling system apps. This refactor improves the maintainability of the PerAppProxyAdapter class.
2024-10-25 19:01:56 +08:00
Tamim Hossain
90f89de957 Improve Error Handling in Batch Config Import and Progress Bar Management (#3771)
Enhanced error handling in the importBatchConfig method by adding a try-catch block. Ensured that the progress bar is hidden in both success and failure cases. Replaced nested if statements with a cleaner when clause for better readability.
2024-10-25 18:58:20 +08:00
Tamim Hossain
9612b868f2 Improve Error Handling in LogcatActivity (#3770)
Refined the error handling in the logcat function of LogcatActivity by ensuring that the progress bar is hidden in both success and failure cases. Added user-friendly error messages using toast and cleaned up the code by using Kotlin's linkedSetOf for the logcat command list.
2024-10-25 18:56:59 +08:00
Tamim Hossain
5f8ea93f36 Refactor attachBaseContext for Null Safety and Clean Code (#3769)
Simplified the attachBaseContext function by removing redundant let block and ensuring null safety with concise null handling. This refactor improves readability and ensures that null cases are handled directly.
2024-10-25 18:55:56 +08:00
Tamim Hossain
fc132f7282 Improve Exception Handling in File Chooser and Restore Process (#3768)
Enhanced exception handling in the file chooser and restore configuration process by adding detailed logging with Log.e(). Simplified the intent creation in showFileChooser and combined nested try-catch blocks for better readability and error management.
2024-10-25 18:36:17 +08:00
Tamim Hossain
6f9bb6caa7 Improve RxJava Disposable Handling in V2RayServiceManager (#3767)
Refactored the RxJava disposable handling in the V2RayServiceManager to ensure proper disposal when the service stops. This change prevents potential memory leaks by disposing of the disposable only when it's initialized, using a more concise and reliable approach with Kotlin's let function.
2024-10-25 18:35:03 +08:00
Tamim Hossain
0e5b88de8f remove parentheses (#3766)
remove parentheses
2024-10-25 18:34:19 +08:00
any116
5b4f51981e Fallback go ver for normal use (#3758)
* Update build.yml

* Delete .github/dependabot.yml
2024-10-24 09:14:38 +08:00
DecorativeFamily
3dcee45e9f Update strings.xml (#3741)
* Update strings.xml

update persian strings

* Update strings.xml

update persian strings

* Update strings.xml

update persian strings

* Update strings.xml

update persian strings
2024-10-23 09:14:27 +08:00
Tamim Hossain
3573a3bec3 Remove unnecessary null check from subscriptionId (#3739)
- Replaced `subscriptionId.isNullOrEmpty()` with `subscriptionId.isEmpty()` since `subscriptionId` is not nullable.
- This refactor simplifies the logic and improves code readability by eliminating the redundant null check.
2024-10-22 20:22:58 +08:00
Tamim Hossain
796bad1c1c Remove redundant qualifier from RulesBean initialization (#3738)
- Removed the redundant qualifier `V2rayConfig.RoutingBean.RulesBean` in favor of directly using `RulesBean`.
- This simplifies the code and improves readability by removing unnecessary fully qualified names.
- The change ensures cleaner and more maintainable code without altering functionality.
2024-10-22 20:22:46 +08:00
Tamim Hossain
77042f6fae Remove unnecessary null check from keywordFilter (#3737)
* Remove unnecessary null check from keywordFilter

- Replaced `keywordFilter.isNullOrEmpty()` with `keywordFilter.isEmpty()` since `keywordFilter` is guaranteed to never be null.
- Simplified the logic by removing the redundant null check, improving code readability.

* Remove unnecessary null check from keywordFilter

- Replaced `keywordFilter.isNullOrEmpty()` with `keywordFilter.isEmpty()` since `keywordFilter` is guaranteed to never be null.
- Simplified the logic by removing the redundant null check, improving code readability.
2024-10-22 20:22:23 +08:00
Tamim Hossain
013ac308f7 Refactor allowInsecure variable declaration (#3736)
- Changed `allowInsecure` from `var` to `val` to ensure immutability.
- Replaced the nullable safe call `settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false` with `settingsStorage.decodeBool(AppConfig.PREF_ALLOW_INSECURE, false)` for more concise and readable code.
- This ensures `allowInsecure` is always initialized with a default value of `false` in a cleaner and more efficient way.
2024-10-22 20:21:57 +08:00
any116
d703582f19 refine upload-artifact and fix version (#3735)
* refine upload-artifact and fix api version

* refine upload-artifact and fix api version

* refine upload-artifact and fix version

* use Dependabot keep actions updated to latest

like: v2-->v3
2024-10-22 20:21:16 +08:00
any116
1db80f740d Update SettingsManager.kt (#3732) 2024-10-22 09:18:13 +08:00
DecorativeFamily
a0d2740280 Update build.yml (#3731) 2024-10-22 09:17:59 +08:00
DecorativeFamily
297083f3c4 Update build.yml (#3730) 2024-10-22 09:17:39 +08:00
DecorativeFamily
15a4ad978a Update libs.versions.toml (#3728)
Co-authored-by: DECORATIVEFAMILYNG <185765765+DECORATIVEFAMILYNG@users.noreply.github.com>
2024-10-22 09:17:15 +08:00
DECORATIVEFAMILYNG
63f4cfac83 Update strings.xml (#3726)
update persian strings
2024-10-21 17:39:52 +08:00
Tamim Hossain
ca849fb19e Organize routing files names (#3717)
* Organize routing files names

Organize routing files names

* Use enum for routing type

Use enum for routing type
2024-10-21 17:39:28 +08:00
Tamim Hossain
3de3070ab7 Organize locale (#3716)
* Organize locale

Organize locale

* use enum for locale

use enum for locale
2024-10-21 17:38:41 +08:00
Tamim Hossain
cbea4bab7c Update kotlin version to 2.0.21 (#3724)
Update kotlin version to 2.0.21
2024-10-21 16:32:19 +08:00
Tamim Hossain
fe8b825c34 Fix subs id check (#3714)
* Fix subs id check

Fix subs id check

* Update MainViewModel.kt
2024-10-21 14:43:48 +08:00
2dust
daa0394960 Fix
https://github.com/2dust/v2rayNG/issues/3720
2024-10-21 14:42:00 +08:00
2dust
e5aba5d99b Adding checks for subscription url 2024-10-21 14:25:05 +08:00
any116
c4847eb3de Update SimpleItemTouchHelperCallback.java (#3719) 2024-10-21 14:04:24 +08:00
Tamim Hossain
5a5f911453 Use entries instead of values as its recommended after kotlin 1.9 (#3718)
Use entries instead of values as its  recommended after kotlin 1.9
2024-10-21 14:01:44 +08:00
Tamim Hossain
22cef29c27 organize dns (#3715)
organize dns
2024-10-21 13:55:45 +08:00
Tamim Hossain
ef41641680 Update bangla translation (#3713)
Update bangla translation
2024-10-21 13:52:11 +08:00
Tamim Hossain
69135e8707 Update plugins in ``libs.versions.toml`` (#3712)
Update plugins in ```libs.versions.toml```
2024-10-21 13:51:59 +08:00
46 changed files with 569 additions and 397 deletions

View File

@@ -26,18 +26,16 @@ jobs:
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version: '1.22.2'
go-version: '1.22.4'
- name: Install gomobile
run: |
go install golang.org/x/mobile/cmd/gomobile@latest
go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240806205939-81131f6468ab
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
- name: Setup Android environment
uses: android-actions/setup-android@v3
- name: Build dependencies
run: |
mkdir ${{ github.workspace }}/build
@@ -56,8 +54,33 @@ jobs:
chmod 755 gradlew
./gradlew assembleDebug
- name: Upload APK
- name: Upload arm64-v8a APK
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
name: arm64-v8a
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*arm64-v8a*.apk
- name: Upload armeabi-v7a APK
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
name: armeabi-v7a
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*armeabi-v7a*.apk
- name: Upload x86 APK
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
name: x86-apk
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*x86*.apk
- name: Upload Other APKs
uses: actions/upload-artifact@v4
with:
name: apk
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/
name: others-apk
path: |
${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug
!${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*arm64-v8a*.apk
!${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*armeabi-v7a*.apk
!${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/*x86*.apk

View File

@@ -3,7 +3,7 @@
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
[![API](https://img.shields.io/badge/API-21%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/lollipop)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-1.9.23-blue.svg)](https://kotlinlang.org)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-2.0.21-blue.svg)](https://kotlinlang.org)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master)
[![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng)
[![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases)

View File

@@ -11,8 +11,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 34
versionCode = 602
versionName = "1.9.8"
versionCode = 603
versionName = "1.9.9"
multiDexEnabled = true
splits {
abi {

View File

@@ -42,15 +42,7 @@
]
},
{
"remarks": "代理GFW",
"outboundTag": "proxy",
"domain": [
"geosite:gfw",
"geosite:greatfire"
]
},
{
"remarks": "代理Google等",
"remarks": "代理IP",
"outboundTag": "proxy",
"ip": [
"1.0.0.1",
@@ -65,6 +57,14 @@
"geoip:twitter"
]
},
{
"remarks": "代理GFW",
"outboundTag": "proxy",
"domain": [
"geosite:gfw",
"geosite:greatfire"
]
},
{
"remarks": "最终直连",
"port": "0-65535",

View File

@@ -1,4 +1,12 @@
[
{
"remarks": "Google cn",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{
"remarks": "阻断udp443",
"outboundTag": "block",
@@ -30,6 +38,25 @@
"remarks": "绕过中国IP",
"outboundTag": "direct",
"ip": [
"223.5.5.5/32",
"223.6.6.6/32",
"2400:3200::1/128",
"2400:3200:baba::1/128",
"119.29.29.29/32",
"1.12.12.12/32",
"120.53.53.53/32",
"2402:4e00::/128",
"2402:4e00:1::/128",
"180.76.76.76/32",
"2400:da00::6666/128",
"114.114.114.114/32",
"114.114.115.115/32",
"180.184.1.1/32",
"180.184.2.2/32",
"101.226.4.6/32",
"218.30.118.6/32",
"123.125.81.6/32",
"140.207.198.6/32",
"geoip:cn"
]
},
@@ -37,21 +64,18 @@
"remarks": "绕过中国域名",
"outboundTag": "direct",
"domain": [
"domain:dns.alidns.com",
"domain:doh.pub",
"domain:dot.pub",
"domain:doh.360.cn",
"domain:dot.360.cn",
"geosite:cn",
"geosite:geolocation-cn"
]
},
{
"remarks": "Google CN",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{
"remarks": "最终代理",
"port": "0-65535",
"outboundTag": "proxy"
}
]
]

View File

@@ -16,14 +16,14 @@
package com.v2ray.ang.helper;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.view.animation.DecelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import org.jetbrains.annotations.NotNull;
/**
* An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and
* swipe-to-dismiss. Drag events are automatically started by an item long-press.<br/>
@@ -36,9 +36,12 @@ import org.jetbrains.annotations.NotNull;
*/
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
public static final float ALPHA_FULL = 1.0f;
private static final float ALPHA_FULL = 1.0f;
private static final float SWIPE_THRESHOLD = 0.25f;
private static final long ANIMATION_DURATION = 200;
private final ItemTouchHelperAdapter mAdapter;
private ValueAnimator mReturnAnimator;
public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
mAdapter = adapter;
@@ -51,15 +54,14 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
@Override
public boolean isItemViewSwipeEnabled() {
return false;
return true;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, @NotNull RecyclerView.ViewHolder viewHolder) {
// Set movement flags based on the layout manager
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
final int swipeFlags = 0;
final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
} else {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
@@ -69,61 +71,89 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
}
@Override
public boolean onMove(@NotNull RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder source, @NonNull RecyclerView.ViewHolder target) {
if (source.getItemViewType() != target.getItemViewType()) {
return false;
}
// Notify the adapter of the move
mAdapter.onItemMove(source.getBindingAdapterPosition(), target.getBindingAdapterPosition());
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
// Notify the adapter of the dismissal
mAdapter.onItemDismiss(viewHolder.getBindingAdapterPosition());
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
// 不执行删除操作,仅返回项目到原位
returnViewToOriginalPosition(viewHolder);
}
@Override
public void onChildDraw(@NotNull Canvas c, @NotNull RecyclerView recyclerView, @NotNull RecyclerView.ViewHolder viewHolder, float dX,
float dY, int actionState, boolean isCurrentlyActive) {
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
// Fade out the view as it is swiped out of the parent's bounds
final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
float maxSwipeDistance = viewHolder.itemView.getWidth() * SWIPE_THRESHOLD;
float swipeAmount = Math.abs(dX);
float direction = Math.signum(dX);
// 限制最大滑动距离
float translationX = Math.min(swipeAmount, maxSwipeDistance) * direction;
float alpha = ALPHA_FULL - Math.min(swipeAmount, maxSwipeDistance) / maxSwipeDistance;
viewHolder.itemView.setTranslationX(translationX);
viewHolder.itemView.setAlpha(alpha);
viewHolder.itemView.setTranslationX(dX);
if (swipeAmount >= maxSwipeDistance && isCurrentlyActive) {
returnViewToOriginalPosition(viewHolder);
}
} else {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
private void returnViewToOriginalPosition(RecyclerView.ViewHolder viewHolder) {
if (mReturnAnimator != null && mReturnAnimator.isRunning()) {
mReturnAnimator.cancel();
}
mReturnAnimator = ValueAnimator.ofFloat(viewHolder.itemView.getTranslationX(), 0f);
mReturnAnimator.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
viewHolder.itemView.setTranslationX(value);
viewHolder.itemView.setAlpha(1f - Math.abs(value) / (viewHolder.itemView.getWidth() * SWIPE_THRESHOLD));
});
mReturnAnimator.setInterpolator(new DecelerateInterpolator());
mReturnAnimator.setDuration(ANIMATION_DURATION);
mReturnAnimator.start();
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
// We only want the active item to change
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (viewHolder instanceof ItemTouchHelperViewHolder) {
// Let the view holder know that this item is being moved or dragged
ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemSelected();
}
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(@NotNull RecyclerView recyclerView, @NotNull RecyclerView.ViewHolder viewHolder) {
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
mAdapter.onItemMoveCompleted();
viewHolder.itemView.setAlpha(ALPHA_FULL);
if (viewHolder instanceof ItemTouchHelperViewHolder) {
// Tell the view holder it's time to restore the idle state
ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemClear();
}
mAdapter.onItemMoveCompleted();
}
@Override
public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
return 1.1f; // 设置一个大于1的值确保不会触发默认的滑动删除操作
}
@Override
public float getSwipeEscapeVelocity(float defaultValue) {
return defaultValue * 10; // 增加滑动逃逸速度,使得更难触发滑动
}
}

View File

@@ -60,8 +60,6 @@ object AppConfig {
const val CACHE_KEYWORD_FILTER = "cache_keyword_filter"
/** Protocol identifiers. */
const val PROTOCOL_HTTP: String = "http://"
const val PROTOCOL_HTTPS: String = "https://"
const val PROTOCOL_FREEDOM: String = "freedom"
/** Broadcast actions. */
@@ -157,5 +155,21 @@ object AppConfig {
/** Give a good name to this, IDK*/
const val VPN = "VPN"
// Google API rule constants
const val GOOGLEAPIS_CN_DOMAIN = "domain:googleapis.cn"
const val GOOGLEAPIS_COM_DOMAIN = "googleapis.com"
// Android Private DNS constants
const val DNS_PUB_DOMAIN = "dns.pub"
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
const val DNS_ONE_ONE_DOMAIN = "one.one.one.one"
const val DNS_GOOGLE_DOMAIN = "dns.google"
val DNS_PUB_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
val DNS_ONE_ONE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
}

View File

@@ -16,6 +16,6 @@ enum class EConfigType(val value: Int, val protocolScheme: String) {
HTTP(10, AppConfig.HTTP);
companion object {
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
fun fromInt(value: Int) = entries.firstOrNull { it.value == value }
}
}

View File

@@ -0,0 +1,18 @@
package com.v2ray.ang.dto
enum class Language(val code: String) {
AUTO("auto"),
ENGLISH("en"),
CHINA("zh-rCN"),
TRADITIONAL_CHINESE("zh-rTW"),
VIETNAMESE("vi"),
RUSSIAN("ru"),
PERSIAN("fa"),
BANGLA("bn");
companion object {
fun fromCode(code: String): Language {
return entries.find { it.code == code } ?: AUTO
}
}
}

View File

@@ -0,0 +1,20 @@
package com.v2ray.ang.dto
enum class RoutingType(val fileName: String) {
WHITE("custom_routing_white"),
BLACK("custom_routing_black"),
GLOBAL("custom_routing_global"),
WHITE_IRAN("custom_routing_white_iran");
companion object {
fun fromIndex(index: Int): RoutingType {
return when (index) {
0 -> WHITE
1 -> BLACK
2 -> GLOBAL
3 -> WHITE_IRAN
else -> WHITE
}
}
}
}

View File

@@ -321,8 +321,8 @@ data class V2rayConfig(
tcpSetting.header.type = HTTP
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
requestObj.headers.Host = (host.orEmpty()).split(",").map { it.trim() }.filter { it.isNotEmpty() }
requestObj.path = (path.orEmpty()).split(",").map { it.trim() }.filter { it.isNotEmpty() }
requestObj.headers.Host = host.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
requestObj.path = path.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
tcpSetting.header.request = requestObj
sni = requestObj.headers.Host?.getOrNull(0) ?: sni
}
@@ -371,7 +371,7 @@ data class V2rayConfig(
"h2", "http" -> {
network = "h2"
val h2Setting = HttpSettingsBean()
h2Setting.host = (host.orEmpty()).split(",").map { it.trim() }.filter { it.isNotEmpty() }
h2Setting.host = host.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
sni = h2Setting.host.getOrNull(0) ?: sni
h2Setting.path = path ?: "/"
httpSettings = h2Setting

View File

@@ -428,10 +428,11 @@ object V2RayServiceManager {
}
private fun stopSpeedNotification() {
if (mDisposable != null) {
mDisposable?.dispose() //stop queryStats
mDisposable?.let {
it.dispose() //stop queryStats
mDisposable = null
updateNotification(currentConfig?.remarks, 0, 0)
}
}
}

View File

@@ -4,6 +4,7 @@ import android.Manifest
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import com.tbruyelle.rxpermissions3.RxPermissions
@@ -133,13 +134,15 @@ class AboutActivity : BaseActivity() {
}
private fun showFileChooser() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "*/*"
addCategory(Intent.CATEGORY_OPENABLE)
}
try {
chooseFile.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
} catch (ex: android.content.ActivityNotFoundException) {
Log.e(AppConfig.ANG_PACKAGE, "File chooser activity not found: ${ex.message}", ex)
toast(R.string.toast_require_file_manager)
}
}
@@ -149,27 +152,23 @@ class AboutActivity : BaseActivity() {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
try {
try {
val targetFile =
File(this.cacheDir.absolutePath, "${System.currentTimeMillis()}.zip")
contentResolver.openInputStream(uri).use { input ->
targetFile.outputStream().use { fileOut ->
input?.copyTo(fileOut)
}
val targetFile =
File(this.cacheDir.absolutePath, "${System.currentTimeMillis()}.zip")
contentResolver.openInputStream(uri).use { input ->
targetFile.outputStream().use { fileOut ->
input?.copyTo(fileOut)
}
if (restoreConfiguration(targetFile)) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
} catch (e: Exception) {
e.printStackTrace()
}
if (restoreConfiguration(targetFile)) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
} catch (e: Exception) {
e.printStackTrace()
toast(e.message.toString())
Log.e(AppConfig.ANG_PACKAGE, "Error during file restore: ${e.message}", e)
toast(R.string.toast_failure)
}
}
}
}

View File

@@ -34,9 +34,6 @@ abstract class BaseActivity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.N)
override fun attachBaseContext(newBase: Context?) {
val context = newBase?.let {
MyContextWrapper.wrap(newBase, Utils.getLocale())
}
super.attachBaseContext(context)
super.attachBaseContext(MyContextWrapper.wrap(newBase ?: return, Utils.getLocale()))
}
}

View File

@@ -33,46 +33,42 @@ class LogcatActivity : BaseActivity() {
}
private fun logcat(shouldFlushLog: Boolean) {
binding.pbWaiting.visibility = View.VISIBLE
try {
binding.pbWaiting.visibility = View.VISIBLE
lifecycleScope.launch(Dispatchers.Default) {
lifecycleScope.launch(Dispatchers.Default) {
try {
if (shouldFlushLog) {
val lst = LinkedHashSet<String>()
lst.add("logcat")
lst.add("-c")
val lst = linkedSetOf("logcat", "-c")
withContext(Dispatchers.IO) {
val process = Runtime.getRuntime().exec(lst.toTypedArray())
process.waitFor()
}
}
val lst = LinkedHashSet<String>()
lst.add("logcat")
lst.add("-d")
lst.add("-v")
lst.add("time")
lst.add("-s")
lst.add("GoLog,tun2socks,${ANG_PACKAGE},AndroidRuntime,System.err")
val lst = linkedSetOf(
"logcat", "-d", "-v", "time", "-s",
"GoLog,tun2socks,$ANG_PACKAGE,AndroidRuntime,System.err"
)
val process = withContext(Dispatchers.IO) {
Runtime.getRuntime().exec(lst.toTypedArray())
}
// val bufferedReader = BufferedReader(
// InputStreamReader(process.inputStream))
// val allText = bufferedReader.use(BufferedReader::readText)
val allText = process.inputStream.bufferedReader().use { it.readText() }
launch(Dispatchers.Main) {
withContext(Dispatchers.Main) {
binding.tvLogcat.text = allText
binding.tvLogcat.movementMethod = ScrollingMovementMethod()
binding.pbWaiting.visibility = View.GONE
Handler(Looper.getMainLooper()).post { binding.svLogcat.fullScroll(View.FOCUS_DOWN) }
}
} catch (e: IOException) {
withContext(Dispatchers.Main) {
binding.pbWaiting.visibility = View.GONE
toast(R.string.toast_failure)
}
e.printStackTrace()
}
} catch (e: IOException) {
e.printStackTrace()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_logcat, menu)
return super.onCreateOptionsMenu(menu)

View File

@@ -46,6 +46,7 @@ import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.drakeet.support.toast.ToastCompat
import java.util.concurrent.TimeUnit
@@ -341,11 +342,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
R.id.ping_all -> {
toast(R.string.connection_test_testing)
mainViewModel.testAllTcping()
true
}
R.id.real_ping_all -> {
toast(R.string.connection_test_testing)
mainViewModel.testAllRealPing()
true
}
@@ -489,30 +492,34 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
private fun importBatchConfig(server: String?) {
// val dialog = AlertDialog.Builder(this)
// .setView(LayoutProgressBinding.inflate(layoutInflater).root)
// .setCancelable(false)
// .show()
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
val (count, countSub) = AngConfigManager.importBatchConfig(server, mainViewModel.subscriptionId, true)
delay(500L)
launch(Dispatchers.Main) {
if (count > 0) {
toast(R.string.toast_success)
mainViewModel.reloadServerList()
} else if (countSub > 0) {
initGroupTab()
} else {
toast(R.string.toast_failure)
try {
val (count, countSub) = AngConfigManager.importBatchConfig(server, mainViewModel.subscriptionId, true)
delay(500L)
withContext(Dispatchers.Main) {
when {
count > 0 -> {
toast(R.string.toast_success)
mainViewModel.reloadServerList()
}
countSub > 0 -> initGroupTab()
else -> toast(R.string.toast_failure)
}
binding.pbWaiting.hide()
}
//dialog.dismiss()
binding.pbWaiting.hide()
} catch (e: Exception) {
withContext(Dispatchers.Main) {
toast(R.string.toast_failure)
binding.pbWaiting.hide()
}
e.printStackTrace()
}
}
}
private fun importConfigCustomClipboard()
: Boolean {
try {
@@ -599,8 +606,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
delay(500L)
launch(Dispatchers.Main) {
if (count > 0) {
toast(R.string.toast_success)
//toast(R.string.toast_success)
toast(R.string.connection_test_testing)
mainViewModel.reloadServerList()
mainViewModel.testAllRealPing()
} else {
toast(R.string.toast_failure)
}

View File

@@ -59,22 +59,23 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
fun bind(appInfo: AppInfo) {
this.appInfo = appInfo
// Set app icon and name
itemBypassBinding.icon.setImageDrawable(appInfo.appIcon)
// name.text = appInfo.appName
itemBypassBinding.checkBox.isChecked = inBlacklist
itemBypassBinding.packageName.text = appInfo.packageName
if (appInfo.isSystemApp) {
itemBypassBinding.name.text = String.format("** %1s", appInfo.appName)
//name.textColor = Color.RED
itemBypassBinding.name.text = if (appInfo.isSystemApp) {
String.format("** %s", appInfo.appName)
} else {
itemBypassBinding.name.text = appInfo.appName
//name.textColor = Color.DKGRAY
appInfo.appName
}
// Set package name and checkbox state
itemBypassBinding.packageName.text = appInfo.packageName
itemBypassBinding.checkBox.isChecked = inBlacklist
// Handle item click to toggle blacklist status
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
if (inBlacklist) {
blacklist.remove(appInfo.packageName)

View File

@@ -59,16 +59,21 @@ class RoutingEditActivity : BaseActivity() {
private fun saveServer(): Boolean {
val rulesetItem = SettingsManager.getRoutingRuleset(position) ?: RulesetItem()
rulesetItem.remarks = binding.etRemarks.text.toString()
rulesetItem.looked = binding.chkLocked.isChecked
binding.etDomain.text.toString().let { rulesetItem.domain = if (it.isEmpty()) null else it.split(",").map { itt -> itt.trim() }.filter { itt -> itt.isNotEmpty() } }
binding.etIp.text.toString().let { rulesetItem.ip = if (it.isEmpty()) null else it.split(",").map { itt -> itt.trim() }.filter { itt -> itt.isNotEmpty() } }
binding.etProtocol.text.toString().let { rulesetItem.protocol = if (it.isEmpty()) null else it.split(",").map { itt -> itt.trim() }.filter { itt -> itt.isNotEmpty() } }
binding.etPort.text.toString().let { rulesetItem.port = it.ifEmpty { null } }
binding.etNetwork.text.toString().let { rulesetItem.network = it.ifEmpty { null } }
rulesetItem.outboundTag = outbound_tag[binding.spOutboundTag.selectedItemPosition]
rulesetItem.apply {
remarks = binding.etRemarks.text.toString()
looked = binding.chkLocked.isChecked
domain = binding.etDomain.text.toString().takeIf { it.isNotEmpty() }
?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }
ip = binding.etIp.text.toString().takeIf { it.isNotEmpty() }
?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }
protocol = binding.etProtocol.text.toString().takeIf { it.isNotEmpty() }
?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }
port = binding.etPort.text.toString().takeIf { it.isNotEmpty() }
network = binding.etNetwork.text.toString().takeIf { it.isNotEmpty() }
outboundTag = outbound_tag[binding.spOutboundTag.selectedItemPosition]
}
if (TextUtils.isEmpty(rulesetItem.remarks)) {
if (rulesetItem.remarks.isNullOrEmpty()) {
toast(R.string.sub_setting_remarks)
return false
}
@@ -79,6 +84,7 @@ class RoutingEditActivity : BaseActivity() {
return true
}
private fun deleteServer(): Boolean {
if (position >= 0) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)

View File

@@ -24,6 +24,7 @@ import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class RoutingSettingActivity : BaseActivity() {
private val binding by lazy { ActivityRoutingSettingBinding.inflate(layoutInflater) }
@@ -113,30 +114,33 @@ class RoutingSettingActivity : BaseActivity() {
R.id.import_rulesets_from_clipboard -> {
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
.setPositiveButton(android.R.string.ok) { _, _ ->
try {
val clipboard = Utils.getClipboard(this)
lifecycleScope.launch(Dispatchers.IO) {
val ret = SettingsManager.resetRoutingRulesetsFromClipboard(clipboard)
launch(Dispatchers.Main) {
if (ret) {
refreshData()
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
}
}
val clipboard = try {
Utils.getClipboard(this)
} catch (e: Exception) {
e.printStackTrace()
toast(R.string.toast_failure)
return@setPositiveButton
}
lifecycleScope.launch(Dispatchers.IO) {
val result = SettingsManager.resetRoutingRulesetsFromClipboard(clipboard)
withContext(Dispatchers.Main) {
if (result) {
refreshData()
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
//do noting
//do nothing
}
.show()
true
}
R.id.export_rulesets_to_clipboard -> {
val rulesetList = MmkvManager.decodeRoutingRulesets()
if (rulesetList.isNullOrEmpty()) {
@@ -152,7 +156,9 @@ class RoutingSettingActivity : BaseActivity() {
}
fun refreshData() {
rulesets = MmkvManager.decodeRoutingRulesets() ?: mutableListOf()
rulesets.clear()
rulesets.addAll(MmkvManager.decodeRoutingRulesets() ?: mutableListOf())
adapter.notifyDataSetChanged()
}
}

View File

@@ -20,26 +20,31 @@ class ScScannerActivity : BaseActivity() {
fun importQRcode(): Boolean {
RxPermissions(this)
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
.subscribe { granted ->
if (granted) {
scanQRCode.launch(Intent(this, ScannerActivity::class.java))
else
} else {
toast(R.string.toast_permission_denied)
}
}
return true
}
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
val (count, countSub) = AngConfigManager.importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"), "", false)
val scanResult = it.data?.getStringExtra("SCAN_RESULT").orEmpty()
val (count, countSub) = AngConfigManager.importBatchConfig(scanResult, "", false)
if (count + countSub > 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
startActivity(Intent(this, MainActivity::class.java))
}
finish()
}
}

View File

@@ -74,19 +74,17 @@ class ScannerActivity : BaseActivity() {
}
RxPermissions(this)
.request(permission)
.subscribe {
if (it) {
try {
showFileChooser()
} catch (e: Exception) {
e.printStackTrace()
}
} else
.subscribe { granted ->
if (granted) {
showFileChooser()
} else {
toast(R.string.toast_permission_denied)
}
}
true
}
else -> super.onOptionsItemSelected(item)
}
@@ -107,13 +105,21 @@ class ScannerActivity : BaseActivity() {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
try {
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
val inputStream = contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
finished(text.orEmpty())
if (text.isNullOrEmpty()) {
toast(R.string.toast_decoding_failed)
} else {
finished(text)
}
} catch (e: Exception) {
e.printStackTrace()
toast(e.message.toString())
toast(R.string.toast_decoding_failed)
}
}
}
}

View File

@@ -201,31 +201,49 @@ class ServerActivity : BaseActivity() {
position: Int,
id: Long
) {
if (streamSecuritys[position].isBlank()) {
container_sni?.visibility = View.GONE
container_fingerprint?.visibility = View.GONE
container_alpn?.visibility = View.GONE
container_allow_insecure?.visibility = View.GONE
container_public_key?.visibility = View.GONE
container_short_id?.visibility = View.GONE
container_spider_x?.visibility = View.GONE
} else {
container_sni?.visibility = View.VISIBLE
container_fingerprint?.visibility = View.VISIBLE
container_alpn?.visibility = View.VISIBLE
if (streamSecuritys[position] == TLS) {
val isBlank = streamSecuritys[position].isBlank()
val isTLS = streamSecuritys[position] == TLS
when {
// Case 1: Null or blank
isBlank -> {
listOf(
container_sni, container_fingerprint, container_alpn,
container_allow_insecure, container_public_key,
container_short_id, container_spider_x
).forEach { it?.visibility = View.GONE }
}
// Case 2: TLS value
isTLS -> {
listOf(
container_sni,
container_fingerprint,
container_alpn
).forEach { it?.visibility = View.VISIBLE }
container_allow_insecure?.visibility = View.VISIBLE
container_public_key?.visibility = View.GONE
container_short_id?.visibility = View.GONE
container_spider_x?.visibility = View.GONE
} else {
container_allow_insecure?.visibility = View.GONE
listOf(
container_public_key,
container_short_id,
container_spider_x
).forEach { it?.visibility = View.GONE }
}
// Case 3: Other reality values
else -> {
listOf(container_sni, container_fingerprint).forEach {
it?.visibility = View.VISIBLE
}
container_alpn?.visibility = View.GONE
container_public_key?.visibility = View.VISIBLE
container_short_id?.visibility = View.VISIBLE
container_spider_x?.visibility = View.VISIBLE
container_allow_insecure?.visibility = View.GONE
listOf(
container_public_key,
container_short_id,
container_spider_x
).forEach { it?.visibility = View.VISIBLE }
}
}
}
override fun onNothingSelected(p0: AdapterView<*>?) {
@@ -560,11 +578,12 @@ class ServerActivity : BaseActivity() {
val shortId = et_short_id?.text?.toString()
val spiderX = et_spider_x?.text?.toString()
val allowInsecure = if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
} else {
allowinsecures[allowInsecureField].toBoolean()
}
val allowInsecure =
if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
} else {
allowinsecures[allowInsecureField].toBoolean()
}
streamSetting.populateTlsSettings(
streamSecurity = streamSecuritys[streamSecurity],

View File

@@ -48,16 +48,14 @@ class ServerCustomConfigActivity : BaseActivity() {
}
/**
* bingding seleced server config
* Binding selected server config
*/
private fun bindingServer(config: ServerConfig): Boolean {
binding.etRemarks.text = Utils.getEditable(config.remarks)
val raw = MmkvManager.decodeServerRaw(editGuid)
if (raw.isNullOrBlank()) {
binding.editor.setTextContent(Utils.getEditable(config.fullConfig?.toPrettyPrinting().orEmpty()))
} else {
binding.editor.setTextContent(Utils.getEditable(raw))
}
val configContent = raw ?: config.fullConfig?.toPrettyPrinting().orEmpty()
binding.editor.setTextContent(Utils.getEditable(configContent))
return true
}

View File

@@ -316,13 +316,11 @@ class SettingsActivity : BaseActivity() {
}
private fun updateMuxConcurrency(value: String?) {
if (value == null) {
} else {
val concurrency = value.toIntOrNull() ?: 8
muxConcurrency?.summary = concurrency.toString()
}
val concurrency = value?.toIntOrNull() ?: 8
muxConcurrency?.summary = concurrency.toString()
}
private fun updateMuxXudpConcurrency(value: String?) {
if (value == null) {
muxXudpQuic?.isEnabled = true

View File

@@ -81,10 +81,17 @@ class SubEditActivity : BaseActivity() {
toast(R.string.sub_setting_remarks)
return false
}
// if (TextUtils.isEmpty(subItem.url)) {
// toast(R.string.sub_setting_url)
// return false
// }
if (subItem.url.isNotEmpty()) {
if (!Utils.isValidUrl(subItem.url)) {
toast(R.string.toast_invalid_url)
return false
}
if (!Utils.isValidSubUrl(subItem.url)) {
toast(R.string.toast_insecure_url_protocol)
//return false
}
}
MmkvManager.encodeSubscription(editSubId, subItem)
toast(R.string.toast_success)

View File

@@ -71,23 +71,11 @@ class UserAssetActivity : BaseActivity() {
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.add_file -> {
showFileChooser()
true
}
R.id.add_url -> {
val intent = Intent(this, UserAssetUrlActivity::class.java)
startActivity(intent)
true
}
R.id.download_file -> {
downloadGeoFiles()
true
}
// Use when to streamline the option selection
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.add_file -> showFileChooser().let { true }
R.id.add_url -> startActivity(Intent(this, UserAssetUrlActivity::class.java)).let { true }
R.id.download_file -> downloadGeoFiles().let { true }
else -> super.onOptionsItemSelected(item)
}
@@ -120,31 +108,29 @@ class UserAssetActivity : BaseActivity() {
}
}
private val chooseFile =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { it ->
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
val assetId = Utils.getUuid()
try {
val assetItem = AssetUrlItem(
getCursorName(uri) ?: uri.toString(),
"file"
)
val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val uri = result.data?.data
if (result.resultCode == RESULT_OK && uri != null) {
val assetId = Utils.getUuid()
runCatching {
val assetItem = AssetUrlItem(
getCursorName(uri) ?: uri.toString(),
"file"
)
// check remarks unique
val assetList = MmkvManager.decodeAssetUrls()
if (assetList.any { it.second.remarks == assetItem.remarks && it.first != assetId }) {
toast(R.string.msg_remark_is_duplicate)
return@registerForActivityResult
}
val assetList = MmkvManager.decodeAssetUrls()
if (assetList.any { it.second.remarks == assetItem.remarks && it.first != assetId }) {
toast(R.string.msg_remark_is_duplicate)
} else {
MmkvManager.encodeAsset(assetId, assetItem)
copyFile(uri)
} catch (e: Exception) {
toast(R.string.toast_asset_copy_failed)
MmkvManager.removeAssetUrl(assetId)
}
}.onFailure {
toast(R.string.toast_asset_copy_failed)
MmkvManager.removeAssetUrl(assetId)
}
}
}
private fun copyFile(uri: Uri): String {
val targetFile = File(extDir, getCursorName(uri) ?: uri.toString())

View File

@@ -219,7 +219,7 @@ object AngConfigManager {
var count = 0
servers.lines()
.forEach { str ->
if (str.startsWith(AppConfig.PROTOCOL_HTTP) || str.startsWith(AppConfig.PROTOCOL_HTTPS)) {
if (Utils.isValidSubUrl(str)) {
count += importUrlAsSubscription(str)
}
}

View File

@@ -23,7 +23,7 @@ object JsonUtil {
val gsonPre = GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
.registerTypeAdapter( // custom serializer is needed here since JSON by default parse number as Double, core will fail to start
object : TypeToken<Double>() {}.type,
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? ->
JsonPrimitive(

View File

@@ -23,36 +23,19 @@ object QRCodeDecoder {
* 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()
}
}
return runCatching {
val hints = mapOf(EncodeHintType.CHARACTER_SET to Charsets.UTF_8)
val bitMatrix = QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, size, size, hints)
val pixels = IntArray(size * size) { i ->
if (bitMatrix.get(i % size, i / size)) 0xff000000.toInt() else 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
}
Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888).apply {
setPixels(pixels, 0, size, 0, 0, size, size)
}
}.getOrNull()
}
/**
* 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。
*
@@ -70,40 +53,24 @@ object QRCodeDecoder {
* @return 返回二维码图片里的内容 或 null
*/
fun syncDecodeQRCode(bitmap: Bitmap?): String? {
if (bitmap == null) {
return null
}
var source: RGBLuminanceSource? = null
try {
val width = bitmap.width
val height = bitmap.height
val pixels = IntArray(width * height)
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
source = RGBLuminanceSource(width, height, pixels)
val qrReader = QRCodeReader()
try {
val result = try {
qrReader.decode(
BinaryBitmap(GlobalHistogramBinarizer(source)),
mapOf(DecodeHintType.TRY_HARDER to true)
)
} catch (e: NotFoundException) {
qrReader.decode(
BinaryBitmap(GlobalHistogramBinarizer(source.invert())),
mapOf(DecodeHintType.TRY_HARDER to true)
)
return bitmap?.let {
runCatching {
val pixels = IntArray(it.width * it.height).also { array ->
it.getPixels(array, 0, it.width, 0, 0, it.width, it.height)
}
return result.text
} catch (e: Exception) {
e.printStackTrace()
}
} catch (e: Exception) {
e.printStackTrace()
}
val source = RGBLuminanceSource(it.width, it.height, pixels)
val qrReader = QRCodeReader()
return null
try {
qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source)), mapOf(DecodeHintType.TRY_HARDER to true)).text
} catch (e: NotFoundException) {
qrReader.decode(BinaryBitmap(GlobalHistogramBinarizer(source.invert())), mapOf(DecodeHintType.TRY_HARDER to true)).text
}
}.getOrNull()
}
}
/**
* 将本地图片文件转换成可解码二维码的 Bitmap。为了避免图片太大这里对图片进行了压缩。感谢 https://github.com/devilsen 提的 PR
*
@@ -149,6 +116,6 @@ object QRCodeDecoder {
)
HINTS[DecodeHintType.TRY_HARDER] = BarcodeFormat.QR_CODE
HINTS[DecodeHintType.POSSIBLE_FORMATS] = allFormats
HINTS[DecodeHintType.CHARACTER_SET] = "utf-8"
HINTS[DecodeHintType.CHARACTER_SET] = Charsets.UTF_8
}
}

View File

@@ -7,6 +7,7 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.GEOIP_PRIVATE
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.dto.RoutingType
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.util.MmkvManager.decodeProfileConfig
@@ -27,13 +28,7 @@ object SettingsManager {
}
private fun getPresetRoutingRulesets(context: Context, index: Int = 0): MutableList<RulesetItem>? {
val fileName = when (index) {
0 -> "custom_routing_white"
1 -> "custom_routing_black"
2 -> "custom_routing_global"
3 -> "custom_routing_white_iran"
else -> "custom_routing_white"
}
val fileName = RoutingType.fromIndex(index).fileName
val assets = Utils.readTextFromAssets(context, fileName)
if (TextUtils.isEmpty(assets)) {
return null
@@ -42,6 +37,7 @@ object SettingsManager {
return JsonUtil.fromJson(assets, Array<RulesetItem>::class.java).toMutableList()
}
fun resetRoutingRulesets(context: Context, index: Int) {
val rulesetList = getPresetRoutingRulesets(context, index) ?: return
resetRoutingRulesetsCommon(rulesetList)

View File

@@ -22,6 +22,7 @@ import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.Language
import com.v2ray.ang.extension.toast
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.MmkvManager.settingsStorage
@@ -405,21 +406,24 @@ object Utils {
}
fun getLocale(): Locale {
val lang = settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: "auto"
return when (lang) {
"auto" -> getSysLocale()
"en" -> Locale.ENGLISH
"zh-rCN" -> Locale.CHINA
"zh-rTW" -> Locale.TRADITIONAL_CHINESE
"vi" -> Locale("vi")
"ru" -> Locale("ru")
"fa" -> Locale("fa")
"bn" -> Locale("bn")
else -> getSysLocale()
val langCode = settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: Language.AUTO.code
val language = Language.fromCode(langCode)
return when (language) {
Language.AUTO -> getSysLocale()
Language.ENGLISH -> Locale.ENGLISH
Language.CHINA -> Locale.CHINA
Language.TRADITIONAL_CHINESE -> Locale.TRADITIONAL_CHINESE
Language.VIETNAMESE -> Locale("vi")
Language.RUSSIAN -> Locale("ru")
Language.PERSIAN -> Locale("fa")
Language.BANGLA -> Locale("bn")
}
}
private fun getSysLocale(): Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
LocaleList.getDefault()[0]
} else {
@@ -465,5 +469,17 @@ object Utils {
// if the program gets here, no port in the range was found
throw IOException("no free port found")
}
fun isValidSubUrl(value: String?): Boolean {
try {
if (value.isNullOrEmpty()) return false
if (URLUtil.isHttpsUrl(value)) return true
if (URLUtil.isHttpUrl(value) && value.contains(LOOPBACK)) return true
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
}

View File

@@ -6,10 +6,20 @@ import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.DNS_ALIDNS_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_ALIDNS_DOMAIN
import com.v2ray.ang.AppConfig.DNS_GOOGLE_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_GOOGLE_DOMAIN
import com.v2ray.ang.AppConfig.DNS_ONE_ONE_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_ONE_ONE_DOMAIN
import com.v2ray.ang.AppConfig.DNS_PUB_ADDRESSES
import com.v2ray.ang.AppConfig.DNS_PUB_DOMAIN
import com.v2ray.ang.AppConfig.GEOIP_CN
import com.v2ray.ang.AppConfig.GEOSITE_CN
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
import com.v2ray.ang.AppConfig.GOOGLEAPIS_CN_DOMAIN
import com.v2ray.ang.AppConfig.GOOGLEAPIS_COM_DOMAIN
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
import com.v2ray.ang.AppConfig.TAG_BLOCKED
import com.v2ray.ang.AppConfig.TAG_DIRECT
@@ -71,7 +81,8 @@ object V2rayConfigUtil {
return result
}
val v2rayConfig = JsonUtil.fromJson(assets, V2rayConfig::class.java) ?: return result
v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
v2rayConfig.log.loglevel =
settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
v2rayConfig.remarks = config.remarks
inbounds(v2rayConfig)
@@ -143,7 +154,11 @@ object V2rayConfigUtil {
return true
}
private fun outbounds(v2rayConfig: V2rayConfig, outbound: V2rayConfig.OutboundBean, isPlugin: Boolean): Pair<Boolean, String> {
private fun outbounds(
v2rayConfig: V2rayConfig,
outbound: V2rayConfig.OutboundBean,
isPlugin: Boolean
): Pair<Boolean, String> {
if (isPlugin) {
val socksPort = Utils.findFreePort(listOf(100 + SettingsManager.getSocksPort(), 0))
val outboundNew = V2rayConfig.OutboundBean(
@@ -190,7 +205,9 @@ object V2rayConfigUtil {
private fun routing(v2rayConfig: V2rayConfig): Boolean {
try {
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) ?: "IPIfNonMatch"
v2rayConfig.routing.domainStrategy =
settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
?: "IPIfNonMatch"
val rulesetItems = MmkvManager.decodeRoutingRulesets()
rulesetItems?.forEach { key ->
@@ -293,7 +310,7 @@ object V2rayConfigUtil {
// DNS routing tag
v2rayConfig.routing.rules.add(
0, V2rayConfig.RoutingBean.RulesBean(
0, RulesBean(
inboundTag = arrayListOf("dns-in"),
outboundTag = "dns-out",
domain = null
@@ -346,7 +363,7 @@ object V2rayConfigUtil {
if (Utils.isPureIpAddress(domesticDns.first())) {
v2rayConfig.routing.rules.add(
0, V2rayConfig.RoutingBean.RulesBean(
0, RulesBean(
outboundTag = TAG_DIRECT,
port = "53",
ip = arrayListOf(domesticDns.first()),
@@ -362,13 +379,14 @@ object V2rayConfigUtil {
}
// hardcode googleapi rule to fix play store problems
hosts["domain:googleapis.cn"] = "googleapis.com"
hosts[GOOGLEAPIS_CN_DOMAIN] = GOOGLEAPIS_COM_DOMAIN
// hardcode popular Android Private DNS rule to fix localhost DNS problem
hosts["dns.pub"] = arrayListOf("1.12.12.12", "120.53.53.53")
hosts["dns.alidns.com"] = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
hosts["one.one.one.one"] = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
hosts["dns.google"] = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
hosts[DNS_PUB_DOMAIN] = DNS_PUB_ADDRESSES
hosts[DNS_ALIDNS_DOMAIN] = DNS_ALIDNS_ADDRESSES
hosts[DNS_ONE_ONE_DOMAIN] = DNS_ONE_ONE_ADDRESSES
hosts[DNS_GOOGLE_DOMAIN] = DNS_GOOGLE_ADDRESSES
// DNS dns对象
v2rayConfig.dns = V2rayConfig.DnsBean(
@@ -379,7 +397,7 @@ object V2rayConfigUtil {
// DNS routing
if (Utils.isPureIpAddress(remoteDns.first())) {
v2rayConfig.routing.rules.add(
0, V2rayConfig.RoutingBean.RulesBean(
0, RulesBean(
outboundTag = TAG_PROXY,
port = "53",
ip = arrayListOf(remoteDns.first()),
@@ -532,7 +550,11 @@ object V2rayConfigUtil {
return true
}
private fun moreOutbounds(v2rayConfig: V2rayConfig, subscriptionId: String, isPlugin: Boolean): Pair<Boolean, String> {
private fun moreOutbounds(
v2rayConfig: V2rayConfig,
subscriptionId: String,
isPlugin: Boolean
): Pair<Boolean, String> {
val returnPair = Pair(false, "")
var domainPort: String = ""
@@ -544,7 +566,7 @@ object V2rayConfigUtil {
return returnPair
}
if (subscriptionId.isNullOrEmpty()) {
if (subscriptionId.isEmpty()) {
return returnPair
}
try {

View File

@@ -15,7 +15,7 @@ import java.net.URI
object Hysteria2Fmt : FmtBase() {
fun parse(str: String): ServerConfig {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val allowInsecure = settingsStorage.decodeBool(AppConfig.PREF_ALLOW_INSECURE,false)
val config = ServerConfig.create(EConfigType.HYSTERIA2)
val uri = URI(Utils.fixIllegalUrl(str))

View File

@@ -23,7 +23,6 @@ import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.serializable
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
@@ -43,7 +42,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
var subscriptionId: String = MmkvManager.settingsStorage.decodeString(AppConfig.CACHE_SUBSCRIPTION_ID, "").orEmpty()
//var keywordFilter: String = MmkvManager.settingsStorage.decodeString(AppConfig.CACHE_KEYWORD_FILTER, "")?:""
var keywordFilter = ""
var keywordFilter = ""
val serversCache = mutableListOf<ServersCache>()
val isRunning by lazy { MutableLiveData<Boolean>() }
val updateListAction by lazy { MutableLiveData<Int>() }
@@ -153,17 +152,17 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
fun updateConfigViaSubAll(): Int {
if (subscriptionId.isNullOrEmpty()) {
if (subscriptionId.isEmpty()) {
return AngConfigManager.updateConfigViaSubAll()
} else {
val subItem = MmkvManager.decodeSubscription(subscriptionId) ?: return 0
return updateConfigViaSub(Pair(subscriptionId, subItem))
return AngConfigManager.updateConfigViaSub(Pair(subscriptionId, subItem))
}
}
fun exportAllServer(): Int {
val serverListCopy =
if (subscriptionId.isNullOrEmpty() && keywordFilter.isNullOrEmpty()) {
if (subscriptionId.isEmpty() && keywordFilter.isEmpty()) {
serverList
} else {
serversCache.map { it.guid }.toList()
@@ -181,10 +180,10 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
SpeedtestUtil.closeAllTcpSockets()
MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList())
updateListAction.value = -1 // update all
//updateListAction.value = -1 // update all
getApplication<AngApplication>().toast(R.string.connection_test_testing)
for (item in serversCache) {
val serversCopy = serversCache.toList() // Create a copy of the list
for (item in serversCopy) {
item.profile.let { outbound ->
val serverAddress = outbound.server
val serverPort = outbound.serverPort
@@ -207,8 +206,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
updateListAction.value = -1 // update all
val serversCopy = serversCache.toList() // Create a copy of the list
getApplication<AngApplication>().toast(R.string.connection_test_testing)
viewModelScope.launch(Dispatchers.Default) { // without Dispatchers.Default viewModelScope will launch in main thread
for (item in serversCopy) {
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, item.guid)
@@ -281,7 +278,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
fun removeAllServer() {
if (subscriptionId.isNullOrEmpty() && keywordFilter.isNullOrEmpty()) {
if (subscriptionId.isEmpty() && keywordFilter.isEmpty()) {
MmkvManager.removeAllServer()
} else {
val serversCopy = serversCache.toList()
@@ -292,7 +289,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
fun removeInvalidServer() {
if (subscriptionId.isNullOrEmpty() && keywordFilter.isNullOrEmpty()) {
if (subscriptionId.isEmpty() && keywordFilter.isEmpty()) {
MmkvManager.removeInvalidServer("")
} else {
val serversCopy = serversCache.toList()
@@ -394,4 +391,4 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
}
}
}
}

View File

@@ -33,17 +33,13 @@
android:gravity="center"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_pref_per_app_proxy"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_per_app_proxy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/padding_start" />
android:maxLines="2"
android:text="@string/title_pref_per_app_proxy"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
@@ -56,18 +52,12 @@
android:gravity="center"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_bypass_apps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/switch_bypass_apps_mode"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_bypass_apps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/padding_start" />
android:text="@string/switch_bypass_apps_mode"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
</LinearLayout>

View File

@@ -99,6 +99,7 @@
<string name="server_lab_content">المحتوى</string>
<string name="toast_none_data_clipboard">لا توجد بيانات في الحافظة</string>
<string name="toast_invalid_url">رابط URL غير صالح</string>
<string name="toast_insecure_url_protocol">Please do not use the insecure HTTP protocol subscription address</string>
<string name="server_lab_need_inbound">تأكد من أن منفذ الاتصالات الواردة يتوافق مع الإعدادات</string>
<string name="toast_malformed_josn">تكوين مشوه</string>
<string name="server_lab_request_host6">مضيف (SNI) (اختياري)</string>

View File

@@ -98,6 +98,7 @@
<string name="server_lab_content">কনটেন্ট</string>
<string name="toast_none_data_clipboard">ক্লিপবোর্ডে কোনও তথ্য নেই</string>
<string name="toast_invalid_url">অবৈধ URL</string>
<string name="toast_insecure_url_protocol">Please do not use the insecure HTTP protocol subscription address</string>
<string name="server_lab_need_inbound">ইনবাউন্ড পোর্ট নিশ্চিত করুন সেটিংসের সাথে সামঞ্জস্যপূর্ণ</string>
<string name="toast_malformed_josn">কনফিগারেশন বিকৃত</string>
<string name="server_lab_request_host6">হোস্ট (SNI) (ঐচ্ছিক)</string>
@@ -300,4 +301,13 @@
<item>লাইট</item>
<item>ডার্ক</item>
</string-array>
<string-array name="preset_rulesets">
<item>চায়না হোয়াইটলিস্ট</item>
<item>চায়না ব্ল্যাকলিস্ট</item>
<item>গ্লোবাল</item>
<item>ইরান হোয়াইটলিস্ট</item>
</string-array>
</resources>

View File

@@ -97,6 +97,7 @@
<string name="server_lab_content">محتوا</string>
<string name="toast_none_data_clipboard">هیچ داده‌ای در کلیپ‌بورد وجود ندارد</string>
<string name="toast_invalid_url">نشانی اینترنتی معتبر نیست</string>
<string name="toast_insecure_url_protocol">Please do not use the insecure HTTP protocol subscription address</string>
<string name="server_lab_need_inbound">اطمینان حاصل کنید که پورت ورودی با تنظیمات مطابقت دارد</string>
<string name="toast_malformed_josn">کانفیگ درست نیست</string>
<string name="server_lab_request_host6">میزبان (SNI) (اختیاری)</string>
@@ -126,13 +127,13 @@
<string name="title_advanced">تنظیمات پیشرفته</string>
<string name="title_vpn_settings">تنظیمات VPN</string>
<string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string>
<string name="summary_pref_per_app_proxy">عمومی: برنامه بررسی شده پروکسی است، اتصال مستقیم بدون بررسی است. \nحالت bypass: برنامه بررسی شده مستقیما متصل است، پراکسی بررسی نشده است. \nگزینهای برای انتخاب خودکار پروکسی برنامه در منو است</string>
<string name="summary_pref_per_app_proxy">عمومی: برنامه بررسی شده پروکسی است، اتصال مستقیم بدون بررسی است. \nحالت bypass: برنامه بررسی شده مستقیما متصل است، پراکسی بررسی نشده است. \nگزینهای برای انتخاب خودکار پروکسی برنامه در منو است.</string>
<string name="title_pref_is_booted">اتصال خودکار هنگام راه اندازی</string>
<string name="summary_pref_is_booted">هنگام راه اندازی به طور خودکار به سرور انتخابی متصل می شود که ممکن است ناموفق باشد</string>
<string name="summary_pref_is_booted">هنگام راه اندازی به طور خودکار به سرور انتخابی متصل می شود که ممکن است ناموفق باشد.</string>
<string name="title_mux_settings">تنظیمات Mux</string>
<string name="title_pref_mux_enabled">فعال کردن Mux</string>
<string name="summary_pref_mux_enabled">سریعتر است، اما ممکن است باعث اتصال ناپایدار شود\nمخزن ترافیک TCP با 8 اتصال پیش‌فرض، نحوه مدیریت UDP و QUIC را در زیر سفارشی کنید</string>
<string name="summary_pref_mux_enabled">سریعتر است، اما ممکن است باعث اتصال ناپایدار شود\nمخزن ترافیک TCP با 8 اتصال پیش‌فرض، نحوه مدیریت UDP و QUIC را در زیر سفارشی کنید.</string>
<string name="title_pref_mux_concurency">اتصالات TCP (محدوده -1 تا 1024)</string>
<string name="title_pref_mux_xudp_concurency">اتصالات XUDP (محدوده -1 تا 1024)</string>
<string name="title_pref_mux_xudp_quic">مدیریت QUIC در تونل mux</string>
@@ -146,16 +147,16 @@
<string name="summary_pref_speed_enabled">نمایش سرعت فعلی در قسمت آگاه‌سازی. \nآیکون آگاه‌سازی بر اساس استفاده تغییر می‌کند.</string>
<string name="title_pref_sniffing_enabled">فعال کردن Sniffing</string>
<string name="summary_pref_sniffing_enabled">دامنه sniff را از بسته امتحان کنید (پیش‌فرض روشن)</string>
<string name="summary_pref_sniffing_enabled">دامنه sniff را از بسته امتحان کنید (پیش‌فرض روشن).</string>
<string name="title_pref_route_only_enabled">فعال کردن routeOnly</string>
<string name="summary_pref_route_only_enabled">از نام دامنه sniffed فقط برای مسیریابی استفاده کنید و آدرس مورد نظر را به عنوان آدرس IP نگه دارید.</string>
<string name="title_pref_local_dns_enabled">فعال کردن DNS محلی</string>
<string name="summary_pref_local_dns_enabled">DNS پردازش شده توسط ماژول DNS هسته (توصیه میشود، در صورت نیاز به دور زدن LAN و نشانی mainland)</string>
<string name="summary_pref_local_dns_enabled">درخواست های DNS به هسته وارد شده و توسط ماژول DNS پردازش می شوند(توصیه می شود در صورت نیاز به مسیریابی برای دور زدن آدرس های LAN و سرزمین اصلی فعال شود)</string>
<string name="title_pref_fake_dns_enabled">فعال کردن DNS جعلی</string>
<string name="summary_pref_fake_dns_enabled">DNS محلی آدرس IP جعلی را برمی‌گرداند (سریعتر میباشد، اما ممکن است برای برخی از برنامهها کار نکند)</string>
<string name="summary_pref_fake_dns_enabled">DNS محلی آدرس های آیپی فیک را برمی‌گرداند(سریعتر می باشد اما ممکن است برای برخی از برنامه ها کار نکند)</string>
<string name="title_pref_prefer_ipv6">ترجیح دادن IPv6</string>
<string name="summary_pref_prefer_ipv6">ترجیح دادن نشانی و مسیر های IPv6</string>
@@ -172,11 +173,11 @@
<string name="summary_pref_delay_test_url">Url</string>
<string name="title_pref_proxy_sharing_enabled">اجازه اتصالات از طریق LAN</string>
<string name="summary_pref_proxy_sharing_enabled">دستگاه‌های دیگر می‌توانند از طریق socks/http به پراکسی توسط نشانی آی‌پی شما متصل شوند، فقط در شبکه مورد اعتماد فعال می‌شوند تا از اتصال غیرمجاز جلوگیری کنند</string>
<string name="summary_pref_proxy_sharing_enabled">دستگاه‌های دیگر می‌توانند از طریق socks/http به پراکسی توسط نشانی آی‌پی شما متصل شوند، فقط در شبکه مورد اعتماد فعال می‌شوند تا از اتصال غیرمجاز جلوگیری کنند.</string>
<string name="toast_warning_pref_proxysharing_short">اتصالات از طریق LAN را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید</string>
<string name="title_pref_allow_insecure">موز ناامن</string>
<string name="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیش‌فرض مجوز ناامن فعال است</string>
<string name="title_pref_allow_insecure">مجوز ناامن</string>
<string name="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیش‌فرض مجوز ناامن فعال است.</string>
<string name="title_pref_socks_port">پورت پروکسی SOCKS5</string>
<string name="summary_pref_socks_port">پورت پروکسی SOCKS5</string>
@@ -202,15 +203,15 @@
<string name="title_about">درباره</string>
<string name="title_source_code">کد منبع</string>
<string name="title_tg_channel">کانال تلگرام</string>
<string name="title_configuration_backup">پشتیبانگیری از پیکربندی</string>
<string name="title_configuration_backup">پشتیبان گیری از پیکربندی</string>
<string name="summary_configuration_backup">محل ذخیره سازی: [%s], پس از حذف نصب برنامه یا پاک کردن فضای ذخیره سازی، نسخه پشتیبان پاک می شود</string>
<string name="title_configuration_restore">بازیابی پیکربندی</string>
<string name="title_configuration_share">اشتراکگذاری پیکربندی</string>
<string name="title_configuration_share">اشتراک گذاری پیکربندی</string>
<string name="title_pref_promotion">تبلیغات</string>
<string name="summary_pref_promotion">تبلیغات، برای جزئیات بیشتر کلیک کنید (کمک مالی کنید تا حذف شود)</string>
<string name="title_pref_auto_update_subscription">به‌روزرسانی خودکار اشتراک ها</string>
<string name="summary_pref_auto_update_subscription">اشتراک های خود را به طور خودکار با فاصله زمانی در پس زمینه به روز کنید. بسته به دستگاه، این ویژگی ممکن است همیشه کار نکند</string>
<string name="summary_pref_auto_update_subscription">اشتراک های خود را به طور خودکار با فاصله زمانی در پس زمینه به روز کنید. بسته به دستگاه، این ویژگی ممکن است همیشه کار نکند.</string>
<string name="title_pref_auto_update_interval">فاصله به‌روزرسانی خودکار (دقیقه، حداقل مقدار 15)</string>
<string name="title_core_loglevel">سطح گزارشات</string>
<string name="title_mode">حالت</string>
@@ -223,11 +224,11 @@
<string name="logcat_copy">کپی</string>
<string name="logcat_clear">پاک کردن</string>
<string name="title_service_restart">راه‌اندازی مجدد خدمات</string>
<string name="title_del_all_config">حذف تمام کانفیگ</string>
<string name="title_del_duplicate_config">حذف کانفیگ های تکراری</string>
<string name="title_del_invalid_config">حذف کانفیگهای نامعتبر (ابتدا آزمایش کنید)</string>
<string name="title_export_all">خروجی گرفتن کانفیگهای غیرسفارشی در کلیپ‌بورد</string>
<string name="title_sub_setting">تنظیمات گروه‌ی اشتراک</string>
<string name="title_del_all_config">حذف تمام کانفیگ های گروه فعلی</string>
<string name="title_del_duplicate_config">حذف کانفیگ های تکراری گروه فعلی</string>
<string name="title_del_invalid_config">حذف کانفیگ های نامعتبر گروه فعلی (ابتدا آزمایش کنید)</string>
<string name="title_export_all">خروجی گرفتن کانفیگ های غیرسفارشی گروه فعلی در کلیپ‌بورد</string>
<string name="title_sub_setting">تنظیمات گروه‌ اشتراک</string>
<string name="sub_setting_remarks">ملاحظات</string>
<string name="sub_setting_url">نشانی اینترنتی اختیاری</string>
<string name="sub_setting_filter">Remarks regular filter</string>
@@ -236,9 +237,9 @@
<string name="sub_setting_pre_profile">Previous proxy remarks</string>
<string name="sub_setting_next_profile">Next proxy remarks</string>
<string name="sub_setting_pre_profile_tip">The remarks exists and is unique</string>
<string name="title_sub_update">به‌روزرسانی اشتراک</string>
<string name="title_ping_all_server">Tcping همه کانفیگ</string>
<string name="title_real_ping_all_server">تاخیر واقعی همه کانفیگ</string>
<string name="title_sub_update">به‌روزرسانی گروه فعلی اشتراک</string>
<string name="title_ping_all_server">Tcping کانفیگ های گروه فعلی</string>
<string name="title_real_ping_all_server">تاخیر واقعی کانفیگ های گروه فعلی</string>
<string name="title_user_asset_setting">فایل‌های دارایی جغرافیا</string>
<string name="title_sort_by_test_results">مرتب‌سازی بر اساس نتایج آزمایش</string>
<string name="title_filter_config">فیلتر کردن کانفیگ‌ها</string>

View File

@@ -97,6 +97,7 @@
<string name="server_lab_content">Данные</string>
<string name="toast_none_data_clipboard">В буфере обмена нет данных</string>
<string name="toast_invalid_url">Неправильный URL</string>
<string name="toast_insecure_url_protocol">Please do not use the insecure HTTP protocol subscription address</string>
<string name="server_lab_need_inbound">Убедитесь, что входящий порт соответствует настройкам</string>
<string name="toast_malformed_josn">Профиль повреждён</string>
<string name="server_lab_request_host6">Узел (SNI) (необязательно)</string>

View File

@@ -97,6 +97,7 @@
<string name="server_lab_content">Nội dung</string>
<string name="toast_none_data_clipboard">Không có dữ liệu nào trong Clipboard!</string>
<string name="toast_invalid_url">URL không hợp lệ hoặc trống!</string>
<string name="toast_insecure_url_protocol">Please do not use the insecure HTTP protocol subscription address</string>
<string name="server_lab_need_inbound">Vui lòng đảm bảo cấu hình tùy chỉnh này không bị lỗi trước khi sử dụng!</string>
<string name="toast_malformed_josn">Cấu hình không hợp lệ!</string>
<string name="server_lab_request_host6">Host (SNI) (Không bắt buộc)</string>

View File

@@ -97,6 +97,7 @@
<string name="server_lab_content">内容</string>
<string name="toast_none_data_clipboard">剪贴板中没有数据</string>
<string name="toast_invalid_url">无效的网址</string>
<string name="toast_insecure_url_protocol">请不要使用不安全的HTTP协议订阅地址</string>
<string name="server_lab_need_inbound">确保inbounds port和设置中的一致</string>
<string name="toast_malformed_josn">配置格式错误</string>
<string name="server_lab_request_host6">Host(SNI)(可选)</string>

View File

@@ -97,6 +97,7 @@
<string name="server_lab_content">內容</string>
<string name="toast_none_data_clipboard">剪貼簿內無資料</string>
<string name="toast_invalid_url">URL 無效</string>
<string name="toast_insecure_url_protocol">請不要使用不安全的HTTP協定訂閱位址</string>
<string name="server_lab_need_inbound">​​確保 inbounds port 和設定中的一致</string>
<string name="toast_malformed_josn">設定格式不正確</string>
<string name="server_lab_request_host6">Host(SNI)(可選)</string>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TabLayoutTextStyle" parent="TextAppearance.Design.Tab">
<item name="textAllCaps">false</item>
</style>

View File

@@ -98,6 +98,7 @@
<string name="server_lab_content">Content</string>
<string name="toast_none_data_clipboard">There is no data in the clipboard</string>
<string name="toast_invalid_url">Invalid URL</string>
<string name="toast_insecure_url_protocol">Please do not use the insecure HTTP protocol subscription address</string>
<string name="server_lab_need_inbound">Ensure inbounds port is consistent with the settings</string>
<string name="toast_malformed_josn">Config malformed</string>
<string name="server_lab_request_host6">Host(SNI)(Optional)</string>

View File

@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.4.2" apply false
id("com.android.library") version "8.4.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.23" apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.android.kotlin) apply false
}

View File

@@ -9,7 +9,7 @@ flexbox = "3.0.0"
fragmentKtx = "1.8.4"
gson = "2.11.0"
junit = "4.13.2"
kotlinReflect = "2.0.20"
kotlinReflect = "2.0.21"
kotlinxCoroutinesCore = "1.9.0"
legacySupportV4 = "1.0.0"
lifecycleViewmodelKtx = "2.8.6"
@@ -25,6 +25,8 @@ rxpermissions = "0.12"
toastcompat = "1.1.0"
viewpager2 = "1.1.0"
workRuntimeKtx = "2.9.1"
androidGradlePlugin = "8.7.1"
androidKotlinPlugin = "2.0.21"
[libraries]
activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
@@ -60,4 +62,7 @@ viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpag
work-multiprocess = { module = "androidx.work:work-multiprocess", version.ref = "workRuntimeKtx" }
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
[plugins]
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
android-kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "androidKotlinPlugin" }