25 Commits

Author SHA1 Message Date
neuronori a73f8d044c merge: in-app apk updater (#12) 2026-06-29 15:32:23 +00:00
neuronori c7180b45db feat(update): download and install apk in-app instead of opening browser
closes #12
2026-06-29 15:30:29 +00:00
neuronori dff7deefe3 raise minsdk to android 11 (api 30)
closes #13
2026-06-29 15:27:21 +00:00
neuronori 688da9d0e1 raise minsdk to android 11 (api 30) 2026-06-29 15:26:18 +00:00
zarazaex69 29fd0d719d feat(ping): persist test result for selected server and update cache 2026-05-27 17:05:49 +03:00
zarazaex69 abeb58191e refactor(ui): improve connection testing, dialog styling, and status indicators
- major release, update app
2026-05-27 16:55:09 +03:00
zarazaex69 f55ac36b5a feat(ui): change dialog divider color from colorOutlineVariant to colorOutline 2026-05-27 14:03:55 +03:00
zarazaex69 63aec656d8 feat(ui): center dialog title, add divider, and flatten dialog corners 2026-05-27 13:55:45 +03:00
zarazaex69 2062d56fdb feat(country-filter): add flag emoji, country code, and circular background to dialog items 2026-05-27 13:43:40 +03:00
zarazaex69 0b02534724 upd git ignore 2026-05-24 17:08:30 +03:00
zarazaex69 a75deb1374 feat(ui): replace built-in multi-choice dialog with custom adapter 2026-05-24 16:31:09 +03:00
zarazaex69 fbb6da0c42 fix: prevent status reset during active connection test 2026-05-24 16:24:48 +03:00
zarazaex69 fc9f18858a refactor(ping): remove chunked batching in favor of single batch call 2026-05-24 16:22:31 +03:00
zarazaex69 9df74d3ad2 perf: increase ping chunk size from 5 to 20 2026-05-24 16:18:34 +03:00
zarazaex69 cbefd19abd refactor: process ping items in chunks of 5 2026-05-24 16:16:11 +03:00
zarazaex69 5dcba978a7 fix: prevent status reset during active operations and update RU string 2026-05-24 16:01:00 +03:00
zarazaex69 b26d1bde77 feat(country-filter): support multiple flag emojis per server 2026-05-24 15:51:44 +03:00
zarazaex69 2878456437 fix(ui): use secondary container color for FAB when disconnected 2026-05-24 15:42:10 +03:00
zarazaex69 d99677ca23 feat: replace MD3 round checkbox style with custom drawable 2026-05-24 15:25:25 +03:00
zarazaex69 4dff052c35 style(strings): improve split tunneling description wording 2026-05-24 15:22:53 +03:00
zarazaex69 4f1aca4928 style: apply round checkbox style to bypass list items 2026-05-24 15:22:26 +03:00
zarazaex69 35f2a1079b feat: add donate dialog shown on app startup 2026-05-24 15:19:43 +03:00
zarazaex69 0adcbfcaf5 chore: update dependencies and build tooling 2026-05-24 13:37:56 +03:00
zarazaex69 b81708ff5c fix(build): fallback to default paths when SDK/NDK dirs don't exist 2026-05-24 12:55:05 +03:00
zarazaex69 4ad24ec2dc build: update default ANDROID_HOME path to $HOME/android-sdk 2026-05-24 12:54:12 +03:00
35 changed files with 1399 additions and 454 deletions
+9
View File
@@ -66,3 +66,12 @@ Thumbs.db
add_subscription_mmkv.py add_subscription_mmkv.py
.gitignore .gitignore
material-design-icons material-design-icons
.kiro/agents/kirograph.json
.kiro/hooks/kirograph-compress-hint.kiro.hook
.kiro/settings/mcp.json
.kiro/hooks/kirograph-sync-if-dirty.kiro.hook
.kiro/steering/kirograph.md
.kirograph/config.json
.kirograph/kirograph.db
.kirograph/.session-id
.kirograph/token-savings.jsonl
+14 -12
View File
@@ -3,13 +3,13 @@ module github.com/2dust/AndroidLibXrayLite
go 1.26 go 1.26
require ( require (
github.com/xtls/xray-core v1.260327.0 github.com/xtls/xray-core v1.260327.1-0.20260509173629-1bdb488c9ec0
golang.org/x/mobile v0.0.0-20260312152759-81488f6aeb60 golang.org/x/mobile v0.0.0-20260520154334-0e4426e1883d
) )
require ( require (
github.com/andybalholm/brotli v1.2.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22 // indirect github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 // indirect
github.com/cloudflare/circl v1.6.3 // indirect github.com/cloudflare/circl v1.6.3 // indirect
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
@@ -19,9 +19,10 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/miekg/dns v1.1.72 // indirect github.com/miekg/dns v1.1.72 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pires/go-proxyproto v0.11.0 // indirect github.com/pires/go-proxyproto v0.12.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af // indirect github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagernet/sing v0.7.13 // indirect github.com/sagernet/sing v0.7.13 // indirect
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
@@ -30,19 +31,20 @@ require (
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f // indirect github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f // indirect
go.uber.org/mock v0.6.0 // indirect go.uber.org/mock v0.6.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.51.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.34.0 // indirect golang.org/x/mod v0.36.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.54.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.44.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.14.0 // indirect golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.43.0 // indirect golang.org/x/tools v0.45.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect golang.zx2c4.com/wireguard/windows v1.0.1 // indirect
google.golang.org/grpc v1.79.3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.81.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
+40 -36
View File
@@ -1,7 +1,7 @@
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22 h1:00ziBGnLWQEcR9LThDwvxOznJJquJ9bYUdmBFnawLMU= github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkdYbw5JPGwBqIHs2J8tiEP7Py9lPqkN2I=
github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA= github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
@@ -40,14 +40,16 @@ github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4= github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs= github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagernet/sing v0.7.13 h1:XNYgd8e3cxMULs/LLJspdn/deHrnPWyrrglNHeCUAYM= github.com/sagernet/sing v0.7.13 h1:XNYgd8e3cxMULs/LLJspdn/deHrnPWyrrglNHeCUAYM=
@@ -62,58 +64,60 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8= github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI= github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/xtls/xray-core v1.260327.0 h1:g4TzxMwyPrxslZh6uD+FiG3lXKTrnNO+b4ky2OhogHE= github.com/xtls/xray-core v1.260327.1-0.20260509173629-1bdb488c9ec0 h1:ft6HiTHelF1z9i3zZVPG9Q1LcLcvEB5jzBxky/+wljk=
github.com/xtls/xray-core v1.260327.0/go.mod h1:OXMlhBloFry8mw0KwWLWLd3RQyXJzEYsCGlgsX36h60= github.com/xtls/xray-core v1.260327.1-0.20260509173629-1bdb488c9ec0/go.mod h1:LWadz6mFBKGPHAe0KscKjugHjmQeyDt5Na6J86ol/hY=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mobile v0.0.0-20260312152759-81488f6aeb60 h1:MOzyaj0wu2xneBkzkg9LHNYjDBB4W5vP043A2SYQRPA= golang.org/x/mobile v0.0.0-20260520154334-0e4426e1883d h1:pWrEKZvKeqE2xPrylgBjgCyJSpPPt3L2WG2DmA+Xccg=
golang.org/x/mobile v0.0.0-20260312152759-81488f6aeb60/go.mod h1:th6VJvzjMbrYF8SduQY5rpD0HG0GleGxjadkqSxFs3k= golang.org/x/mobile v0.0.0-20260520154334-0e4426e1883d/go.mod h1:ltIbhcRzKgwHa4ZxKJeiv0nyzcXUUYCqMyO0Y+vPmXw=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+1 -1
View File
@@ -8,7 +8,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "xyz.zarazaex.olc" applicationId = "xyz.zarazaex.olc"
minSdk = 24 minSdk = 30
targetSdk = 36 targetSdk = 36
val envVersionName = System.getenv("VERSION_NAME") val envVersionName = System.getenv("VERSION_NAME")
+1
View File
@@ -41,6 +41,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application <application
android:name=".AngApplication" android:name=".AngApplication"
@@ -78,6 +78,10 @@ object AppConfig {
const val PREF_DYNAMIC_COLORS = "pref_dynamic_colors" const val PREF_DYNAMIC_COLORS = "pref_dynamic_colors"
const val PREF_SUBSCRIPTIONS_BOTTOM = "pref_subscriptions_bottom" const val PREF_SUBSCRIPTIONS_BOTTOM = "pref_subscriptions_bottom"
/** Donate dialog flags. */
const val PREF_DONATE_DIALOG_DISMISSED = "pref_donate_dialog_dismissed"
const val PREF_DONATE_DIALOG_POSTPONE_UNTIL = "pref_donate_dialog_postpone_until"
/** Cache keys. */ /** Cache keys. */
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id" const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
@@ -16,7 +16,11 @@ object CountryDetector {
// ── Emoji flag → ISO 2-letter country code ──────────────────────────────── // ── Emoji flag → ISO 2-letter country code ────────────────────────────────
/** Extract first flag emoji found in [text] and return its ISO country code (e.g. "RU"). */ /** Extract first flag emoji found in [text] and return its ISO country code (e.g. "RU"). */
fun extractFlagCode(text: String): String? { fun extractFlagCode(text: String): String? = extractAllFlagCodes(text).firstOrNull()
/** Extract all flag emojis found in [text] and return their ISO country codes. */
fun extractAllFlagCodes(text: String): List<String> {
val result = mutableListOf<String>()
val codePoints = text.codePoints().toArray() val codePoints = text.codePoints().toArray()
var i = 0 var i = 0
while (i < codePoints.size - 1) { while (i < codePoints.size - 1) {
@@ -25,11 +29,13 @@ object CountryDetector {
if (cp1 in 0x1F1E6..0x1F1FF && cp2 in 0x1F1E6..0x1F1FF) { if (cp1 in 0x1F1E6..0x1F1FF && cp2 in 0x1F1E6..0x1F1FF) {
val c1 = ('A'.code + (cp1 - 0x1F1E6)).toChar() val c1 = ('A'.code + (cp1 - 0x1F1E6)).toChar()
val c2 = ('A'.code + (cp2 - 0x1F1E6)).toChar() val c2 = ('A'.code + (cp2 - 0x1F1E6)).toChar()
return "$c1$c2" result.add("$c1$c2")
i += 2
continue
} }
i++ i++
} }
return null return result
} }
/** Get best country code for a server (emoji first, then cache). */ /** Get best country code for a server (emoji first, then cache). */
@@ -41,6 +47,16 @@ object CountryDetector {
return UNKNOWN return UNKNOWN
} }
/** Get all country codes for a server (all emojis, then cache fallback). */
fun getCountryCodes(remarks: String, serverIp: String?): List<String> {
val flags = extractAllFlagCodes(remarks)
if (flags.isNotEmpty()) return flags
if (!serverIp.isNullOrBlank() && !isPrivateIp(serverIp)) {
MmkvManager.getCountryCache(serverIp)?.let { return listOf(it) }
}
return listOf(UNKNOWN)
}
// ── Flag emoji rendering ────────────────────────────────────────────────── // ── Flag emoji rendering ──────────────────────────────────────────────────
/** ISO code → flag emoji string */ /** ISO code → flag emoji string */
@@ -70,14 +70,24 @@ object UpdateCheckerManager {
} }
suspend fun downloadApk(context: Context, downloadUrl: String): File? = withContext(Dispatchers.IO) { suspend fun downloadApk(context: Context, downloadUrl: String): File? = withContext(Dispatchers.IO) {
try { // Try a direct connection first, then fall back to the local proxy
val httpPort = SettingsManager.getHttpPort() // (mirrors checkForUpdate). The proxy only listens while the VPN is up,
val connection = HttpUtil.createProxyConnection(downloadUrl, httpPort, 10000, 10000, true) // so a direct attempt is what makes updating work when disconnected.
?: throw IllegalStateException("Failed to create connection") downloadApkVia(context, downloadUrl, 0)
?: downloadApkVia(context, downloadUrl, SettingsManager.getHttpPort())
}
try { private fun downloadApkVia(context: Context, downloadUrl: String, httpPort: Int): File? {
val connection = HttpUtil.createProxyConnection(downloadUrl, httpPort, 10000, 10000, true)
?: return null
return try {
val code = connection.responseCode
if (code !in 200..299) {
Log.e(AppConfig.TAG, "APK download failed, http $code (port=$httpPort)")
return null
}
val apkFile = File(context.cacheDir, "update.apk") val apkFile = File(context.cacheDir, "update.apk")
Log.i(AppConfig.TAG, "Downloading APK to: ${apkFile.absolutePath}") Log.i(AppConfig.TAG, "Downloading APK to: ${apkFile.absolutePath} (port=$httpPort)")
FileOutputStream(apkFile).use { outputStream -> FileOutputStream(apkFile).use { outputStream ->
connection.inputStream.use { inputStream -> connection.inputStream.use { inputStream ->
@@ -85,10 +95,10 @@ object UpdateCheckerManager {
} }
} }
Log.i(AppConfig.TAG, "APK download completed") Log.i(AppConfig.TAG, "APK download completed")
return@withContext apkFile apkFile
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to download APK: ${e.message}") Log.e(AppConfig.TAG, "Failed to download APK (port=$httpPort): ${e.message}")
return@withContext null null
} finally { } finally {
try { try {
connection.disconnect() connection.disconnect()
@@ -96,10 +106,6 @@ object UpdateCheckerManager {
Log.e(AppConfig.TAG, "Error closing connection: ${e.message}") Log.e(AppConfig.TAG, "Error closing connection: ${e.message}")
} }
} }
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to initiate download: ${e.message}")
return@withContext null
}
} }
private fun compareVersions(version1: String, version2: String): Int { private fun compareVersions(version1: String, version2: String): Int {
@@ -305,17 +305,25 @@ object V2RayServiceManager {
var time = -1L var time = -1L
var errorStr = "" var errorStr = ""
val urls = listOf(
SettingsManager.getDelayTestUrl(),
SettingsManager.getDelayTestUrl(true)
)
for (url in urls) {
if (time >= 0) break
try { try {
time = coreController.measureDelay(SettingsManager.getDelayTestUrl()) time = coreController.measureDelay(url)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e) Log.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e)
errorStr = e.message?.substringAfter("\":") ?: "empty message" errorStr = e.message?.substringAfter("\":") ?: "empty message"
} }
}
// One more retry after a brief pause to reduce false negatives
if (time == -1L) { if (time == -1L) {
kotlinx.coroutines.delay(500)
try { try {
time = coreController.measureDelay(SettingsManager.getDelayTestUrl(true)) time = coreController.measureDelay(urls[0])
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e)
errorStr = e.message?.substringAfter("\":") ?: "empty message" errorStr = e.message?.substringAfter("\":") ?: "empty message"
} }
} }
@@ -1,8 +1,11 @@
package xyz.zarazaex.olc.ui package xyz.zarazaex.olc.ui
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.util.Log import android.util.Log
import android.widget.TextView import android.widget.TextView
import androidx.core.net.toUri
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import xyz.zarazaex.olc.AppConfig import xyz.zarazaex.olc.AppConfig
@@ -63,6 +66,40 @@ class CheckUpdateActivity : BaseActivity() {
} }
} }
private fun downloadAndInstall(downloadUrl: String) {
if (!Utils.canInstallApk(this)) {
toast(R.string.update_install_permission_required)
try {
val intent = Intent(
Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
"package:$packageName".toUri()
)
startActivity(intent)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to open install-sources settings: ${e.message}")
}
return
}
toast(R.string.update_downloading)
showLoading()
lifecycleScope.launch {
try {
val apk = UpdateCheckerManager.downloadApk(this@CheckUpdateActivity, downloadUrl)
if (apk != null && Utils.installApk(this@CheckUpdateActivity, apk)) {
return@launch
}
toastError(R.string.update_download_failed)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to download/install update: ${e.message}")
toastError(e.message ?: getString(R.string.update_download_failed))
} finally {
hideLoading()
}
}
}
private fun showUpdateDialog(result: CheckUpdateResult) { private fun showUpdateDialog(result: CheckUpdateResult) {
val message = result.releaseNotes?.let { MarkdownUtil.parseBasic(it) } ?: "" val message = result.releaseNotes?.let { MarkdownUtil.parseBasic(it) } ?: ""
val titleStr = getString(R.string.update_new_version_found, result.latestVersion) val titleStr = getString(R.string.update_new_version_found, result.latestVersion)
@@ -70,9 +107,7 @@ class CheckUpdateActivity : BaseActivity() {
.setTitle(titleStr) .setTitle(titleStr)
.setMessage(message) .setMessage(message)
.setPositiveButton(R.string.update_now) { _, _ -> .setPositiveButton(R.string.update_now) { _, _ ->
result.downloadUrl?.let { result.downloadUrl?.let { downloadAndInstall(it) }
Utils.openUri(this, it)
}
} }
.create() .create()
dialog.show() dialog.show()
@@ -1,5 +1,7 @@
package xyz.zarazaex.olc.ui package xyz.zarazaex.olc.ui
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
@@ -199,10 +201,17 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
} }
checkForUpdatesOnStartup() checkForUpdatesOnStartup()
showDonateDialogIfNeeded()
} }
private fun setupViewModel() { private fun setupViewModel() {
mainViewModel.updateTestResultAction.observe(this) { setTestState(it) } mainViewModel.updateTestResultAction.observe(this) { result ->
setTestState(result)
if (result != null && mainViewModel.isRunning.value == true) {
val isSuccess = result.contains(Regex("\\d+\\s*(ms|мс|毫秒)"))
setStatusDot(if (isSuccess) DotState.CONNECTED else DotState.FAILURE)
}
}
mainViewModel.isTesting.observe(this) { testing -> mainViewModel.isTesting.observe(this) { testing ->
if (testing) { if (testing) {
@@ -217,13 +226,13 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
binding.btnSummaryLite.isEnabled = true binding.btnSummaryLite.isEnabled = true
binding.btnSummaryLite.alpha = 1.0f binding.btnSummaryLite.alpha = 1.0f
binding.btnSummaryLite.setIconResource(R.drawable.ic_stop_24dp) binding.btnSummaryLite.setIconResource(R.drawable.ic_stop_24dp)
binding.btnSummaryLite.backgroundTintList = ColorStateList.valueOf( animateButtonTint(binding.btnSummaryLite,
com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorPrimaryContainer, 0) com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorSecondaryContainer, 0)
) )
} else { } else {
setButtonsEnabled(true) setButtonsEnabled(true)
binding.btnSummaryLite.setIconResource(R.drawable.bolt_24) binding.btnSummaryLite.setIconResource(R.drawable.bolt_24)
binding.btnSummaryLite.backgroundTintList = ColorStateList.valueOf( animateButtonTint(binding.btnSummaryLite,
com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorSecondaryContainer, 0) com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorSecondaryContainer, 0)
) )
if (!isLiteTesting) { if (!isLiteTesting) {
@@ -382,7 +391,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
showStatus("Остановлено") showStatus("Остановлено")
setButtonsEnabled(true) setButtonsEnabled(true)
binding.btnSummaryLite.setIconResource(R.drawable.bolt_24) binding.btnSummaryLite.setIconResource(R.drawable.bolt_24)
binding.btnSummaryLite.backgroundTintList = ColorStateList.valueOf( animateButtonTint(binding.btnSummaryLite,
com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorSecondaryContainer, 0) com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorSecondaryContainer, 0)
) )
hideLoading() hideLoading()
@@ -489,9 +498,9 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
delay(1000) delay(1000)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "Error in restartV2Ray", e) Log.e(AppConfig.TAG, "Error in restartV2Ray", e)
applyRunningState(isLoading = false, isRunning = mainViewModel.isRunning.value == true)
} finally { } finally {
isFabOperationInProgress = false isFabOperationInProgress = false
applyRunningState(isLoading = false, isRunning = mainViewModel.isRunning.value == true)
} }
} }
} }
@@ -506,6 +515,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
private fun showStatus(message: String) { private fun showStatus(message: String) {
statusResetJob?.cancel() statusResetJob?.cancel()
binding.tvTestState.text = message binding.tvTestState.text = message
if (isFabOperationInProgress || mainViewModel.isTesting.value == true) return
statusResetJob = lifecycleScope.launch { statusResetJob = lifecycleScope.launch {
delay(3000) delay(3000)
val isRunning = mainViewModel.isRunning.value == true val isRunning = mainViewModel.isRunning.value == true
@@ -528,6 +538,16 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
return ColorStateList.valueOf(color) return ColorStateList.valueOf(color)
} }
private fun animateButtonTint(view: com.google.android.material.button.MaterialButton, toColor: Int, duration: Long = 300L) {
val from = view.backgroundTintList?.defaultColor ?: toColor
if (from == toColor) { view.backgroundTintList = ColorStateList.valueOf(toColor); return }
ValueAnimator.ofArgb(from, toColor).apply {
this.duration = duration
addUpdateListener { view.backgroundTintList = ColorStateList.valueOf(it.animatedValue as Int) }
start()
}
}
private fun applyRunningState(isLoading: Boolean, isRunning: Boolean) { private fun applyRunningState(isLoading: Boolean, isRunning: Boolean) {
val secContainer = ColorStateList.valueOf( val secContainer = ColorStateList.valueOf(
com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorSecondaryContainer, 0) com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorSecondaryContainer, 0)
@@ -547,28 +567,39 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
return return
} }
val onPrimary = ColorStateList.valueOf(
com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnPrimary, 0)
)
val onSecContainer = ColorStateList.valueOf(
com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSecondaryContainer, 0)
)
if (isRunning) { if (isRunning) {
setSecondaryButtonsEnabled(false) setSecondaryButtonsEnabled(false)
binding.fab.isEnabled = true binding.fab.isEnabled = true
binding.fab.alpha = 1.0f binding.fab.alpha = 1.0f
binding.fab.backgroundTintList = accentColor() binding.fab.backgroundTintList = accentColor()
binding.btnSummaryLite.backgroundTintList = secContainer binding.fab.iconTint = onPrimary
animateButtonTint(binding.btnSummaryLite, secContainer.defaultColor)
binding.fab.contentDescription = getString(R.string.action_stop_service) binding.fab.contentDescription = getString(R.string.action_stop_service)
setTestState(getString(R.string.connection_connected)) setTestState(getString(R.string.connection_connected))
binding.layoutTest.isFocusable = true binding.layoutTest.isFocusable = true
setStatusDot(DotState.CONNECTED) setStatusDot(DotState.CONNECTED)
} else { } else {
setButtonsEnabled(true) setButtonsEnabled(true)
binding.fab.backgroundTintList = accentColor() binding.fab.backgroundTintList = secContainer
binding.btnSummaryLite.backgroundTintList = secContainer binding.fab.iconTint = onSecContainer
animateButtonTint(binding.btnSummaryLite, secContainer.defaultColor)
binding.fab.contentDescription = getString(R.string.tasker_start_service) binding.fab.contentDescription = getString(R.string.tasker_start_service)
if (mainViewModel.isTesting.value != true && statusResetJob?.isActive != true) {
setTestState(getString(R.string.connection_not_connected)) setTestState(getString(R.string.connection_not_connected))
}
binding.layoutTest.isFocusable = false binding.layoutTest.isFocusable = false
setStatusDot(DotState.IDLE) setStatusDot(DotState.IDLE)
} }
} }
private enum class DotState { IDLE, CONNECTED, LOADING } private enum class DotState { IDLE, CONNECTED, LOADING, FAILURE }
private fun setStatusDot(state: DotState) { private fun setStatusDot(state: DotState) {
val dot = binding.statusDot val dot = binding.statusDot
@@ -576,6 +607,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
dot.alpha = 1f; dot.scaleX = 1f; dot.scaleY = 1f dot.alpha = 1f; dot.scaleX = 1f; dot.scaleY = 1f
dot.backgroundTintList = ColorStateList.valueOf(when (state) { dot.backgroundTintList = ColorStateList.valueOf(when (state) {
DotState.CONNECTED -> ContextCompat.getColor(this, R.color.status_connected) DotState.CONNECTED -> ContextCompat.getColor(this, R.color.status_connected)
DotState.FAILURE -> ContextCompat.getColor(this, R.color.status_failure)
DotState.LOADING -> com.google.android.material.color.MaterialColors.getColor(this, androidx.appcompat.R.attr.colorPrimary, 0) DotState.LOADING -> com.google.android.material.color.MaterialColors.getColor(this, androidx.appcompat.R.attr.colorPrimary, 0)
DotState.IDLE -> com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorOutline, 0) DotState.IDLE -> com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorOutline, 0)
}) })
@@ -851,6 +883,74 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
} }
} }
private fun showDonateDialogIfNeeded() {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_DONATE_DIALOG_DISMISSED)) return
val postponeUntil = MmkvManager.decodeSettingsLong(AppConfig.PREF_DONATE_DIALOG_POSTPONE_UNTIL, 0L)
if (System.currentTimeMillis() < postponeUntil) return
// Откладываем на следующий тик, чтобы Activity полностью отрисовался
binding.root.post { showDonateDialog() }
}
private fun showDonateDialog() {
val view = layoutInflater.inflate(R.layout.dialog_donate, null)
val tonValue = view.findViewById<TextView>(R.id.donate_ton_value)
val trcValue = view.findViewById<TextView>(R.id.donate_trc_value)
val btcValue = view.findViewById<TextView>(R.id.donate_btc_value)
val openCard = View.OnClickListener { openUrl(getString(R.string.donate_card_link_url)) }
view.findViewById<View>(R.id.donate_card_row).setOnClickListener(openCard)
view.findViewById<View>(R.id.donate_card_open).setOnClickListener(openCard)
view.findViewById<View>(R.id.donate_ton_copy).setOnClickListener {
copyToClipboard(tonValue.text.toString())
}
view.findViewById<View>(R.id.donate_trc_copy).setOnClickListener {
copyToClipboard(trcValue.text.toString())
}
view.findViewById<View>(R.id.donate_btc_copy).setOnClickListener {
copyToClipboard(btcValue.text.toString())
}
val titleStr = getString(R.string.donate_dialog_title)
val dialog = MaterialAlertDialogBuilder(this)
.setView(view)
.setNegativeButton(R.string.donate_btn_dont_show) { d, _ ->
MmkvManager.encodeSettings(AppConfig.PREF_DONATE_DIALOG_DISMISSED, true)
d.dismiss()
}
.setCancelable(true)
.create()
// Closing (X / outside / back) postpones for 24h
val postpone = {
val postponeUntil = System.currentTimeMillis() + 24L * 60 * 60 * 1000
MmkvManager.encodeSettings(AppConfig.PREF_DONATE_DIALOG_POSTPONE_UNTIL, postponeUntil)
}
dialog.setOnCancelListener { postpone() }
dialog.setCustomTitle(buildDialogTitleWithClose(titleStr) {
postpone()
dialog.dismiss()
})
dialog.show()
}
private fun copyToClipboard(text: String) {
val clipboard = getSystemService(android.content.Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
clipboard.setPrimaryClip(android.content.ClipData.newPlainText("addr", text))
toast(R.string.donate_toast_copied)
}
private fun openUrl(url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} catch (e: Exception) {
Log.w("MainActivity", "Failed to open url: $url", e)
}
}
private fun delAllConfig() { private fun delAllConfig() {
MaterialAlertDialogBuilder(this).setMessage(R.string.del_config_comfirm) MaterialAlertDialogBuilder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
@@ -1013,16 +1113,36 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
} }
val codes = allCountriesMap.keys.toTypedArray() val codes = allCountriesMap.keys.toTypedArray()
val labels = allCountriesMap.values.toTypedArray()
// currentFilter stores excluded set (empty = show all) // currentFilter stores excluded set (empty = show all)
val checked = BooleanArray(codes.size) { codes[it] in currentFilter } val checked = BooleanArray(codes.size) { codes[it] in currentFilter }
val dialog = MaterialAlertDialogBuilder(this@MainActivity) val adapter = object : android.widget.BaseAdapter() {
.setTitle("Исключить страны") override fun getCount() = codes.size
.setMultiChoiceItems(labels, checked) { _, which, isChecked -> override fun getItem(pos: Int) = codes[pos]
checked[which] = isChecked override fun getItemId(pos: Int) = pos.toLong()
override fun getView(pos: Int, convertView: android.view.View?, parent: android.view.ViewGroup): android.view.View {
val view = convertView ?: layoutInflater.inflate(R.layout.item_dialog_country, parent, false)
val code = codes[pos]
val isUnknown = code == CountryDetector.UNKNOWN
view.findViewById<android.widget.TextView>(R.id.flag).text =
if (isUnknown) "🌐" else CountryDetector.codeToFlag(code)
view.findViewById<android.widget.TextView>(R.id.text).text =
if (isUnknown) "Неизвестно" else CountryDetector.codeToName(code)
view.findViewById<android.widget.TextView>(R.id.code).text =
if (isUnknown) "" else code
val cb = view.findViewById<com.google.android.material.checkbox.MaterialCheckBox>(R.id.check_box)
cb.isChecked = checked[pos]
view.setOnClickListener {
checked[pos] = !checked[pos]
cb.isChecked = checked[pos]
} }
return view
}
}
val dialog = MaterialAlertDialogBuilder(this@MainActivity)
.setAdapter(adapter, null)
.setPositiveButton("Применить") { _, _ -> .setPositiveButton("Применить") { _, _ ->
val excluded = codes.filterIndexed { i, _ -> checked[i] }.toSet() val excluded = codes.filterIndexed { i, _ -> checked[i] }.toSet()
mainViewModel.applyCountryFilter(excluded) mainViewModel.applyCountryFilter(excluded)
@@ -1035,8 +1155,8 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
showStatus("Показаны все страны") showStatus("Показаны все страны")
} }
.create() .create()
dialog.show()
dialog.setCustomTitle(buildDialogTitleWithClose("Исключить страны") { dialog.dismiss() }) dialog.setCustomTitle(buildDialogTitleWithClose("Исключить страны") { dialog.dismiss() })
dialog.show()
} }
} }
} }
@@ -1062,7 +1182,6 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
val message = result.releaseNotes?.let { xyz.zarazaex.olc.util.MarkdownUtil.parseBasic(it) } ?: "" val message = result.releaseNotes?.let { xyz.zarazaex.olc.util.MarkdownUtil.parseBasic(it) } ?: ""
val titleStr = getString(R.string.update_new_version_found, result.latestVersion) val titleStr = getString(R.string.update_new_version_found, result.latestVersion)
val dialog = MaterialAlertDialogBuilder(this) val dialog = MaterialAlertDialogBuilder(this)
.setTitle(titleStr)
.setMessage(message) .setMessage(message)
.setPositiveButton(R.string.update_now) { _, _ -> .setPositiveButton(R.string.update_now) { _, _ ->
result.downloadUrl?.let { result.downloadUrl?.let {
@@ -1070,8 +1189,8 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
} }
} }
.create() .create()
dialog.show()
dialog.setCustomTitle(buildDialogTitleWithClose(titleStr) { dialog.dismiss() }) dialog.setCustomTitle(buildDialogTitleWithClose(titleStr) { dialog.dismiss() })
dialog.show()
} }
private fun buildDialogTitleWithClose(title: String, onClose: () -> Unit): View { private fun buildDialogTitleWithClose(title: String, onClose: () -> Unit): View {
@@ -166,10 +166,14 @@ class MainRecyclerAdapter(
if (addressText.isEmpty()) View.GONE else View.VISIBLE if (addressText.isEmpty()) View.GONE else View.VISIBLE
// TestResult // TestResult
val aff = MmkvManager.decodeServerAffiliationInfo(guid) val delayMillis = data[position].testDelayMillis
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString().orEmpty() holder.itemMainBinding.tvTestResult.text = when {
delayMillis == 0L -> "—ms"
delayMillis < 0L -> "-ms"
else -> "${delayMillis}ms"
}
holder.itemMainBinding.tvTestResult.setTextColor( holder.itemMainBinding.tvTestResult.setTextColor(
getPingColor(context, aff?.testDelayMillis) getPingColor(context, delayMillis)
) )
(holder.itemMainBinding.tvTestResult.layoutParams as? ViewGroup.MarginLayoutParams)?.marginStart = (holder.itemMainBinding.tvTestResult.layoutParams as? ViewGroup.MarginLayoutParams)?.marginStart =
if (addressText.isEmpty()) 0 else 6.dpToPx(context) if (addressText.isEmpty()) 0 else 6.dpToPx(context)
@@ -264,9 +268,7 @@ class MainRecyclerAdapter(
private fun recomputePingRange() { private fun recomputePingRange() {
val delays = data.mapNotNull { item -> val delays = data.mapNotNull { item ->
MmkvManager.decodeServerAffiliationInfo(item.guid) item.testDelayMillis.takeIf { it > 0L }
?.testDelayMillis
?.takeIf { it > 0L }
} }
minReachablePing = delays.minOrNull() minReachablePing = delays.minOrNull()
maxReachablePing = delays.maxOrNull() maxReachablePing = delays.maxOrNull()
@@ -15,10 +15,12 @@ import android.util.Log
import android.util.Patterns import android.util.Patterns
import android.webkit.URLUtil import android.webkit.URLUtil
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.net.toUri import androidx.core.net.toUri
import xyz.zarazaex.olc.AppConfig import xyz.zarazaex.olc.AppConfig
import xyz.zarazaex.olc.AppConfig.LOOPBACK import xyz.zarazaex.olc.AppConfig.LOOPBACK
import xyz.zarazaex.olc.BuildConfig import xyz.zarazaex.olc.BuildConfig
import java.io.File
import java.io.IOException import java.io.IOException
import java.net.InetAddress import java.net.InetAddress
import java.net.ServerSocket import java.net.ServerSocket
@@ -291,6 +293,40 @@ object Utils {
} }
} }
/**
* Launch the system package installer for a downloaded APK file.
*
* @param context The context to use.
* @param apkFile The downloaded APK file (must live under the FileProvider paths).
* @return true if the installer intent was started, false otherwise.
*/
fun installApk(context: Context, apkFile: File): Boolean {
return try {
val uri = FileProvider.getUriForFile(
context,
"${BuildConfig.APPLICATION_ID}.cache",
apkFile
)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "application/vnd.android.package-archive")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
true
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to install APK", e)
false
}
}
/**
* Whether the app is allowed to install APKs from unknown sources.
*/
fun canInstallApk(context: Context): Boolean {
return context.packageManager.canRequestPackageInstalls()
}
/** /**
* Generate a UUID. * Generate a UUID.
* *
@@ -185,8 +185,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val profile = MmkvManager.decodeServerConfig(guid) ?: continue val profile = MmkvManager.decodeServerConfig(guid) ?: continue
if (activeCountryFilter.isNotEmpty()) { if (activeCountryFilter.isNotEmpty()) {
val code = CountryDetector.getCountryCode(profile.remarks, profile.server) val codes = CountryDetector.getCountryCodes(profile.remarks, profile.server)
if (code in activeCountryFilter) continue if (codes.all { it in activeCountryFilter }) continue
} }
val delay = MmkvManager.decodeServerAffiliationInfo(guid)?.testDelayMillis ?: 0L val delay = MmkvManager.decodeServerAffiliationInfo(guid)?.testDelayMillis ?: 0L
@@ -260,13 +260,15 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
var hasUnknown = false var hasUnknown = false
for (guid in MmkvManager.decodeAllServerList()) { for (guid in MmkvManager.decodeAllServerList()) {
val profile = MmkvManager.decodeServerConfig(guid) ?: continue val profile = MmkvManager.decodeServerConfig(guid) ?: continue
val code = CountryDetector.getCountryCode(profile.remarks, profile.server) val codes = CountryDetector.getCountryCodes(profile.remarks, profile.server)
for (code in codes) {
if (code == CountryDetector.UNKNOWN) { if (code == CountryDetector.UNKNOWN) {
hasUnknown = true hasUnknown = true
} else { } else {
result[code] = "${CountryDetector.codeToFlag(code)} ${CountryDetector.codeToName(code)}" result[code] = "${CountryDetector.codeToFlag(code)} ${CountryDetector.codeToName(code)}"
} }
} }
}
if (hasUnknown) { if (hasUnknown) {
result[CountryDetector.UNKNOWN] = "🌐 Неизвестно" result[CountryDetector.UNKNOWN] = "🌐 Неизвестно"
} }
@@ -900,7 +902,17 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
} }
AppConfig.MSG_MEASURE_DELAY_SUCCESS -> { AppConfig.MSG_MEASURE_DELAY_SUCCESS -> {
updateTestResultAction.value = intent.getStringExtra("content") val content = intent.getStringExtra("content")
updateTestResultAction.value = content
// Save ping for selected server so it shows in the list
val guid = MmkvManager.getSelectServer()
if (!guid.isNullOrEmpty() && content != null) {
val ms = Regex("\\d+").find(content)?.value?.toLongOrNull()
if (ms != null && ms > 0) {
MmkvManager.encodeServerTestDelayMillis(guid, ms)
refreshPingInCache(listOf(guid))
}
}
} }
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> { AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="?attr/colorSurfaceContainerHighest" />
</shape>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="?attr/colorPrimary" />
<size
android:width="20dp"
android:height="20dp" />
</shape>
</item>
<item
android:drawable="@drawable/ic_check_white"
android:width="14dp"
android:height="14dp"
android:gravity="center" />
</layer-list>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/checkbox_round_checked" android:state_checked="true" />
<item android:drawable="@drawable/checkbox_round_unchecked" />
</selector>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="2dp"
android:color="?attr/colorOnSurfaceVariant" />
<size
android:width="20dp"
android:height="20dp" />
</shape>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/md_theme_onPrimary"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z" />
</vector>
@@ -144,9 +144,9 @@
android:contentDescription="@string/tasker_start_service" android:contentDescription="@string/tasker_start_service"
app:icon="@drawable/shield_24" app:icon="@drawable/shield_24"
app:iconSize="26dp" app:iconSize="26dp"
app:backgroundTint="?attr/colorPrimary" app:backgroundTint="?attr/colorSecondaryContainer"
app:iconTint="?attr/colorOnPrimary" app:iconTint="?attr/colorOnSecondaryContainer"
app:rippleColor="?attr/colorOnPrimary" app:rippleColor="?attr/colorOnSecondaryContainer"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" /> app:layout_constraintBottom_toBottomOf="parent" />
@@ -0,0 +1,247 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingTop="4dp"
android:paddingBottom="8dp">
<!-- Карта / СБП -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/donate_card_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:clickable="true"
android:focusable="true"
app:cardBackgroundColor="?attr/colorSurfaceContainerHigh"
app:cardCornerRadius="14dp"
app:cardElevation="0dp"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="14dp"
android:paddingEnd="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/donate_label_card"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
android:textColor="?attr/colorOnSurface" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/donate_card_url"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:typeface="monospace" />
</LinearLayout>
<ImageButton
android:id="@+id/donate_card_open"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/donate_open_link"
android:padding="10dp"
android:scaleType="centerInside"
android:src="@drawable/ic_share_24dp"
app:tint="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- TON -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardBackgroundColor="?attr/colorSurfaceContainerHigh"
app:cardCornerRadius="14dp"
app:cardElevation="0dp"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="14dp"
android:paddingEnd="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/donate_label_ton"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
android:textColor="?attr/colorOnSurface" />
<TextView
android:id="@+id/donate_ton_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="middle"
android:maxLines="1"
android:text="@string/donate_addr_ton"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:typeface="monospace" />
</LinearLayout>
<ImageButton
android:id="@+id/donate_ton_copy"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/donate_copy"
android:padding="10dp"
android:scaleType="centerInside"
android:src="@drawable/ic_copy"
app:tint="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- TRC20 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardBackgroundColor="?attr/colorSurfaceContainerHigh"
app:cardCornerRadius="14dp"
app:cardElevation="0dp"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="14dp"
android:paddingEnd="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/donate_label_trc20"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
android:textColor="?attr/colorOnSurface" />
<TextView
android:id="@+id/donate_trc_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="middle"
android:maxLines="1"
android:text="@string/donate_addr_trc20"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:typeface="monospace" />
</LinearLayout>
<ImageButton
android:id="@+id/donate_trc_copy"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/donate_copy"
android:padding="10dp"
android:scaleType="centerInside"
android:src="@drawable/ic_copy"
app:tint="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- BTC -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="?attr/colorSurfaceContainerHigh"
app:cardCornerRadius="14dp"
app:cardElevation="0dp"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="14dp"
android:paddingEnd="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/donate_label_btc"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
android:textColor="?attr/colorOnSurface" />
<TextView
android:id="@+id/donate_btc_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="middle"
android:maxLines="1"
android:text="@string/donate_addr_btc"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:typeface="monospace" />
</LinearLayout>
<ImageButton
android:id="@+id/donate_btc_copy"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/donate_copy"
android:padding="10dp"
android:scaleType="centerInside"
android:src="@drawable/ic_copy"
app:tint="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
@@ -1,30 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="24dp" android:paddingStart="24dp"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingTop="20dp" android:paddingTop="16dp"
android:paddingBottom="12dp"> android:paddingBottom="8dp">
<TextView <TextView
android:id="@+id/dialog_title_text" android:id="@+id/dialog_title_text"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_gravity="center_vertical"
android:layout_toStartOf="@+id/dialog_close_btn" android:gravity="start"
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall" android:paddingEnd="32dp"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
android:textColor="?attr/colorOnSurface" /> android:textColor="?attr/colorOnSurface" />
<ImageButton <ImageButton
android:id="@+id/dialog_close_btn" android:id="@+id/dialog_close_btn"
android:layout_width="40dp" android:layout_width="36dp"
android:layout_height="40dp" android:layout_height="36dp"
android:layout_alignParentEnd="true" android:layout_gravity="end|center_vertical"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Закрыть" android:contentDescription="Закрыть"
android:src="@drawable/ic_close_24dp" android:src="@drawable/ic_close_24dp"
android:tint="?attr/colorOnSurfaceVariant" /> android:tint="?attr/colorOnSurfaceVariant" />
</FrameLayout>
</RelativeLayout>
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:minHeight="56dp"
android:paddingHorizontal="24dp"
android:paddingVertical="8dp">
<FrameLayout
android:layout_width="36dp"
android:layout_height="36dp"
android:background="@drawable/bg_country_flag_circle">
<TextView
android:id="@+id/flag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:includeFontPadding="false"
android:textSize="18sp" />
</FrameLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:layout_marginEnd="12dp"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorOnSurface" />
<TextView
android:id="@+id/code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="1"
android:textAllCaps="true"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:letterSpacing="0.08" />
</LinearLayout>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/check_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false" />
</LinearLayout>
@@ -34,6 +34,7 @@
android:id="@+id/check_box" android:id="@+id/check_box"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:button="@drawable/checkbox_round_selector"
android:clickable="false" android:clickable="false"
android:focusable="false" android:focusable="false"
android:padding="@dimen/padding_spacing_dp8" /> android:padding="@dimen/padding_spacing_dp8" />
@@ -4,6 +4,7 @@
<color name="colorPingGood">#86D993</color> <color name="colorPingGood">#86D993</color>
<color name="colorPingMedium">#F1C76A</color> <color name="colorPingMedium">#F1C76A</color>
<color name="status_connected">#66BB6A</color> <color name="status_connected">#66BB6A</color>
<color name="status_failure">#EF5350</color>
<color name="colorPingRed">#FFB4AB</color> <color name="colorPingRed">#FFB4AB</color>
<color name="color_fab_active">@color/md_theme_primary</color> <color name="color_fab_active">@color/md_theme_primary</color>
<color name="color_fab_inactive">@color/md_theme_secondaryContainer</color> <color name="color_fab_inactive">@color/md_theme_secondaryContainer</color>
+13 -1
View File
@@ -558,7 +558,7 @@
<string name="connection_test_fail">Интернет недоступен</string> <string name="connection_test_fail">Интернет недоступен</string>
<string name="connection_test_error_status_code">Код ошибки: #%d</string> <string name="connection_test_error_status_code">Код ошибки: #%d</string>
<string name="connection_connected">нажмите для проверки</string> <string name="connection_connected">нажмите для проверки</string>
<string name="connection_not_connected">Ожидаем действий</string> <string name="connection_not_connected">Не подключено</string>
<string name="connection_updating_profiles">Обновление профилей…</string> <string name="connection_updating_profiles">Обновление профилей…</string>
<string name="connection_runing_task_left">Проверено %s</string> <string name="connection_runing_task_left">Проверено %s</string>
@@ -678,4 +678,16 @@
<item>WebDAV</item> <item>WebDAV</item>
</string-array> </string-array>
<!-- Donate dialog -->
<string name="donate_dialog_title">Поддержите нас</string>
<string name="donate_label_card">Карта</string>
<string name="donate_label_ton">TON</string>
<string name="donate_label_trc20">TRC20</string>
<string name="donate_label_btc">BTC</string>
<string name="donate_copy">Копировать</string>
<string name="donate_open_link">Открыть</string>
<string name="donate_btn_dont_show">Не показывать</string>
<string name="donate_btn_postpone">Позже</string>
<string name="donate_toast_copied">Скопировано</string>
</resources> </resources>
@@ -4,6 +4,7 @@
<color name="colorPingGood">#2E7D32</color> <color name="colorPingGood">#2E7D32</color>
<color name="colorPingMedium">#9A6700</color> <color name="colorPingMedium">#9A6700</color>
<color name="status_connected">#4CAF50</color> <color name="status_connected">#4CAF50</color>
<color name="status_failure">#E53935</color>
<color name="colorPingRed">#BA1A1A</color> <color name="colorPingRed">#BA1A1A</color>
<color name="colorConfigType">#1565C0</color> <color name="colorConfigType">#1565C0</color>
<color name="colorWhite">#FFFFFF</color> <color name="colorWhite">#FFFFFF</color>
+348 -102
View File
@@ -1,22 +1,35 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<resources> <resources>
<string name="app_name" translatable="false">olcng</string> <string name="app_name" translatable="false">olcng</string>
<string name="app_widget_name">Switch</string> <string name="app_widget_name">Switch</string>
<string name="app_tile_name">Switch</string> <string name="app_tile_name">Switch</string>
<string name="app_tile_first_use">First use of this feature, please use the app to add server</string> <string
name="app_tile_first_use"
>First use of this feature, please use the app to add server</string>
<string name="navigation_drawer_open">Open navigation drawer</string> <string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">Data migration success!</string> <string name="migration_success">Data migration success!</string>
<string name="drawer_forked_text" translatable="false">forked from <a href="https://github.com/2dust/v2rayng">V2RayNG</a></string> <string name="drawer_forked_text" translatable="false">forked from <a
<string name="drawer_developed_text" translatable="false">developed by developers from <a href="https://t.me/openlibrecommunity">Olc</a></string> href="https://github.com/2dust/v2rayng"
>V2RayNG</a></string>
<string
name="drawer_developed_text"
translatable="false"
>developed by developers from <a
href="https://t.me/openlibrecommunity"
>Olc</a></string>
<string name="action_stop_service">Stop service</string> <string name="action_stop_service">Stop service</string>
<string name="migration_fail">Data migration failed!</string> <string name="migration_fail">Data migration failed!</string>
<string name="pull_down_to_refresh">Please pull down to refresh!</string> <string name="pull_down_to_refresh">Please pull down to refresh!</string>
<!-- Notifications --> <!-- Notifications -->
<string name="notification_action_stop_v2ray">Stop</string> <string name="notification_action_stop_v2ray">Stop</string>
<string name="toast_permission_denied">Unable to obtain the permission</string> <string
<string name="toast_permission_denied_notification">Unable to obtain the notification permission</string> name="toast_permission_denied"
>Unable to obtain the permission</string>
<string
name="toast_permission_denied_notification"
>Unable to obtain the notification permission</string>
<string name="notification_action_more">Click for more</string> <string name="notification_action_more">Click for more</string>
<string name="toast_services_start">Start Services</string> <string name="toast_services_start">Start Services</string>
<string name="toast_services_stop">Stop Services</string> <string name="toast_services_stop">Stop Services</string>
@@ -30,19 +43,31 @@
<string name="menu_item_edit_config">Edit config</string> <string name="menu_item_edit_config">Edit config</string>
<string name="menu_item_del_config">Delete config</string> <string name="menu_item_del_config">Delete config</string>
<string name="menu_item_import_config_qrcode">Import from QRcode</string> <string name="menu_item_import_config_qrcode">Import from QRcode</string>
<string name="menu_item_import_config_clipboard">Import from Clipboard</string> <string
name="menu_item_import_config_clipboard"
>Import from Clipboard</string>
<string name="menu_item_import_config_local">Import from locally</string> <string name="menu_item_import_config_local">Import from locally</string>
<string name="menu_item_import_config_policy_group">Add [Policy group]</string> <string
name="menu_item_import_config_policy_group"
>Add [Policy group]</string>
<string name="menu_item_import_config_manually_vmess">Add [VMess]</string> <string name="menu_item_import_config_manually_vmess">Add [VMess]</string>
<string name="menu_item_import_config_manually_vless">Add [VLESS]</string> <string name="menu_item_import_config_manually_vless">Add [VLESS]</string>
<string name="menu_item_import_config_manually_ss">Add [Shadowsocks]</string> <string
name="menu_item_import_config_manually_ss"
>Add [Shadowsocks]</string>
<string name="menu_item_import_config_manually_socks">Add [SOCKS]</string> <string name="menu_item_import_config_manually_socks">Add [SOCKS]</string>
<string name="menu_item_import_config_manually_http">Add [HTTP]</string> <string name="menu_item_import_config_manually_http">Add [HTTP]</string>
<string name="menu_item_import_config_manually_trojan">Add [Trojan]</string> <string name="menu_item_import_config_manually_trojan">Add [Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">Add [Wireguard]</string> <string
<string name="menu_item_import_config_manually_hysteria2">Add [Hysteria2]</string> name="menu_item_import_config_manually_wireguard"
>Add [Wireguard]</string>
<string
name="menu_item_import_config_manually_hysteria2"
>Add [Hysteria2]</string>
<string name="del_config_comfirm">Confirm delete ?</string> <string name="del_config_comfirm">Confirm delete ?</string>
<string name="del_invalid_config_comfirm">Please test before deleting! Confirm delete ?</string> <string
name="del_invalid_config_comfirm"
>Please test before deleting! Confirm delete ?</string>
<string name="server_lab_remarks">remarks</string> <string name="server_lab_remarks">remarks</string>
<string name="server_lab_address">address</string> <string name="server_lab_address">address</string>
<string name="server_lab_port">port</string> <string name="server_lab_port">port</string>
@@ -70,7 +95,10 @@
<string name="server_lab_path_kcp">kcp seed</string> <string name="server_lab_path_kcp">kcp seed</string>
<string name="server_lab_path_grpc">gRPC serviceName</string> <string name="server_lab_path_grpc">gRPC serviceName</string>
<string name="server_lab_stream_security">TLS</string> <string name="server_lab_stream_security">TLS</string>
<string name="server_lab_stream_fingerprint" translatable="false">Fingerprint</string> <string
name="server_lab_stream_fingerprint"
translatable="false"
>Fingerprint</string>
<string name="server_lab_stream_alpn">Alpn</string> <string name="server_lab_stream_alpn">Alpn</string>
<string name="server_lab_allow_insecure">allowInsecure</string> <string name="server_lab_allow_insecure">allowInsecure</string>
<string name="server_lab_sni">SNI</string> <string name="server_lab_sni">SNI</string>
@@ -88,8 +116,12 @@
<string name="server_lab_spider_x">SpiderX</string> <string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string> <string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">SecretKey</string> <string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved(Optional, separated by commas)</string> <string
<string name="server_lab_local_address">Local address (optional IPv4/IPv6, separated by commas)</string> name="server_lab_reserved"
>Reserved(Optional, separated by commas)</string>
<string
name="server_lab_local_address"
>Local address (optional IPv4/IPv6, separated by commas)</string>
<string name="server_lab_local_mtu">Mtu(optional, default 1420)</string> <string name="server_lab_local_mtu">Mtu(optional, default 1420)</string>
<string name="toast_success">Success</string> <string name="toast_success">Success</string>
<string name="toast_failure">Failure</string> <string name="toast_failure">Failure</string>
@@ -97,31 +129,53 @@
<string name="toast_incorrect_protocol">Incorrect protocol</string> <string name="toast_incorrect_protocol">Incorrect protocol</string>
<string name="toast_decoding_failed">Decoding failed</string> <string name="toast_decoding_failed">Decoding failed</string>
<string name="title_file_chooser">Select a config</string> <string name="title_file_chooser">Select a config</string>
<string name="toast_require_file_manager">Please install a File Manager.</string> <string
name="toast_require_file_manager"
>Please install a File Manager.</string>
<string name="server_customize_config">Customize config</string> <string name="server_customize_config">Customize config</string>
<string name="toast_config_file_invalid">Invalid config</string> <string name="toast_config_file_invalid">Invalid config</string>
<string name="server_lab_content">Content</string> <string name="server_lab_content">Content</string>
<string name="toast_none_data_clipboard">There is no data in the clipboard</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_invalid_url">Invalid URL</string>
<string name="toast_insecure_url_protocol">Please do not use the insecure HTTP protocol subscription address</string> <string
<string name="server_lab_need_inbound">Ensure inbounds port is consistent with the settings</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="toast_malformed_josn">Config malformed</string>
<string name="server_lab_request_host6">Host(SNI)(Optional)</string> <string name="server_lab_request_host6">Host(SNI)(Optional)</string>
<string name="toast_action_not_allowed">Action not allowed</string> <string name="toast_action_not_allowed">Action not allowed</string>
<string name="server_obfs_password">Obfs password</string> <string name="server_obfs_password">Obfs password</string>
<string name="server_lab_port_hop">Port Hopping(will override the port)</string> <string
name="server_lab_port_hop"
>Port Hopping(will override the port)</string>
<string name="server_lab_port_hop_interval">Port Hopping Interval</string> <string name="server_lab_port_hop_interval">Port Hopping Interval</string>
<string name="server_lab_bandwidth_down">Bandwidth down (Supported units: k/m/g/t)</string> <string
<string name="server_lab_bandwidth_up">Bandwidth up (Supported units: k/m/g/t)</string> name="server_lab_bandwidth_down"
>Bandwidth down (Supported units: k/m/g/t)</string>
<string
name="server_lab_bandwidth_up"
>Bandwidth up (Supported units: k/m/g/t)</string>
<string name="server_lab_xhttp_mode">XHTTP Mode</string> <string name="server_lab_xhttp_mode">XHTTP Mode</string>
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string> <string
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string> name="server_lab_xhttp_extra"
>XHTTP Extra raw JSON, format: { XHTTPObject }</string>
<string
name="server_lab_final_mask"
>finalMask raw JSON, format: { FinalMaskObject }</string>
<string name="server_lab_ech_config_list">EchConfigList</string> <string name="server_lab_ech_config_list">EchConfigList</string>
<string name="server_lab_ech_force_query">EchForceQuery</string> <string name="server_lab_ech_force_query">EchForceQuery</string>
<string name="server_lab_pinned_ca256">Certificate fingerprint (SHA-256)</string> <string
name="server_lab_pinned_ca256"
>Certificate fingerprint (SHA-256)</string>
<!-- UserAssetActivity --> <!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">File copy failed, please use File Manager</string> <string
name="toast_asset_copy_failed"
>File copy failed, please use File Manager</string>
<string name="menu_item_add_asset">Add asset</string> <string name="menu_item_add_asset">Add asset</string>
<string name="menu_item_add_file">Add files</string> <string name="menu_item_add_file">Add files</string>
<string name="menu_item_add_url">Add URL</string> <string name="menu_item_add_url">Add URL</string>
@@ -146,7 +200,9 @@
<string name="menu_item_import_proxy_app">Import from Clipboard</string> <string name="menu_item_import_proxy_app">Import from Clipboard</string>
<string name="per_app_proxy_settings">Раздельное туннелирование</string> <string name="per_app_proxy_settings">Раздельное туннелирование</string>
<string name="per_app_proxy_settings_enable">Включить</string> <string name="per_app_proxy_settings_enable">Включить</string>
<string name="split_tunneling_description">Выберите приложения, которые будут использовать VPN — остальные приложения, например банки, будут идти напрямую через ваш домашний интернет.\n\nВ режиме обхода всё зеркально — выбранные приложения не используют VPN.</string> <string
name="split_tunneling_description"
>Выберите приложения, которые будут использовать VPN. Остальные (например банки) пойдут напрямую через ваш домашний интернет.\n\nВ режиме обхода всё зеркально: выбранные приложения не используют VPN.</string>
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">Settings</string> <string name="title_settings">Settings</string>
@@ -154,24 +210,44 @@
<string name="title_core_settings">Core Settings</string> <string name="title_core_settings">Core Settings</string>
<string name="title_vpn_settings">VPN Settings</string> <string name="title_vpn_settings">VPN Settings</string>
<string name="title_pref_per_app_proxy">Per-app proxy</string> <string name="title_pref_per_app_proxy">Per-app proxy</string>
<string name="summary_pref_per_app_proxy">General: Checked apps use proxy, unchecked apps connect directly; \nBypass mode: checked apps connect directly, unchecked apps use proxy. \nThe option to automatically select proxy applications is in the menu</string> <string
name="summary_pref_per_app_proxy"
>General: Checked apps use proxy, unchecked apps connect directly; \nBypass mode: checked apps connect directly, unchecked apps use proxy. \nThe option to automatically select proxy applications is in the menu</string>
<string name="title_pref_is_booted">Auto connect at startup</string> <string name="title_pref_is_booted">Auto connect at startup</string>
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string> <string
name="summary_pref_is_booted"
>Automatically connects to the selected server at startup, which may be unsuccessful</string>
<string name="title_pref_auto_sort_after_test">Auto sort after testing</string> <string
<string name="summary_pref_auto_sort_after_test">Test results may not be accurate;</string> name="title_pref_auto_sort_after_test"
>Auto sort after testing</string>
<string
name="summary_pref_auto_sort_after_test"
>Test results may not be accurate;</string>
<string name="title_pref_show_copy_button">Show copy button</string> <string name="title_pref_show_copy_button">Show copy button</string>
<string name="summary_pref_show_copy_button">Show button to copy server configuration to clipboard</string> <string
name="summary_pref_show_copy_button"
>Show button to copy server configuration to clipboard</string>
<string name="title_pref_show_server_ip">Show server IP / host</string> <string name="title_pref_show_server_ip">Show server IP / host</string>
<string name="summary_pref_show_server_ip">Display the server IP address or host under the server name</string> <string
name="summary_pref_show_server_ip"
>Display the server IP address or host under the server name</string>
<string name="title_mux_settings">Mux Settings</string> <string name="title_mux_settings">Mux Settings</string>
<string name="title_pref_mux_enabled">Enable Mux</string> <string name="title_pref_mux_enabled">Enable Mux</string>
<string name="summary_pref_mux_enabled">Faster, but it may cause unstable connectivity\nCustomize how to handle TCP, UDP and QUIC below</string> <string
<string name="title_pref_mux_concurency">TCP connectionsrange -1 to 1024</string> name="summary_pref_mux_enabled"
<string name="title_pref_mux_xudp_concurency">XUDP connectionsrange -1 to 1024</string> >Faster, but it may cause unstable connectivity\nCustomize how to handle TCP, UDP and QUIC below</string>
<string name="title_pref_mux_xudp_quic">Handling of QUIC in mux tunnel</string> <string
name="title_pref_mux_concurency"
>TCP connectionsrange -1 to 1024</string>
<string
name="title_pref_mux_xudp_concurency"
>XUDP connectionsrange -1 to 1024</string>
<string
name="title_pref_mux_xudp_quic"
>Handling of QUIC in mux tunnel</string>
<string-array name="mux_xudp_quic_entries"> <string-array name="mux_xudp_quic_entries">
<item>reject</item> <item>reject</item>
<item>allow</item> <item>allow</item>
@@ -179,30 +255,46 @@
</string-array> </string-array>
<string name="title_pref_speed_enabled">Enable speed display</string> <string name="title_pref_speed_enabled">Enable speed display</string>
<string name="summary_pref_speed_enabled">Display current speed in the notification.\nNotification icon would change based on <string
name="summary_pref_speed_enabled"
>Display current speed in the notification.\nNotification icon would change based on
usage.</string> usage.</string>
<string name="title_pref_sniffing_enabled">Enable Sniffing</string> <string name="title_pref_sniffing_enabled">Enable Sniffing</string>
<string name="summary_pref_sniffing_enabled">Try sniff domain from the packet (default on)</string> <string
name="summary_pref_sniffing_enabled"
>Try sniff domain from the packet (default on)</string>
<string name="title_pref_route_only_enabled">Enable routeOnly</string> <string name="title_pref_route_only_enabled">Enable routeOnly</string>
<string name="summary_pref_route_only_enabled">Use the sniffed domain name for routing only, and keep the target address as the IP address.</string> <string
name="summary_pref_route_only_enabled"
>Use the sniffed domain name for routing only, and keep the target address as the IP address.</string>
<string name="title_pref_local_dns_enabled">Enable local DNS</string> <string name="title_pref_local_dns_enabled">Enable local DNS</string>
<string name="summary_pref_local_dns_enabled">DNS processed by cores DNS module (Recommended if you need routing bypassing LAN and mainland addresses)</string> <string
name="summary_pref_local_dns_enabled"
>DNS processed by cores DNS module (Recommended if you need routing bypassing LAN and mainland addresses)</string>
<string name="title_pref_fake_dns_enabled">Enable fake DNS</string> <string name="title_pref_fake_dns_enabled">Enable fake DNS</string>
<string name="summary_pref_fake_dns_enabled">Local DNS returns fake IP addresses (faster, but it may not work for some apps)</string> <string
name="summary_pref_fake_dns_enabled"
>Local DNS returns fake IP addresses (faster, but it may not work for some apps)</string>
<string name="title_pref_prefer_ipv6">Prefer IPv6</string> <string name="title_pref_prefer_ipv6">Prefer IPv6</string>
<string name="summary_pref_prefer_ipv6">Enable IPv6 routes and Prefer IPv6 addresses</string> <string
name="summary_pref_prefer_ipv6"
>Enable IPv6 routes and Prefer IPv6 addresses</string>
<string name="title_pref_remote_dns">Remote DNS (udp/tcp/https/quic)(Optional)</string> <string
name="title_pref_remote_dns"
>Remote DNS (udp/tcp/https/quic)(Optional)</string>
<string name="summary_pref_remote_dns">DNS</string> <string name="summary_pref_remote_dns">DNS</string>
<string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string> <string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string> <string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string> <string
name="title_pref_vpn_interface_address"
>VPN Interface Address</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string> <string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
@@ -210,21 +302,33 @@
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string> <string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">DNS hosts (Format: domain:address,…)</string> <string
name="title_pref_dns_hosts"
>DNS hosts (Format: domain:address,…)</string>
<string name="summary_pref_dns_hosts">domain:address,…</string> <string name="summary_pref_dns_hosts">domain:address,…</string>
<string name="title_pref_delay_test_url">True delay test url </string> <string name="title_pref_delay_test_url">True delay test url </string>
<string name="summary_pref_delay_test_url">Url</string> <string name="summary_pref_delay_test_url">Url</string>
<string name="title_pref_ip_api_url">Current connection info test url</string> <string
name="title_pref_ip_api_url"
>Current connection info test url</string>
<string name="summary_pref_ip_api_url">Url</string> <string name="summary_pref_ip_api_url">Url</string>
<string name="title_pref_proxy_sharing_enabled">Allow connections from the LAN</string> <string
<string name="summary_pref_proxy_sharing_enabled">Other devices can connect to proxy by your IP address through local proxy. Only enable in trusted networks to avoid unauthorized connections</string> name="title_pref_proxy_sharing_enabled"
<string name="toast_warning_pref_proxysharing_short">Allow connections from the LAN. Make sure you are in a trusted network</string> >Allow connections from the LAN</string>
<string
name="summary_pref_proxy_sharing_enabled"
>Other devices can connect to proxy by your IP address through local proxy. Only enable in trusted networks to avoid unauthorized connections</string>
<string
name="toast_warning_pref_proxysharing_short"
>Allow connections from the LAN. Make sure you are in a trusted network</string>
<string name="title_pref_allow_insecure">allowInsecure</string> <string name="title_pref_allow_insecure">allowInsecure</string>
<string name="summary_pref_allow_insecure">When TLS is selected, allow insecure connections by default</string> <string
name="summary_pref_allow_insecure"
>When TLS is selected, allow insecure connections by default</string>
<string name="title_pref_socks_port">Local proxy port</string> <string name="title_pref_socks_port">Local proxy port</string>
<string name="summary_pref_socks_port">Local proxy port</string> <string name="summary_pref_socks_port">Local proxy port</string>
@@ -233,23 +337,41 @@
<string name="summary_pref_local_dns_port">Local DNS port</string> <string name="summary_pref_local_dns_port">Local DNS port</string>
<string name="title_pref_confirm_remove">Delete config confirmation</string> <string name="title_pref_confirm_remove">Delete config confirmation</string>
<string name="summary_pref_confirm_remove">Whether deleting a config requires a second confirmation by the user</string> <string
name="summary_pref_confirm_remove"
>Whether deleting a config requires a second confirmation by the user</string>
<string name="title_pref_start_scan_immediate">Start scanning immediately</string> <string
<string name="summary_pref_start_scan_immediate">Open the camera to scan immediately at startup, otherwise you can choose to scan the code or select a photo in the toolbar</string> name="title_pref_start_scan_immediate"
>Start scanning immediately</string>
<string
name="summary_pref_start_scan_immediate"
>Open the camera to scan immediately at startup, otherwise you can choose to scan the code or select a photo in the toolbar</string>
<string name="title_pref_append_http_proxy">Append HTTP Proxy to VPN</string> <string
<string name="summary_pref_append_http_proxy">HTTP proxy will be used directly from (browser/ some supported apps), without going through the virtual NIC device (Android 10+)</string> name="title_pref_append_http_proxy"
>Append HTTP Proxy to VPN</string>
<string
name="summary_pref_append_http_proxy"
>HTTP proxy will be used directly from (browser/ some supported apps), without going through the virtual NIC device (Android 10+)</string>
<string name="title_pref_double_column_display">Enable double column display</string> <string
<string name="summary_pref_double_column_display">The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect.</string> name="title_pref_double_column_display"
>Enable double column display</string>
<string
name="summary_pref_double_column_display"
>The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect.</string>
<string name="title_pref_group_all_display">Enable show all groups</string> <string name="title_pref_group_all_display">Enable show all groups</string>
<string name="summary_pref_group_all_display">Add an extra "All Tabs" page</string> <string
name="summary_pref_group_all_display"
>Add an extra "All Tabs" page</string>
<!-- AboutActivity --> <!-- AboutActivity -->
<string name="title_pref_feedback">Feedback</string> <string name="title_pref_feedback">Feedback</string>
<string name="summary_pref_feedback">Feedback enhancements or bugs to GitHub</string> <string
name="summary_pref_feedback"
>Feedback enhancements or bugs to GitHub</string>
<string name="summary_pref_tg_group">Join Telegram Group</string> <string name="summary_pref_tg_group">Join Telegram Group</string>
<string name="toast_tg_app_not_found">Telegram app not found</string> <string name="toast_tg_app_not_found">Telegram app not found</string>
<string name="title_privacy_policy">Privacy policy</string> <string name="title_privacy_policy">Privacy policy</string>
@@ -261,25 +383,45 @@
<string name="title_pref_promotion">Promotion</string> <string name="title_pref_promotion">Promotion</string>
<string name="title_pref_auto_update_subscription">Automatic update subscriptions</string> <string
<string name="summary_pref_auto_update_subscription">Update your subscriptions automatically at set intervals in the background. Depending on the device, this feature may not always work</string> name="title_pref_auto_update_subscription"
<string name="title_pref_auto_update_interval">Auto Update Interval (Minutes, Min value 15)</string> >Automatic update subscriptions</string>
<string
name="summary_pref_auto_update_subscription"
>Update your subscriptions automatically at set intervals in the background. Depending on the device, this feature may not always work</string>
<string
name="title_pref_auto_update_interval"
>Auto Update Interval (Minutes, Min value 15)</string>
<string name="title_core_loglevel">Log Level</string> <string name="title_core_loglevel">Log Level</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string> <string
name="title_outbound_domain_resolve_method"
>Outbound domain pre-resolve method</string>
<string name="title_mode">Mode</string> <string name="title_mode">Mode</string>
<string name="title_mode_help">Click me for more help</string> <string name="title_mode_help">Click me for more help</string>
<string name="title_language">Language</string> <string name="title_language">Language</string>
<string name="title_ui_settings">UI settings</string> <string name="title_ui_settings">UI settings</string>
<string name="title_pref_ui_mode_night">UI mode settings</string> <string name="title_pref_ui_mode_night">UI mode settings</string>
<string name="title_pref_dynamic_colors">Dynamic colors (Material You)</string> <string
<string name="summary_pref_dynamic_colors">Use wallpaper-based colors (Android 12+). Requires app restart.</string> name="title_pref_dynamic_colors"
<string name="title_pref_subscriptions_bottom">Subscriptions panel at the bottom</string> >Dynamic colors (Material You)</string>
<string name="summary_pref_subscriptions_bottom">Move the subscription tabs below the server list</string> <string
name="summary_pref_dynamic_colors"
>Use wallpaper-based colors (Android 12+). Requires app restart.</string>
<string
name="title_pref_subscriptions_bottom"
>Subscriptions panel at the bottom</string>
<string
name="summary_pref_subscriptions_bottom"
>Move the subscription tabs below the server list</string>
<string name="title_pref_use_hev_tunnel">Enable Hev TUN Feature</string> <string name="title_pref_use_hev_tunnel">Enable Hev TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use xray-core.</string> <string
name="summary_pref_use_hev_tunnel"
>When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use xray-core.</string>
<string name="title_pref_hev_tunnel_loglevel">Hev Tun Log Level</string> <string name="title_pref_hev_tunnel_loglevel">Hev Tun Log Level</string>
<string name="title_pref_hev_tunnel_rw_timeout">Hev Tun read/write timeout (seconds) (tcp,udp default 300,60)</string> <string
name="title_pref_hev_tunnel_rw_timeout"
>Hev Tun read/write timeout (seconds) (tcp,udp default 300,60)</string>
<string name="restart_required">Restart required to apply changes</string> <string name="restart_required">Restart required to apply changes</string>
<string name="title_logcat">Logcat</string> <string name="title_logcat">Logcat</string>
@@ -298,9 +440,13 @@
<string name="sub_setting_enable">Enable update</string> <string name="sub_setting_enable">Enable update</string>
<string name="sub_auto_update">Enable automatic update</string> <string name="sub_auto_update">Enable automatic update</string>
<string name="sub_allow_insecure_url">Allow insecure HTTP address</string> <string name="sub_allow_insecure_url">Allow insecure HTTP address</string>
<string name="sub_setting_pre_profile">Previous proxy config remarks</string> <string
name="sub_setting_pre_profile"
>Previous proxy config remarks</string>
<string name="sub_setting_next_profile">Next proxy config remarks</string> <string name="sub_setting_next_profile">Next proxy config remarks</string>
<string name="sub_setting_pre_profile_tip">The config remarks exist and are unique</string> <string
name="sub_setting_pre_profile_tip"
>The config remarks exist and are unique</string>
<string name="title_sub_update">Update subscription</string> <string name="title_sub_update">Update subscription</string>
<string name="title_ping_all_server">Tcping config</string> <string name="title_ping_all_server">Tcping config</string>
<string name="title_real_ping_all_server">Real delay config</string> <string name="title_real_ping_all_server">Real delay config</string>
@@ -308,17 +454,29 @@
<string name="title_sort_by_test_results">Sorting by test results</string> <string name="title_sort_by_test_results">Sorting by test results</string>
<string name="title_filter_config">Filter config</string> <string name="title_filter_config">Filter config</string>
<string name="filter_config_all">All</string> <string name="filter_config_all">All</string>
<string name="title_del_duplicate_config_count">Delete %d duplicate configs</string> <string
name="title_del_duplicate_config_count"
>Delete %d duplicate configs</string>
<string name="title_del_config_count">Delete %d configs</string> <string name="title_del_config_count">Delete %d configs</string>
<string name="title_import_config_count">Import %d configs</string> <string name="title_import_config_count">Import %d configs</string>
<string name="title_export_config_count">Export %d configs</string> <string name="title_export_config_count">Export %d configs</string>
<string name="title_update_config_count">Update %d configs</string> <string name="title_update_config_count">Update %d configs</string>
<string name="title_updating">Updating…</string> <string name="title_updating">Updating…</string>
<string name="title_update_subscription_result">Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string> <string
<string name="title_update_subscription_no_subscription">No subscriptions</string> name="title_update_subscription_result"
<string name="toast_server_not_found_in_group">Selected server not found in current group</string> >Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string>
<string name="toast_fragment_not_available">Unable to locate current view</string> <string
<string name="title_locate_selected_config">Locate the selected config</string> name="title_update_subscription_no_subscription"
>No subscriptions</string>
<string
name="toast_server_not_found_in_group"
>Selected server not found in current group</string>
<string
name="toast_fragment_not_available"
>Unable to locate current view</string>
<string
name="title_locate_selected_config"
>Locate the selected config</string>
<string name="tasker_start_service">Start Service</string> <string name="tasker_start_service">Start Service</string>
<string name="tasker_setting_confirm">Confirm</string> <string name="tasker_setting_confirm">Confirm</string>
@@ -326,64 +484,120 @@
<!-- RoutingSettingActivity --> <!-- RoutingSettingActivity -->
<string name="routing_settings_domain_strategy">Domain strategy</string> <string name="routing_settings_domain_strategy">Domain strategy</string>
<string name="routing_settings_title">Routing Settings</string> <string name="routing_settings_title">Routing Settings</string>
<string name="routing_settings_tips">Separated by commas(,), choose domain or ip</string> <string
name="routing_settings_tips"
>Separated by commas(,), choose domain or ip</string>
<string name="routing_settings_save">Save</string> <string name="routing_settings_save">Save</string>
<string name="routing_settings_delete">Clear</string> <string name="routing_settings_delete">Clear</string>
<string name="routing_settings_rule_title">Routing Rule Settings</string> <string name="routing_settings_rule_title">Routing Rule Settings</string>
<string name="routing_settings_add_rule">Add rule</string> <string name="routing_settings_add_rule">Add rule</string>
<string name="routing_settings_import_predefined_rulesets">Import predefined rulesets</string> <string
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string> name="routing_settings_import_predefined_rulesets"
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string> >Import predefined rulesets</string>
<string name="routing_settings_import_rulesets_from_qrcode">Import ruleset from QRcode</string> <string
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string> name="routing_settings_import_rulesets_tip"
<string name="routing_settings_locked">Locked, keep this rule when import presets</string> >Existing rulesets will be deleted, are you sure to continue?</string>
<string
name="routing_settings_import_rulesets_from_clipboard"
>Import ruleset from clipboard</string>
<string
name="routing_settings_import_rulesets_from_qrcode"
>Import ruleset from QRcode</string>
<string
name="routing_settings_export_rulesets_to_clipboard"
>Export ruleset to clipboard</string>
<string
name="routing_settings_locked"
>Locked, keep this rule when import presets</string>
<string name="routing_settings_domain" translatable="false">domain</string> <string name="routing_settings_domain" translatable="false">domain</string>
<string name="routing_settings_ip" translatable="false">ip</string> <string name="routing_settings_ip" translatable="false">ip</string>
<string name="routing_settings_port" translatable="false">port</string> <string name="routing_settings_port" translatable="false">port</string>
<string name="routing_settings_protocol" translatable="false">protocol</string> <string
<string name="routing_settings_protocol_tip" translatable="false">[http,tls,bittorrent]</string> name="routing_settings_protocol"
<string name="routing_settings_network" translatable="false">network</string> translatable="false"
<string name="routing_settings_network_tip" translatable="false">[udp|tcp]</string> >protocol</string>
<string name="routing_settings_outbound_tag" translatable="false">outboundTag</string> <string
name="routing_settings_protocol_tip"
translatable="false"
>[http,tls,bittorrent]</string>
<string
name="routing_settings_network"
translatable="false"
>network</string>
<string
name="routing_settings_network_tip"
translatable="false"
>[udp|tcp]</string>
<string
name="routing_settings_outbound_tag"
translatable="false"
>outboundTag</string>
<string name="connection_test_pending">Check Connectivity</string> <string name="connection_test_pending">Check Connectivity</string>
<string name="connection_test_testing">Testing…</string> <string name="connection_test_testing">Testing…</string>
<string name="connection_test_testing_count">Тестирование %d серверов…</string> <string
<string name="connection_test_available">Success: Connection took %dms</string> name="connection_test_testing_count"
<string name="connection_test_error">Fail to detect internet connection: %s</string> >Тестирование %d серверов…</string>
<string
name="connection_test_available"
>Success: Connection took %dms</string>
<string
name="connection_test_error"
>Fail to detect internet connection: %s</string>
<string name="connection_test_fail">Internet Unavailable</string> <string name="connection_test_fail">Internet Unavailable</string>
<string name="connection_test_error_status_code">Error code: #%d</string> <string name="connection_test_error_status_code">Error code: #%d</string>
<string name="connection_connected">Connected, tap to check connection</string> <string
name="connection_connected"
>Connected, tap to check connection</string>
<string name="connection_not_connected">Готово к подключению</string> <string name="connection_not_connected">Готово к подключению</string>
<string name="connection_updating_profiles">Updating profiles…</string> <string name="connection_updating_profiles">Updating profiles…</string>
<string name="connection_runing_task_left">Проверено %s</string> <string name="connection_runing_task_left">Проверено %s</string>
<string name="import_subscription_success">Subscription imported Successfully</string> <string
<string name="import_subscription_failure">Import subscription failed</string> name="import_subscription_success"
>Subscription imported Successfully</string>
<string
name="import_subscription_failure"
>Import subscription failed</string>
<string name="title_fragment_settings">Fragment Settings</string> <string name="title_fragment_settings">Fragment Settings</string>
<string name="title_pref_fragment_packets">Fragment Packets</string> <string name="title_pref_fragment_packets">Fragment Packets</string>
<string name="title_pref_fragment_length">Fragment Length (min-max)</string> <string name="title_pref_fragment_length">Fragment Length (min-max)</string>
<string name="title_pref_fragment_interval">Fragment Interval (min-max)</string> <string
name="title_pref_fragment_interval"
>Fragment Interval (min-max)</string>
<string name="title_pref_fragment_enabled">Enable Fragment</string> <string name="title_pref_fragment_enabled">Enable Fragment</string>
<string name="update_check_for_update">Check for update</string> <string name="update_check_for_update">Check for update</string>
<string name="update_already_latest_version">Already on the latest version</string> <string
name="update_already_latest_version"
>Already on the latest version</string>
<string name="update_new_version_found">New version found: %s</string> <string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string> <string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string> <string name="update_check_pre_release">Check Pre-release</string>
<string name="update_checking_for_update">Checking for update…</string> <string name="update_checking_for_update">Checking for update…</string>
<string name="update_downloading">Downloading update…</string>
<string name="update_download_failed">Download failed</string>
<string name="update_install_permission_required">Allow installing unknown apps to update, then tap Update now again</string>
<string name="title_policy_group_type">Policy group type</string> <string name="title_policy_group_type">Policy group type</string>
<string name="title_policy_group_subscription_id">From subscription group</string> <string
<string name="title_policy_group_subscription_filter">Remarks regular filter</string> name="title_policy_group_subscription_id"
>From subscription group</string>
<string
name="title_policy_group_subscription_filter"
>Remarks regular filter</string>
<!-- BackupActivity --> <!-- BackupActivity -->
<string name="title_configuration_backup_restore">Backup &amp; Restore</string> <string
name="title_configuration_backup_restore"
>Backup &amp; Restore</string>
<string name="title_configuration_backup">Backup config</string> <string name="title_configuration_backup">Backup config</string>
<string name="title_configuration_restore">Restore config</string> <string name="title_configuration_restore">Restore config</string>
<string name="title_configuration_share">Share config</string> <string name="title_configuration_share">Share config</string>
<string name="title_webdav_config_setting">WebDAV Settings</string> <string name="title_webdav_config_setting">WebDAV Settings</string>
<string name="title_webdav_config_setting_unknown">Please configure WebDAV first.</string> <string
name="title_webdav_config_setting_unknown"
>Please configure WebDAV first.</string>
<string name="title_webdav_url">WebDAV server URL</string> <string name="title_webdav_url">WebDAV server URL</string>
<string name="title_webdav_user">Username</string> <string name="title_webdav_user">Username</string>
<string name="title_webdav_pass">Password</string> <string name="title_webdav_pass">Password</string>
@@ -451,4 +665,36 @@
<item>WebDAV</item> <item>WebDAV</item>
</string-array> </string-array>
<!-- Donate dialog -->
<string name="donate_dialog_title">Поддержите нас</string>
<string name="donate_label_card">Карта</string>
<string name="donate_label_ton">TON</string>
<string name="donate_label_trc20">TRC20</string>
<string name="donate_label_btc">BTC</string>
<string
name="donate_card_url"
translatable="false"
>pay.cloudtips.ru/p/28c476e5</string>
<string
name="donate_addr_ton"
translatable="false"
>UQD_Qc2cxLGe1P4wANi46cKdEvvzyJRrJTYPvGX2KAZDnsDh</string>
<string
name="donate_addr_trc20"
translatable="false"
>TYQqdACH5PrScvsMowSyS8JjaaF5wvFf5Q</string>
<string
name="donate_addr_btc"
translatable="false"
>bc1qvw0ts0jk5e5dfj9fdez76j9ck95lqz04fpf02a</string>
<string name="donate_copy">Копировать</string>
<string name="donate_open_link">Открыть</string>
<string
name="donate_card_link_url"
translatable="false"
>https://pay.cloudtips.ru/p/28c476e5</string>
<string name="donate_btn_dont_show">Не показывать</string>
<string name="donate_btn_postpone">Позже</string>
<string name="donate_toast_copied">Скопировано</string>
</resources> </resources>
@@ -83,6 +83,12 @@
<item name="cornerSize">16dp</item> <item name="cornerSize">16dp</item>
</style> </style>
<!-- Dialog corner shape — flatter than M3 default (28dp) -->
<style name="ShapeAppearance.App.Dialog" parent="ShapeAppearance.Material3.Corner.ExtraLarge">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">16dp</item>
</style>
<style name="RobotoEditTextStyle" parent="Widget.Material3.TextInputEditText.OutlinedBox"> <style name="RobotoEditTextStyle" parent="Widget.Material3.TextInputEditText.OutlinedBox">
<item name="android:fontFamily">sans-serif</item> <item name="android:fontFamily">sans-serif</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
@@ -92,6 +98,10 @@
<item name="android:fontFamily">sans-serif</item> <item name="android:fontFamily">sans-serif</item>
<item name="fontFamily">sans-serif</item> <item name="fontFamily">sans-serif</item>
<item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.App.MediumComponent</item> <item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.App.MediumComponent</item>
<!-- Flatter corners -->
<item name="shapeAppearanceCornerExtraLarge">@style/ShapeAppearance.App.Dialog</item>
<!-- Make dialog background match app surface instead of standing out -->
<item name="colorSurfaceContainerHigh">?attr/colorSurface</item>
</style> </style>
<!-- Light --> <!-- Light -->
+8 -7
View File
@@ -2,17 +2,17 @@
agp = "9.1.0" agp = "9.1.0"
desugarJdkLibs = "2.1.5" desugarJdkLibs = "2.1.5"
gradleLicensePlugin = "0.9.8" gradleLicensePlugin = "0.9.8"
kotlin = "2.1.0" kotlin = "2.2.0"
coreKtx = "1.17.0" coreKtx = "1.18.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.3.0" junitVersion = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"
appcompat = "1.7.1" appcompat = "1.7.1"
material = "1.13.0" material = "1.14.0"
activity = "1.12.4" activity = "1.13.0"
constraintlayout = "2.2.1" constraintlayout = "2.2.1"
mmkvStatic = "1.3.16" mmkvStatic = "1.3.16"
gson = "2.13.2" gson = "2.14.0"
okhttp = "5.3.2" okhttp = "5.3.2"
quickieFoss = "1.14.0" quickieFoss = "1.14.0"
kotlinxCoroutinesAndroid = "1.10.2" kotlinxCoroutinesAndroid = "1.10.2"
@@ -21,10 +21,11 @@ swiperefreshlayout = "1.2.0"
toasty = "1.5.2" toasty = "1.5.2"
editorkit = "2.9.0" editorkit = "2.9.0"
core = "3.5.4" core = "3.5.4"
workRuntimeKtx = "2.11.1" workRuntimeKtx = "2.11.2"
lifecycleViewmodelKtx = "2.10.0" lifecycleViewmodelKtx = "2.10.0"
multidex = "2.0.1" multidex = "2.0.1"
mockitoMockitoInline = "5.2.0" mockitoMockitoInline = "5.2.0"
mockitoKotlin = "5.4.0"
flexbox = "3.0.0" flexbox = "3.0.0"
preferenceKtx = "1.2.1" preferenceKtx = "1.2.1"
recyclerview = "1.4.0" recyclerview = "1.4.0"
@@ -60,7 +61,7 @@ lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx",
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleViewmodelKtx" } lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleViewmodelKtx" }
multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" } multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" }
org-mockito-mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockitoMockitoInline" } org-mockito-mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockitoMockitoInline" }
mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoMockitoInline" } mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlin" }
flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbox" } flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbox" }
recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" } preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" }
Binary file not shown.
+5 -2
View File
@@ -1,6 +1,9 @@
#Thu Nov 14 12:42:51 BDT 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
+179 -116
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright © 2015 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -15,81 +15,114 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" # This is normally unused
APP_BASE_NAME=`basename "$0"` # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,88 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
MAX_FD="$MAX_FD_LIMIT" # shellcheck disable=SC2039,SC3045
fi MAX_FD=$( ulimit -H -n ) ||
ulimit -n $MAX_FD warn "Could not query maximum file descriptor limit"
if [ $? -ne 0 ] ; then esac
warn "Could not set maximum file descriptor limit: $MAX_FD" case $MAX_FD in #(
fi '' | soft) :;; #(
else *)
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
fi # shellcheck disable=SC2039,SC3045
fi ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=`save "$@"` # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"
+26 -33
View File
@@ -13,19 +13,22 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables, and ensure extensions are enabled
if "%OS%"=="Windows_NT" setlocal setlocal EnableExtensions
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@@ -40,15 +43,15 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
@@ -56,34 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* @rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end :exitWithErrorLevel
@rem End local scope for the variables with windows NT shell @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
if "%ERRORLEVEL%"=="0" goto mainEnd "%COMSPEC%" /c exit %ERRORLEVEL%
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+7 -1
View File
@@ -10,8 +10,14 @@ __base="$(basename "${__file}" .sh)"
trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; exit 1' ERR INT trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; exit 1' ERR INT
export ANDROID_HOME=${ANDROID_HOME:-/opt/android-sdk} export ANDROID_HOME=${ANDROID_HOME:-$HOME/android-sdk}
if [[ ! -d $ANDROID_HOME ]]; then
export ANDROID_HOME=$HOME/android-sdk
fi
export NDK_HOME=${NDK_HOME:-$ANDROID_HOME/ndk/25.2.9519653} export NDK_HOME=${NDK_HOME:-$ANDROID_HOME/ndk/25.2.9519653}
if [[ ! -d $NDK_HOME ]]; then
export NDK_HOME=$ANDROID_HOME/ndk/25.2.9519653
fi
if [[ ! -d $NDK_HOME ]]; then if [[ ! -d $NDK_HOME ]]; then
echo "Android NDK: NDK_HOME not found. please set env \$NDK_HOME" echo "Android NDK: NDK_HOME not found. please set env \$NDK_HOME"