20 Commits

Author SHA1 Message Date
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
30 changed files with 1279 additions and 425 deletions
+9
View File
@@ -66,3 +66,12 @@ Thumbs.db
add_subscription_mmkv.py
.gitignore
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
require (
github.com/xtls/xray-core v1.260327.0
golang.org/x/mobile v0.0.0-20260312152759-81488f6aeb60
github.com/xtls/xray-core v1.260327.1-0.20260509173629-1bdb488c9ec0
golang.org/x/mobile v0.0.0-20260520154334-0e4426e1883d
)
require (
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/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // 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/miekg/dns v1.1.72 // 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/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/sagernet/sing v0.7.13 // 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
go.uber.org/mock v0.6.0 // 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/mod v0.34.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/mod v0.36.0 // indirect
golang.org/x/net v0.54.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/sys v0.44.0 // indirect
golang.org/x/text v0.37.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/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect
golang.zx2c4.com/wireguard/windows v1.0.1 // 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
gopkg.in/yaml.v2 v2.4.0 // 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/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.20260217092621-db4786c77a22/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkdYbw5JPGwBqIHs2J8tiEP7Py9lPqkN2I=
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/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/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/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/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/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/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
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/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/xray-core v1.260327.0 h1:g4TzxMwyPrxslZh6uD+FiG3lXKTrnNO+b4ky2OhogHE=
github.com/xtls/xray-core v1.260327.0/go.mod h1:OXMlhBloFry8mw0KwWLWLd3RQyXJzEYsCGlgsX36h60=
github.com/xtls/xray-core v1.260327.1-0.20260509173629-1bdb488c9ec0 h1:ft6HiTHelF1z9i3zZVPG9Q1LcLcvEB5jzBxky/+wljk=
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/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/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
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/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/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
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/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mobile v0.0.0-20260312152759-81488f6aeb60 h1:MOzyaj0wu2xneBkzkg9LHNYjDBB4W5vP043A2SYQRPA=
golang.org/x/mobile v0.0.0-20260312152759-81488f6aeb60/go.mod h1:th6VJvzjMbrYF8SduQY5rpD0HG0GleGxjadkqSxFs3k=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/mobile v0.0.0-20260520154334-0e4426e1883d h1:pWrEKZvKeqE2xPrylgBjgCyJSpPPt3L2WG2DmA+Xccg=
golang.org/x/mobile v0.0.0-20260520154334-0e4426e1883d/go.mod h1:ltIbhcRzKgwHa4ZxKJeiv0nyzcXUUYCqMyO0Y+vPmXw=
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
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/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
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/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
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/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/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8=
golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
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/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -78,6 +78,10 @@ object AppConfig {
const val PREF_DYNAMIC_COLORS = "pref_dynamic_colors"
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. */
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
@@ -16,7 +16,11 @@ object CountryDetector {
// ── Emoji flag → ISO 2-letter country code ────────────────────────────────
/** 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()
var i = 0
while (i < codePoints.size - 1) {
@@ -25,11 +29,13 @@ object CountryDetector {
if (cp1 in 0x1F1E6..0x1F1FF && cp2 in 0x1F1E6..0x1F1FF) {
val c1 = ('A'.code + (cp1 - 0x1F1E6)).toChar()
val c2 = ('A'.code + (cp2 - 0x1F1E6)).toChar()
return "$c1$c2"
result.add("$c1$c2")
i += 2
continue
}
i++
}
return null
return result
}
/** Get best country code for a server (emoji first, then cache). */
@@ -41,6 +47,16 @@ object CountryDetector {
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 ──────────────────────────────────────────────────
/** ISO code → flag emoji string */
@@ -305,20 +305,28 @@ object V2RayServiceManager {
var time = -1L
var errorStr = ""
try {
time = coreController.measureDelay(SettingsManager.getDelayTestUrl())
} catch (e: Exception) {
Log.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e)
errorStr = e.message?.substringAfter("\":") ?: "empty message"
}
if (time == -1L) {
val urls = listOf(
SettingsManager.getDelayTestUrl(),
SettingsManager.getDelayTestUrl(true)
)
for (url in urls) {
if (time >= 0) break
try {
time = coreController.measureDelay(SettingsManager.getDelayTestUrl(true))
time = coreController.measureDelay(url)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e)
errorStr = e.message?.substringAfter("\":") ?: "empty message"
}
}
// One more retry after a brief pause to reduce false negatives
if (time == -1L) {
kotlinx.coroutines.delay(500)
try {
time = coreController.measureDelay(urls[0])
} catch (e: Exception) {
errorStr = e.message?.substringAfter("\":") ?: "empty message"
}
}
val result = if (time >= 0) {
service.getString(R.string.connection_test_available, time)
@@ -1,5 +1,7 @@
package xyz.zarazaex.olc.ui
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
@@ -199,10 +201,17 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
checkForUpdatesOnStartup()
showDonateDialogIfNeeded()
}
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 ->
if (testing) {
@@ -217,13 +226,13 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
binding.btnSummaryLite.isEnabled = true
binding.btnSummaryLite.alpha = 1.0f
binding.btnSummaryLite.setIconResource(R.drawable.ic_stop_24dp)
binding.btnSummaryLite.backgroundTintList = ColorStateList.valueOf(
com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorPrimaryContainer, 0)
animateButtonTint(binding.btnSummaryLite,
com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorSecondaryContainer, 0)
)
} else {
setButtonsEnabled(true)
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)
)
if (!isLiteTesting) {
@@ -382,7 +391,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
showStatus("Остановлено")
setButtonsEnabled(true)
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)
)
hideLoading()
@@ -489,9 +498,9 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
delay(1000)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Error in restartV2Ray", e)
applyRunningState(isLoading = false, isRunning = mainViewModel.isRunning.value == true)
} finally {
isFabOperationInProgress = false
applyRunningState(isLoading = false, isRunning = mainViewModel.isRunning.value == true)
}
}
}
@@ -506,6 +515,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
private fun showStatus(message: String) {
statusResetJob?.cancel()
binding.tvTestState.text = message
if (isFabOperationInProgress || mainViewModel.isTesting.value == true) return
statusResetJob = lifecycleScope.launch {
delay(3000)
val isRunning = mainViewModel.isRunning.value == true
@@ -528,6 +538,16 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
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) {
val secContainer = ColorStateList.valueOf(
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
}
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) {
setSecondaryButtonsEnabled(false)
binding.fab.isEnabled = true
binding.fab.alpha = 1.0f
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)
setTestState(getString(R.string.connection_connected))
binding.layoutTest.isFocusable = true
setStatusDot(DotState.CONNECTED)
} else {
setButtonsEnabled(true)
binding.fab.backgroundTintList = accentColor()
binding.btnSummaryLite.backgroundTintList = secContainer
binding.fab.backgroundTintList = secContainer
binding.fab.iconTint = onSecContainer
animateButtonTint(binding.btnSummaryLite, secContainer.defaultColor)
binding.fab.contentDescription = getString(R.string.tasker_start_service)
setTestState(getString(R.string.connection_not_connected))
if (mainViewModel.isTesting.value != true && statusResetJob?.isActive != true) {
setTestState(getString(R.string.connection_not_connected))
}
binding.layoutTest.isFocusable = false
setStatusDot(DotState.IDLE)
}
}
private enum class DotState { IDLE, CONNECTED, LOADING }
private enum class DotState { IDLE, CONNECTED, LOADING, FAILURE }
private fun setStatusDot(state: DotState) {
val dot = binding.statusDot
@@ -576,6 +607,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
dot.alpha = 1f; dot.scaleX = 1f; dot.scaleY = 1f
dot.backgroundTintList = ColorStateList.valueOf(when (state) {
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.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() {
MaterialAlertDialogBuilder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
@@ -1013,16 +1113,36 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
val codes = allCountriesMap.keys.toTypedArray()
val labels = allCountriesMap.values.toTypedArray()
// currentFilter stores excluded set (empty = show all)
val checked = BooleanArray(codes.size) { codes[it] in currentFilter }
val dialog = MaterialAlertDialogBuilder(this@MainActivity)
.setTitle("Исключить страны")
.setMultiChoiceItems(labels, checked) { _, which, isChecked ->
checked[which] = isChecked
val adapter = object : android.widget.BaseAdapter() {
override fun getCount() = codes.size
override fun getItem(pos: Int) = codes[pos]
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("Применить") { _, _ ->
val excluded = codes.filterIndexed { i, _ -> checked[i] }.toSet()
mainViewModel.applyCountryFilter(excluded)
@@ -1035,8 +1155,8 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
showStatus("Показаны все страны")
}
.create()
dialog.show()
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 titleStr = getString(R.string.update_new_version_found, result.latestVersion)
val dialog = MaterialAlertDialogBuilder(this)
.setTitle(titleStr)
.setMessage(message)
.setPositiveButton(R.string.update_now) { _, _ ->
result.downloadUrl?.let {
@@ -1070,8 +1189,8 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
}
.create()
dialog.show()
dialog.setCustomTitle(buildDialogTitleWithClose(titleStr) { dialog.dismiss() })
dialog.show()
}
private fun buildDialogTitleWithClose(title: String, onClose: () -> Unit): View {
@@ -166,10 +166,14 @@ class MainRecyclerAdapter(
if (addressText.isEmpty()) View.GONE else View.VISIBLE
// TestResult
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString().orEmpty()
val delayMillis = data[position].testDelayMillis
holder.itemMainBinding.tvTestResult.text = when {
delayMillis == 0L -> "—ms"
delayMillis < 0L -> "-ms"
else -> "${delayMillis}ms"
}
holder.itemMainBinding.tvTestResult.setTextColor(
getPingColor(context, aff?.testDelayMillis)
getPingColor(context, delayMillis)
)
(holder.itemMainBinding.tvTestResult.layoutParams as? ViewGroup.MarginLayoutParams)?.marginStart =
if (addressText.isEmpty()) 0 else 6.dpToPx(context)
@@ -264,9 +268,7 @@ class MainRecyclerAdapter(
private fun recomputePingRange() {
val delays = data.mapNotNull { item ->
MmkvManager.decodeServerAffiliationInfo(item.guid)
?.testDelayMillis
?.takeIf { it > 0L }
item.testDelayMillis.takeIf { it > 0L }
}
minReachablePing = delays.minOrNull()
maxReachablePing = delays.maxOrNull()
@@ -185,8 +185,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val profile = MmkvManager.decodeServerConfig(guid) ?: continue
if (activeCountryFilter.isNotEmpty()) {
val code = CountryDetector.getCountryCode(profile.remarks, profile.server)
if (code in activeCountryFilter) continue
val codes = CountryDetector.getCountryCodes(profile.remarks, profile.server)
if (codes.all { it in activeCountryFilter }) continue
}
val delay = MmkvManager.decodeServerAffiliationInfo(guid)?.testDelayMillis ?: 0L
@@ -260,11 +260,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
var hasUnknown = false
for (guid in MmkvManager.decodeAllServerList()) {
val profile = MmkvManager.decodeServerConfig(guid) ?: continue
val code = CountryDetector.getCountryCode(profile.remarks, profile.server)
if (code == CountryDetector.UNKNOWN) {
hasUnknown = true
} else {
result[code] = "${CountryDetector.codeToFlag(code)} ${CountryDetector.codeToName(code)}"
val codes = CountryDetector.getCountryCodes(profile.remarks, profile.server)
for (code in codes) {
if (code == CountryDetector.UNKNOWN) {
hasUnknown = true
} else {
result[code] = "${CountryDetector.codeToFlag(code)} ${CountryDetector.codeToName(code)}"
}
}
}
if (hasUnknown) {
@@ -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"
app:icon="@drawable/shield_24"
app:iconSize="26dp"
app:backgroundTint="?attr/colorPrimary"
app:iconTint="?attr/colorOnPrimary"
app:rippleColor="?attr/colorOnPrimary"
app:backgroundTint="?attr/colorSecondaryContainer"
app:iconTint="?attr/colorOnSecondaryContainer"
app:rippleColor="?attr/colorOnSecondaryContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="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"?>
<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_height="wrap_content"
android:paddingStart="24dp"
android:paddingEnd="8dp"
android:paddingTop="20dp"
android:paddingBottom="12dp">
android:paddingTop="16dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/dialog_title_text"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/dialog_close_btn"
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
android:layout_gravity="center_vertical"
android:gravity="start"
android:paddingEnd="32dp"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
android:textColor="?attr/colorOnSurface" />
<ImageButton
android:id="@+id/dialog_close_btn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="end|center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Закрыть"
android:src="@drawable/ic_close_24dp"
android:tint="?attr/colorOnSurfaceVariant" />
</RelativeLayout>
</FrameLayout>
@@ -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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:button="@drawable/checkbox_round_selector"
android:clickable="false"
android:focusable="false"
android:padding="@dimen/padding_spacing_dp8" />
@@ -4,6 +4,7 @@
<color name="colorPingGood">#86D993</color>
<color name="colorPingMedium">#F1C76A</color>
<color name="status_connected">#66BB6A</color>
<color name="status_failure">#EF5350</color>
<color name="colorPingRed">#FFB4AB</color>
<color name="color_fab_active">@color/md_theme_primary</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_error_status_code">Код ошибки: #%d</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_runing_task_left">Проверено %s</string>
@@ -678,4 +678,16 @@
<item>WebDAV</item>
</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>
@@ -4,6 +4,7 @@
<color name="colorPingGood">#2E7D32</color>
<color name="colorPingMedium">#9A6700</color>
<color name="status_connected">#4CAF50</color>
<color name="status_failure">#E53935</color>
<color name="colorPingRed">#BA1A1A</color>
<color name="colorConfigType">#1565C0</color>
<color name="colorWhite">#FFFFFF</color>
+345 -102
View File
@@ -1,22 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<string name="app_name" translatable="false">olcng</string>
<string name="app_widget_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_close">Close navigation drawer</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_developed_text" translatable="false">developed by developers from <a href="https://t.me/openlibrecommunity">Olc</a></string>
<string name="drawer_forked_text" translatable="false">forked from <a
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="migration_fail">Data migration failed!</string>
<string name="pull_down_to_refresh">Please pull down to refresh!</string>
<!-- Notifications -->
<string name="notification_action_stop_v2ray">Stop</string>
<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="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="toast_services_start">Start 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_del_config">Delete config</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_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_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_http">Add [HTTP]</string>
<string name="menu_item_import_config_manually_trojan">Add [Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">Add [Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">Add [Hysteria2]</string>
<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_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_address">address</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_grpc">gRPC serviceName</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_allow_insecure">allowInsecure</string>
<string name="server_lab_sni">SNI</string>
@@ -88,8 +116,12 @@
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">SecretKey</string>
<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_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="toast_success">Success</string>
<string name="toast_failure">Failure</string>
@@ -97,31 +129,53 @@
<string name="toast_incorrect_protocol">Incorrect protocol</string>
<string name="toast_decoding_failed">Decoding failed</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="toast_config_file_invalid">Invalid config</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_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_insecure_url_protocol"
>Please do not use the insecure HTTP protocol subscription address</string>
<string
name="server_lab_need_inbound"
>Ensure inbounds port is consistent with the settings</string>
<string name="toast_malformed_josn">Config malformed</string>
<string name="server_lab_request_host6">Host(SNI)(Optional)</string>
<string name="toast_action_not_allowed">Action not allowed</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_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_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_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
<string name="server_lab_final_mask">finalMask raw JSON, format: { FinalMaskObject }</string>
<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_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 -->
<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_file">Add files</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="per_app_proxy_settings">Раздельное туннелирование</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 -->
<string name="title_settings">Settings</string>
@@ -154,24 +210,44 @@
<string name="title_core_settings">Core Settings</string>
<string name="title_vpn_settings">VPN Settings</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="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 name="summary_pref_auto_sort_after_test">Test results may not be accurate;</string>
<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="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="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_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 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
name="summary_pref_mux_enabled"
>Faster, but it may cause unstable connectivity\nCustomize how to handle TCP, UDP and QUIC below</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">
<item>reject</item>
<item>allow</item>
@@ -179,30 +255,46 @@
</string-array>
<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>
<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="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="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="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="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="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_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>
@@ -210,21 +302,33 @@
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</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="title_pref_delay_test_url">True delay test 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="title_pref_proxy_sharing_enabled">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_proxy_sharing_enabled"
>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="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="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="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 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_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 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_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 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_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="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 -->
<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="toast_tg_app_not_found">Telegram app not found</string>
<string name="title_privacy_policy">Privacy policy</string>
@@ -261,25 +383,45 @@
<string name="title_pref_promotion">Promotion</string>
<string name="title_pref_auto_update_subscription">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_pref_auto_update_subscription"
>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_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_help">Click me for more help</string>
<string name="title_language">Language</string>
<string name="title_ui_settings">UI 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 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_dynamic_colors"
>Dynamic colors (Material You)</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="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_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="title_logcat">Logcat</string>
@@ -298,9 +440,13 @@
<string name="sub_setting_enable">Enable 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_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_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_ping_all_server">Tcping 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_filter_config">Filter config</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_import_config_count">Import %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_updating">Updating…</string>
<string name="title_update_subscription_result">Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string>
<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="title_update_subscription_result"
>Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string>
<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_setting_confirm">Confirm</string>
@@ -326,64 +484,117 @@
<!-- RoutingSettingActivity -->
<string name="routing_settings_domain_strategy">Domain strategy</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_delete">Clear</string>
<string name="routing_settings_rule_title">Routing Rule Settings</string>
<string name="routing_settings_add_rule">Add rule</string>
<string name="routing_settings_import_predefined_rulesets">Import predefined rulesets</string>
<string name="routing_settings_import_rulesets_tip">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_import_predefined_rulesets"
>Import predefined rulesets</string>
<string
name="routing_settings_import_rulesets_tip"
>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_ip" translatable="false">ip</string>
<string name="routing_settings_port" translatable="false">port</string>
<string name="routing_settings_protocol" translatable="false">protocol</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="routing_settings_protocol"
translatable="false"
>protocol</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_testing">Testing…</string>
<string name="connection_test_testing_count">Тестирование %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_testing_count"
>Тестирование %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_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_updating_profiles">Updating profiles…</string>
<string name="connection_runing_task_left">Проверено %s</string>
<string name="import_subscription_success">Subscription imported Successfully</string>
<string name="import_subscription_failure">Import subscription failed</string>
<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_pref_fragment_packets">Fragment Packets</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="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_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string>
<string name="update_checking_for_update">Checking for update…</string>
<string name="title_policy_group_type">Policy group type</string>
<string name="title_policy_group_subscription_id">From subscription group</string>
<string name="title_policy_group_subscription_filter">Remarks regular filter</string>
<string
name="title_policy_group_subscription_id"
>From subscription group</string>
<string
name="title_policy_group_subscription_filter"
>Remarks regular filter</string>
<!-- 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_restore">Restore 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_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_user">Username</string>
<string name="title_webdav_pass">Password</string>
@@ -451,4 +662,36 @@
<item>WebDAV</item>
</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>
@@ -83,6 +83,12 @@
<item name="cornerSize">16dp</item>
</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">
<item name="android:fontFamily">sans-serif</item>
<item name="android:textSize">16sp</item>
@@ -92,6 +98,10 @@
<item name="android:fontFamily">sans-serif</item>
<item name="fontFamily">sans-serif</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>
<!-- Light -->
+8 -7
View File
@@ -2,17 +2,17 @@
agp = "9.1.0"
desugarJdkLibs = "2.1.5"
gradleLicensePlugin = "0.9.8"
kotlin = "2.1.0"
coreKtx = "1.17.0"
kotlin = "2.2.0"
coreKtx = "1.18.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
appcompat = "1.7.1"
material = "1.13.0"
activity = "1.12.4"
material = "1.14.0"
activity = "1.13.0"
constraintlayout = "2.2.1"
mmkvStatic = "1.3.16"
gson = "2.13.2"
gson = "2.14.0"
okhttp = "5.3.2"
quickieFoss = "1.14.0"
kotlinxCoroutinesAndroid = "1.10.2"
@@ -21,10 +21,11 @@ swiperefreshlayout = "1.2.0"
toasty = "1.5.2"
editorkit = "2.9.0"
core = "3.5.4"
workRuntimeKtx = "2.11.1"
workRuntimeKtx = "2.11.2"
lifecycleViewmodelKtx = "2.10.0"
multidex = "2.0.1"
mockitoMockitoInline = "5.2.0"
mockitoKotlin = "5.4.0"
flexbox = "3.0.0"
preferenceKtx = "1.2.1"
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" }
multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" }
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" }
recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
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
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
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");
# 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
# 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
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# 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"'
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
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.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
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."
fi
else
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.
JAVACMD=java
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
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# 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" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --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
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
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" "$@"
+82 -89
View File
@@ -1,89 +1,82 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
: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
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@rem Execute Gradle
@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
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
+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
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}
if [[ ! -d $NDK_HOME ]]; then
export NDK_HOME=$ANDROID_HOME/ndk/25.2.9519653
fi
if [[ ! -d $NDK_HOME ]]; then
echo "Android NDK: NDK_HOME not found. please set env \$NDK_HOME"