From 68078c769c67e5e1a49e4c6ec1e1a9739ed78b77 Mon Sep 17 00:00:00 2001 From: Binary Beast <43644992+binaryb3ast@users.noreply.github.com> Date: Sat, 6 Jul 2024 15:52:32 +0330 Subject: [PATCH] Data binding/theme approach Co-authored-by: Benny --- app/build.gradle | 3 + app/libs/go.mod | 17 +- app/libs/go.sum | 17 + app/src/main/AndroidManifest.xml | 17 +- .../oblivion/BypassListAppsAdapter.java | 2 + .../java/org/bepass/oblivion/EditSheet.java | 3 + .../java/org/bepass/oblivion/FileManager.java | 99 -- .../org/bepass/oblivion/InfoActivity.java | 32 - .../org/bepass/oblivion/PublicIPUtils.java | 86 -- .../org/bepass/oblivion/SettingsActivity.java | 194 ---- .../bepass/oblivion/SplashScreenActivity.java | 37 - .../oblivion/SplitTunnelOptionsAdapter.java | 3 + .../oblivion/base/ApplicationLoader.java | 45 + .../bepass/oblivion/base/BaseActivity.java | 53 + .../{ => base}/StateAwareBaseActivity.java | 40 +- .../org/bepass/oblivion/component/Icon.java | 83 ++ .../{ => component}/TouchAwareSwitch.java | 2 +- .../oblivion/{ => enums}/ConnectionState.java | 2 +- .../oblivion/{ => enums}/SplitTunnelMode.java | 4 +- .../ConnectionStateChangeListener.java | 4 +- .../{ => interfaces}/SheetsCallBack.java | 2 +- .../oblivion/{ => model}/IPDetails.java | 2 +- .../{ => service}/OblivionVpnService.java | 9 +- .../{ => service}/QuickStartService.java | 4 +- .../org/bepass/oblivion/ui/InfoActivity.java | 39 + .../bepass/oblivion/{ => ui}/LogActivity.java | 41 +- .../oblivion/{ => ui}/MainActivity.java | 220 ++-- .../bepass/oblivion/ui/SettingsActivity.java | 201 ++++ .../oblivion/ui/SplashScreenActivity.java | 70 ++ .../{ => ui}/SplitTunnelActivity.java | 51 +- .../{ => utils}/BatteryOptimization.kt | 3 +- .../org/bepass/oblivion/utils/ColorUtils.java | 11 + .../oblivion/{ => utils}/CountryUtils.java | 7 +- .../bepass/oblivion/utils/FileManager.java | 240 ++++ .../oblivion/utils/LocalController.java | 62 + .../oblivion/{ => utils}/LocaleHandler.java | 3 +- .../oblivion/{ => utils}/LocaleHelper.java | 6 +- .../bepass/oblivion/utils/PublicIPUtils.java | 129 +++ .../bepass/oblivion/utils/SystemUtils.java | 42 + .../bepass/oblivion/utils/ThemeHelper.java | 88 ++ app/src/main/res/color/checkbox_tint.xml | 4 +- .../drawable-anydpi/bottom_sheet_closer.xml | 2 +- app/src/main/res/drawable/button.xml | 4 +- app/src/main/res/layout/activity_info.xml | 206 ++-- app/src/main/res/layout/activity_log.xml | 137 +-- app/src/main/res/layout/activity_main.xml | 357 +++--- app/src/main/res/layout/activity_settings.xml | 1002 +++++++++-------- .../res/layout/activity_splash_screen.xml | 302 ++--- .../main/res/layout/activity_split_tunnel.xml | 129 ++- .../layout/dialog_battery_optimization.xml | 23 +- app/src/main/res/layout/edit_sheet.xml | 13 +- .../main/res/layout/installed_app_item.xml | 3 +- .../main/res/layout/split_tunnel_options.xml | 21 +- app/src/main/res/values-fa/strings.xml | 4 +- app/src/main/res/values-night/colors.xml | 17 + app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-zh/strings.xml | 4 +- app/src/main/res/values/attrs.xml | 8 + app/src/main/res/values/colors.xml | 16 +- app/src/main/res/values/strings.xml | 8 +- 60 files changed, 2561 insertions(+), 1676 deletions(-) delete mode 100644 app/src/main/java/org/bepass/oblivion/FileManager.java delete mode 100644 app/src/main/java/org/bepass/oblivion/InfoActivity.java delete mode 100644 app/src/main/java/org/bepass/oblivion/PublicIPUtils.java delete mode 100644 app/src/main/java/org/bepass/oblivion/SettingsActivity.java delete mode 100644 app/src/main/java/org/bepass/oblivion/SplashScreenActivity.java create mode 100644 app/src/main/java/org/bepass/oblivion/base/ApplicationLoader.java create mode 100644 app/src/main/java/org/bepass/oblivion/base/BaseActivity.java rename app/src/main/java/org/bepass/oblivion/{ => base}/StateAwareBaseActivity.java (59%) create mode 100644 app/src/main/java/org/bepass/oblivion/component/Icon.java rename app/src/main/java/org/bepass/oblivion/{ => component}/TouchAwareSwitch.java (96%) rename app/src/main/java/org/bepass/oblivion/{ => enums}/ConnectionState.java (86%) rename app/src/main/java/org/bepass/oblivion/{ => enums}/SplitTunnelMode.java (84%) rename app/src/main/java/org/bepass/oblivion/{ => interfaces}/ConnectionStateChangeListener.java (50%) rename app/src/main/java/org/bepass/oblivion/{ => interfaces}/SheetsCallBack.java (61%) rename app/src/main/java/org/bepass/oblivion/{ => model}/IPDetails.java (74%) rename app/src/main/java/org/bepass/oblivion/{ => service}/OblivionVpnService.java (98%) rename app/src/main/java/org/bepass/oblivion/{ => service}/QuickStartService.java (98%) create mode 100644 app/src/main/java/org/bepass/oblivion/ui/InfoActivity.java rename app/src/main/java/org/bepass/oblivion/{ => ui}/LogActivity.java (74%) rename app/src/main/java/org/bepass/oblivion/{ => ui}/MainActivity.java (56%) create mode 100644 app/src/main/java/org/bepass/oblivion/ui/SettingsActivity.java create mode 100644 app/src/main/java/org/bepass/oblivion/ui/SplashScreenActivity.java rename app/src/main/java/org/bepass/oblivion/{ => ui}/SplitTunnelActivity.java (53%) rename app/src/main/java/org/bepass/oblivion/{ => utils}/BatteryOptimization.kt (97%) create mode 100644 app/src/main/java/org/bepass/oblivion/utils/ColorUtils.java rename app/src/main/java/org/bepass/oblivion/{ => utils}/CountryUtils.java (94%) create mode 100644 app/src/main/java/org/bepass/oblivion/utils/FileManager.java create mode 100644 app/src/main/java/org/bepass/oblivion/utils/LocalController.java rename app/src/main/java/org/bepass/oblivion/{ => utils}/LocaleHandler.java (98%) rename app/src/main/java/org/bepass/oblivion/{ => utils}/LocaleHelper.java (96%) create mode 100644 app/src/main/java/org/bepass/oblivion/utils/PublicIPUtils.java create mode 100644 app/src/main/java/org/bepass/oblivion/utils/SystemUtils.java create mode 100644 app/src/main/java/org/bepass/oblivion/utils/ThemeHelper.java create mode 100644 app/src/main/res/values-night/colors.xml create mode 100644 app/src/main/res/values/attrs.xml diff --git a/app/build.gradle b/app/build.gradle index b81a63e9..fa3ec6d8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,6 +18,9 @@ android { useSupportLibrary true } } + buildFeatures { + dataBinding = true + } buildTypes { release { diff --git a/app/libs/go.mod b/app/libs/go.mod index 413b230f..91934257 100644 --- a/app/libs/go.mod +++ b/app/libs/go.mod @@ -6,12 +6,13 @@ toolchain go1.22.2 replace github.com/eycorsican/go-tun2socks => github.com/trojan-gfw/go-tun2socks v1.16.3-0.20210702214000-083d49176e05 + require ( github.com/bepass-org/warp-plus v1.2.4-0.20240603141320-b3f8c9953e10 github.com/eycorsican/go-tun2socks v1.16.11 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/xjasonlyu/tun2socks/v2 v2.5.2 - golang.org/x/mobile v0.0.0-20240213143359-d1f7d3436075 + golang.org/x/mobile v0.0.0-20240604190613-2782386b8afd ) require ( @@ -72,15 +73,15 @@ require ( gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/crypto v0.22.0 // indirect + golang.org/x/crypto v0.24.0 // indirect golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect - golang.org/x/mod v0.15.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/tools v0.22.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/protobuf v1.32.0 // indirect diff --git a/app/libs/go.sum b/app/libs/go.sum index 9ff453cb..28d434a3 100644 --- a/app/libs/go.sum +++ b/app/libs/go.sum @@ -221,14 +221,20 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mobile v0.0.0-20240213143359-d1f7d3436075 h1:iZzqyDd8gFkJZpsJNzveyScRBcQlsveheh6Q77LzhPY= golang.org/x/mobile v0.0.0-20240213143359-d1f7d3436075/go.mod h1:Y8Bnziw2dX69ZhYuqQB8Ihyjks1Q6fMmbg17j9+ISNA= +golang.org/x/mobile v0.0.0-20240604190613-2782386b8afd h1:ow0zRCrn9LoaazcXsRUYYjFp+cwkdgB/vlW/ZJEzNRw= +golang.org/x/mobile v0.0.0-20240604190613-2782386b8afd/go.mod h1:CKRkjuY6XI4LTd47GM+4lCT8aFnTO+FcLAGMu0ibd0g= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -238,12 +244,16 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -259,6 +269,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -267,6 +279,7 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -275,6 +288,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -283,6 +298,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c644e93b..6897f76c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,6 +33,7 @@ android:banner="@mipmap/tv_banner" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:name=".base.ApplicationLoader" tools:replace="android:supportsRtl" android:screenOrientation="portrait" android:enableOnBackInvokedCallback="true" @@ -41,7 +42,7 @@ tools:ignore="DiscouragedApi"> - @@ -78,7 +79,7 @@ @@ -88,20 +89,20 @@ diff --git a/app/src/main/java/org/bepass/oblivion/BypassListAppsAdapter.java b/app/src/main/java/org/bepass/oblivion/BypassListAppsAdapter.java index 70ea2000..8e953e73 100644 --- a/app/src/main/java/org/bepass/oblivion/BypassListAppsAdapter.java +++ b/app/src/main/java/org/bepass/oblivion/BypassListAppsAdapter.java @@ -18,6 +18,8 @@ import com.bumptech.glide.Glide; import com.google.android.material.imageview.ShapeableImageView; +import org.bepass.oblivion.utils.FileManager; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; diff --git a/app/src/main/java/org/bepass/oblivion/EditSheet.java b/app/src/main/java/org/bepass/oblivion/EditSheet.java index b127966b..4e7f5e77 100644 --- a/app/src/main/java/org/bepass/oblivion/EditSheet.java +++ b/app/src/main/java/org/bepass/oblivion/EditSheet.java @@ -7,6 +7,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialog; +import org.bepass.oblivion.interfaces.SheetsCallBack; +import org.bepass.oblivion.utils.FileManager; + public class EditSheet { FileManager fileManager; diff --git a/app/src/main/java/org/bepass/oblivion/FileManager.java b/app/src/main/java/org/bepass/oblivion/FileManager.java deleted file mode 100644 index 024463af..00000000 --- a/app/src/main/java/org/bepass/oblivion/FileManager.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.bepass.oblivion; - -import android.content.Context; -import android.content.SharedPreferences; - -import java.util.Set; - -public class FileManager { - private static FileManager instance; - private final SharedPreferences sharedPreferences; - - // Private constructor for singleton pattern - private FileManager(Context context) { - sharedPreferences = context.getSharedPreferences("UserData", Context.MODE_PRIVATE); - } - - // Public method to get the singleton instance - public static synchronized FileManager getInstance(Context context) { - if (instance == null) { - instance = new FileManager(context.getApplicationContext()); - } - return instance; - } - - // Methods to set various types of data - public void set(String name, String value) { - sharedPreferences.edit().putString(name, value).apply(); - } - - public void set(String name, boolean value) { - sharedPreferences.edit().putBoolean(name, value).apply(); - } - - public void set(String name, Set value) { - sharedPreferences.edit().putStringSet(name, value).apply(); - } - - public void set(String name, int value) { - sharedPreferences.edit().putInt(name, value).apply(); - } - - public void set(String name, float value) { - sharedPreferences.edit().putFloat(name, value).apply(); - } - - public void set(String name, long value) { - sharedPreferences.edit().putLong(name, value).apply(); - } - - public void setDouble(String name, double value) { - sharedPreferences.edit().putLong(name, Double.doubleToRawLongBits(value)).apply(); - } - - // Methods to get various types of data - public String getString(String name) { - return sharedPreferences.getString(name, ""); - } - - public String getString(String name, String defaultValue) { - return sharedPreferences.getString(name, defaultValue); - } - - public Set getStringSet(String name, Set def) { - return sharedPreferences.getStringSet(name, def); - } - - public boolean getBoolean(String name) { - return sharedPreferences.getBoolean(name, false); - } - - public boolean getBoolean(String name, boolean defaultValue) { - return sharedPreferences.getBoolean(name, defaultValue); - } - - public int getInt(String name) { - return sharedPreferences.getInt(name, 0); - } - - public float getFloat(String name) { - return sharedPreferences.getFloat(name, 0f); - } - - public long getLong(String name) { - return sharedPreferences.getLong(name, 0L); - } - - public double getDouble(String name) { - return Double.longBitsToDouble(sharedPreferences.getLong(name, 0)); - } - - // Methods for handling logs - public void resetLog() { - sharedPreferences.edit().putString("APP_LOG", "").apply(); - } - - public void addLog(String log) { - sharedPreferences.edit().putString("APP_LOG", log).apply(); - } -} diff --git a/app/src/main/java/org/bepass/oblivion/InfoActivity.java b/app/src/main/java/org/bepass/oblivion/InfoActivity.java deleted file mode 100644 index 3767966d..00000000 --- a/app/src/main/java/org/bepass/oblivion/InfoActivity.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.bepass.oblivion; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.widget.ImageView; -import android.widget.RelativeLayout; - -import androidx.appcompat.app.AppCompatActivity; - -public class InfoActivity extends AppCompatActivity { - - ImageView back; - RelativeLayout github; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_info); - - back = findViewById(R.id.back); - github = findViewById(R.id.github_layout); - - github.setOnClickListener(v -> { - Uri uri = Uri.parse("https://github.com/bepass-org/oblivion"); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - startActivity(intent); - }); - - back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); - } -} diff --git a/app/src/main/java/org/bepass/oblivion/PublicIPUtils.java b/app/src/main/java/org/bepass/oblivion/PublicIPUtils.java deleted file mode 100644 index 16036a35..00000000 --- a/app/src/main/java/org/bepass/oblivion/PublicIPUtils.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.bepass.oblivion; - -import android.content.Context; -import android.os.Handler; - -import com.vdurmont.emoji.EmojiManager; - -import org.json.JSONObject; - -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.util.Locale; -import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - - -public class PublicIPUtils { - - private static PublicIPUtils instance; - private final FileManager fm; - ExecutorService executorService = Executors.newFixedThreadPool(1); - - public PublicIPUtils(Context context) { - fm = FileManager.getInstance(context); - } - - // Public method to get the singleton instance - public static synchronized PublicIPUtils getInstance(Context context) { - if (instance == null) { - instance = new PublicIPUtils(context.getApplicationContext()); - } - return instance; - } - - - public void getIPDetails(IPDetailsCallback callback) { - Handler handler = new Handler(); - IPDetails details = new IPDetails(); - executorService.execute(() -> { - long startTime = System.currentTimeMillis(); - while (System.currentTimeMillis() - startTime < 30 * 1000) { // 30 seconds - try { - int socksPort = Integer.parseInt(fm.getString("USERSETTING_port")); - Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("localhost", socksPort)); - OkHttpClient client = new OkHttpClient.Builder() - .proxy(proxy) - .connectTimeout(5, TimeUnit.SECONDS) // 5 seconds connection timeout - .readTimeout(5, TimeUnit.SECONDS) // 5 seconds read timeout - .build(); - Request request = new Request.Builder() - .url("https://api.country.is/") - .build(); - JSONObject jsonData; - try (Response response = client.newCall(request).execute()) { - jsonData = new JSONObject(Objects.requireNonNull(response.body()).string()); - } - details.ip = jsonData.getString("ip"); - details.country = jsonData.getString("country"); - details.flag = EmojiManager.getForAlias(jsonData.getString("country").toLowerCase(Locale.ROOT)).getUnicode(); - handler.post(() -> callback.onDetailsReceived(details)); - return; - } catch (Exception e) { - e.printStackTrace(); - } - - try { - Thread.sleep(1000); // Sleep before retrying - } catch (InterruptedException e) { - e.printStackTrace(); - return; - } - handler.post(() -> callback.onDetailsReceived(details)); - } - }); - } - - public interface IPDetailsCallback { - void onDetailsReceived(IPDetails details); - } -} diff --git a/app/src/main/java/org/bepass/oblivion/SettingsActivity.java b/app/src/main/java/org/bepass/oblivion/SettingsActivity.java deleted file mode 100644 index 38de084e..00000000 --- a/app/src/main/java/org/bepass/oblivion/SettingsActivity.java +++ /dev/null @@ -1,194 +0,0 @@ -package org.bepass.oblivion; - -import static org.bepass.oblivion.BatteryOptimizationKt.isBatteryOptimizationEnabled; -import static org.bepass.oblivion.BatteryOptimizationKt.showBatteryOptimizationDialog; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.util.Pair; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.Spinner; -import android.widget.TextView; - -import androidx.activity.OnBackPressedCallback; - -public class SettingsActivity extends StateAwareBaseActivity { - private FileManager fileManager; - private LinearLayout countryLayout; - private TextView endpoint, port, license; - private CheckBox psiphon, lan, gool; - private Spinner country; - private CheckBox.OnCheckedChangeListener psiphonListener; - private CheckBox.OnCheckedChangeListener goolListener; - private Context context; - private void setCheckBoxWithoutTriggeringListener(CheckBox checkBox, boolean isChecked, CheckBox.OnCheckedChangeListener listener) { - checkBox.setOnCheckedChangeListener(null); // Temporarily detach the listener - checkBox.setChecked(isChecked); // Set the checked state - checkBox.setOnCheckedChangeListener(listener); // Reattach the listener - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_settings); - context = this; - fileManager = FileManager.getInstance(this); - - LinearLayout batteryOptLayout = findViewById(R.id.battery_optimization_layout); - LinearLayout batteryOptLine = findViewById(R.id.battery_opt_line); - if(isBatteryOptimizationEnabled(this)){ - batteryOptLayout.setOnClickListener(view -> { - showBatteryOptimizationDialog(this); - }); - }else{ - batteryOptLayout.setVisibility(View.GONE); - batteryOptLine.setVisibility(View.GONE); - } - LinearLayout endpointLayout = findViewById(R.id.endpoint_layout); - LinearLayout portLayout = findViewById(R.id.port_layout); - LinearLayout splitTunnelLayout = findViewById(R.id.split_tunnel_layout); - LinearLayout lanLayout = findViewById(R.id.lan_layout); - LinearLayout psiphonLayout = findViewById(R.id.psiphon_layout); - countryLayout = findViewById(R.id.country_layout); - LinearLayout licenseLayout = findViewById(R.id.license_layout); - LinearLayout goolLayout = findViewById(R.id.gool_layout); - - endpoint = findViewById(R.id.endpoint); - port = findViewById(R.id.port); - country = findViewById(R.id.country); - license = findViewById(R.id.license); - - psiphon = findViewById(R.id.psiphon); - lan = findViewById(R.id.lan); - gool = findViewById(R.id.gool); - - ImageView back = findViewById(R.id.back); - back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); - - getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - if (StateAwareBaseActivity.getRequireRestartVpnService()) { - StateAwareBaseActivity.setRequireRestartVpnService(false); - if (!lastKnownConnectionState.isDisconnected()) { - OblivionVpnService.stopVpnService(SettingsActivity.this); - OblivionVpnService.startVpnService(SettingsActivity.this); - } - } - finish(); - } - }); - - SheetsCallBack sheetsCallBack = this::settingBasicValuesFromSPF; - // Listen to Changes - endpointLayout.setOnClickListener(v -> (new EditSheet(this, getString(R.string.endpointText), "endpoint", sheetsCallBack)).start()); - portLayout.setOnClickListener(v -> (new EditSheet(this, getString(R.string.portTunText), "port", sheetsCallBack)).start()); - licenseLayout.setOnClickListener(v -> (new EditSheet(this, getString(R.string.licenseText), "license", sheetsCallBack)).start()); - - ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.countries, R.layout.country_item_layout); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - country.setAdapter(adapter); - - country.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - String name = parent.getItemAtPosition(position).toString(); - Pair codeAndName = CountryUtils.getCountryCode(context, name); - fileManager.set("USERSETTING_country", codeAndName.first); - } - - @Override - public void onNothingSelected(AdapterView parent) {} - }); - - splitTunnelLayout.setOnClickListener(v -> startActivity(new Intent(this, SplitTunnelActivity.class))); - - // Set Current Values - settingBasicValuesFromSPF(); - - goolLayout.setOnClickListener(v -> gool.setChecked(!gool.isChecked())); - lanLayout.setOnClickListener(v -> lan.setChecked(!lan.isChecked())); - psiphonLayout.setOnClickListener(v -> psiphon.setChecked(!psiphon.isChecked())); - - lan.setOnCheckedChangeListener((buttonView, isChecked) -> fileManager.set("USERSETTING_lan", isChecked)); - // Initialize the listeners - psiphonListener = (buttonView, isChecked) -> { - fileManager.set("USERSETTING_psiphon", isChecked); - if (isChecked && gool.isChecked()) { - setCheckBoxWithoutTriggeringListener(gool, false, goolListener); - fileManager.set("USERSETTING_gool", false); - } - countryLayout.setAlpha(isChecked ? 1f : 0.2f); - country.setEnabled(isChecked); - }; - - goolListener = (buttonView, isChecked) -> { - fileManager.set("USERSETTING_gool", isChecked); - if (isChecked && psiphon.isChecked()) { - setCheckBoxWithoutTriggeringListener(psiphon, false, psiphonListener); - fileManager.set("USERSETTING_psiphon", false); - countryLayout.setAlpha(0.2f); - country.setEnabled(false); - } - }; - - // Set the listeners to the checkboxes - psiphon.setOnCheckedChangeListener(psiphonListener); - gool.setOnCheckedChangeListener(goolListener); - } - - private int getIndexFromName(Spinner spinner, String name) { - String ccn = CountryUtils.getCountryName(name); - String newname = LocaleHelper.restoreText(this,ccn); - for (int i = 0; i < spinner.getCount(); i++) { - if (spinner.getItemAtPosition(i).toString().equalsIgnoreCase(newname)) { - return i; - } - } - - return 0; - } - - private void settingBasicValuesFromSPF() { - endpoint.setText(fileManager.getString("USERSETTING_endpoint")); - port.setText(fileManager.getString("USERSETTING_port")); - license.setText(fileManager.getString("USERSETTING_license")); - - String countryCode = fileManager.getString("USERSETTING_country"); - int index = 0; - if (!countryCode.isEmpty()) { - LocaleHelper.goEn(this); - String countryName = CountryUtils.getCountryName(countryCode); - index = getIndexFromName(country, countryName); - LocaleHelper.restoreLocale(this); - } - country.setSelection(index); - - psiphon.setChecked(fileManager.getBoolean("USERSETTING_psiphon")); - lan.setChecked(fileManager.getBoolean("USERSETTING_lan")); - gool.setChecked(fileManager.getBoolean("USERSETTING_gool")); - - - if (!psiphon.isChecked()) { - countryLayout.setAlpha(0.2f); - country.setEnabled(false); - } else { - countryLayout.setAlpha(1f); - country.setEnabled(true); - } - } - - @Override - String getKey() { - return "settingsActivity"; - } - - @Override - void onConnectionStateChange(ConnectionState state) {} -} diff --git a/app/src/main/java/org/bepass/oblivion/SplashScreenActivity.java b/app/src/main/java/org/bepass/oblivion/SplashScreenActivity.java deleted file mode 100644 index 54212178..00000000 --- a/app/src/main/java/org/bepass/oblivion/SplashScreenActivity.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.bepass.oblivion; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.view.View; - -import androidx.appcompat.app.AppCompatActivity; - -@SuppressLint("CustomSplashScreen") -public class SplashScreenActivity extends AppCompatActivity implements View.OnClickListener { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // Initialize the LocaleHandler and set the locale - LocaleHandler localeHandler = new LocaleHandler(this); - setContentView(R.layout.activity_splash_screen); - final int SHORT_SPLASH_DISPLAY_LENGTH = 1000; // 1 second - findViewById(R.id.splashScreen).setOnClickListener(this); - new Handler().postDelayed(() -> { - // Create an Intent that will start the Main Activity. - Intent mainIntent = new Intent(SplashScreenActivity.this, MainActivity.class); - SplashScreenActivity.this.startActivity(mainIntent); - SplashScreenActivity.this.finish(); - }, SHORT_SPLASH_DISPLAY_LENGTH); - } - - @Override - public void onClick(View v) { - // If the user clicks on the splash screen, move to the MainActivity immediately - Intent mainIntent = new Intent(SplashScreenActivity.this, MainActivity.class); - SplashScreenActivity.this.startActivity(mainIntent); - SplashScreenActivity.this.finish(); - } -} diff --git a/app/src/main/java/org/bepass/oblivion/SplitTunnelOptionsAdapter.java b/app/src/main/java/org/bepass/oblivion/SplitTunnelOptionsAdapter.java index aa445f51..83288c15 100644 --- a/app/src/main/java/org/bepass/oblivion/SplitTunnelOptionsAdapter.java +++ b/app/src/main/java/org/bepass/oblivion/SplitTunnelOptionsAdapter.java @@ -11,6 +11,9 @@ import com.google.android.material.switchmaterial.SwitchMaterial; +import org.bepass.oblivion.enums.SplitTunnelMode; +import org.bepass.oblivion.utils.FileManager; + public class SplitTunnelOptionsAdapter extends RecyclerView.Adapter { private final OnSettingsChanged settingsCallback; diff --git a/app/src/main/java/org/bepass/oblivion/base/ApplicationLoader.java b/app/src/main/java/org/bepass/oblivion/base/ApplicationLoader.java new file mode 100644 index 00000000..20dda09a --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/base/ApplicationLoader.java @@ -0,0 +1,45 @@ +package org.bepass.oblivion.base; + +import android.annotation.SuppressLint; +import android.app.Application; +import android.content.Context; + +import androidx.appcompat.app.AppCompatDelegate; + +import org.bepass.oblivion.utils.ThemeHelper; + +/** + * ApplicationLoader is a custom Application class that extends the Android Application class. + * It is designed to provide a centralized context reference throughout the application. + */ +public class ApplicationLoader extends Application { + + // Tag for logging purposes + private static final String TAG = "ApplicationLoader"; + + // Context reference + @SuppressLint("StaticFieldLeak") + private static Context context; + + /** + * This method is called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created. + * + * @see android.app.Application#onCreate() + */ + @Override + public void onCreate() { + super.onCreate(); + context = getApplicationContext(); + ThemeHelper.getInstance().init(); + ThemeHelper.getInstance().applyTheme(); + } + + /** + * Returns the application context. + * + * @return The application context. + */ + public static Context getAppCtx() { + return context; + } +} diff --git a/app/src/main/java/org/bepass/oblivion/base/BaseActivity.java b/app/src/main/java/org/bepass/oblivion/base/BaseActivity.java new file mode 100644 index 00000000..d256b93b --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/base/BaseActivity.java @@ -0,0 +1,53 @@ +package org.bepass.oblivion.base; + +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; + +import androidx.activity.result.ActivityResult; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.databinding.DataBindingUtil; +import androidx.databinding.ViewDataBinding; + +import org.bepass.oblivion.utils.ColorUtils; +import org.bepass.oblivion.utils.SystemUtils; + +/** + * BaseActivity is an abstract class serving as a base for activities in an Android application. + * It extends the AppCompatActivity class and utilizes Android's data binding framework. + * @param The type of ViewDataBinding associated with the activity. + */ +public abstract class BaseActivity extends AppCompatActivity { + + // ViewDataBinding instance associated with the activity layout + protected B binding; + + // Tag for logging purposes + protected String TAG = this.getClass().getSimpleName(); + + /** + * Abstract method to be implemented by subclasses to provide the layout resource ID for the activity. + * @return The layout resource ID. + */ + protected abstract int getLayoutResourceId(); + + protected abstract int getStatusBarColor(); + + /** + * Called when the activity is starting. + * @param savedInstanceState If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). + * @see AppCompatActivity#onCreate(Bundle) + */ + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Inflates the layout and initializes the binding object + binding = DataBindingUtil.setContentView(this, getLayoutResourceId()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + SystemUtils.setStatusBarColor( + this, getStatusBarColor(), ColorUtils.isColorDark(getStatusBarColor()) + ); + } + } +} diff --git a/app/src/main/java/org/bepass/oblivion/StateAwareBaseActivity.java b/app/src/main/java/org/bepass/oblivion/base/StateAwareBaseActivity.java similarity index 59% rename from app/src/main/java/org/bepass/oblivion/StateAwareBaseActivity.java rename to app/src/main/java/org/bepass/oblivion/base/StateAwareBaseActivity.java index ebe94bbe..76bcd7c2 100644 --- a/app/src/main/java/org/bepass/oblivion/StateAwareBaseActivity.java +++ b/app/src/main/java/org/bepass/oblivion/base/StateAwareBaseActivity.java @@ -1,24 +1,56 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.base; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.os.Build; +import android.os.Bundle; import android.os.IBinder; import android.os.Messenger; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.databinding.DataBindingUtil; +import androidx.databinding.ViewDataBinding; + +import org.bepass.oblivion.enums.ConnectionState; +import org.bepass.oblivion.service.OblivionVpnService; +import org.bepass.oblivion.utils.ColorUtils; +import org.bepass.oblivion.utils.SystemUtils; /** * Those activities that inherit this class observe connection state by default and have access to lastKnownConnectionState variable. */ -public abstract class StateAwareBaseActivity extends AppCompatActivity { +public abstract class StateAwareBaseActivity extends AppCompatActivity { protected ConnectionState lastKnownConnectionState = ConnectionState.DISCONNECTED; private static boolean requireRestartVpnService = false; + protected B binding; private Messenger serviceMessenger; private boolean isBound; + protected abstract int getLayoutResourceId(); + + protected abstract int getStatusBarColor(); + + /** + * Called when the activity is starting. + * @param savedInstanceState If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). + * @see AppCompatActivity#onCreate(Bundle) + */ + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Inflates the layout and initializes the binding object + binding = DataBindingUtil.setContentView(this, getLayoutResourceId()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + SystemUtils.setStatusBarColor( + this, getStatusBarColor(), ColorUtils.isColorDark(getStatusBarColor()) + ); + } + } + public static boolean getRequireRestartVpnService() { return requireRestartVpnService; } @@ -42,9 +74,9 @@ public void onServiceDisconnected(ComponentName arg0) { } }; - abstract String getKey(); + protected abstract String getKey(); - abstract void onConnectionStateChange(ConnectionState state); + protected abstract void onConnectionStateChange(ConnectionState state); private void observeConnectionStatus() { if (!isBound) return; diff --git a/app/src/main/java/org/bepass/oblivion/component/Icon.java b/app/src/main/java/org/bepass/oblivion/component/Icon.java new file mode 100644 index 00000000..d00c0313 --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/component/Icon.java @@ -0,0 +1,83 @@ +package org.bepass.oblivion.component; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; + +import org.bepass.oblivion.R; +import org.bepass.oblivion.utils.LocalController; + +import java.io.File; + +public class Icon extends AppCompatImageView { + private boolean hasBounceAnimation; + private boolean isPrimaryIcon; + + public Icon(@NonNull Context context) { + super(context); + if (!isInEditMode())init(); + } + + public Icon(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + if (!isInEditMode()){ + setupAttrs(context, attrs, 0); + init(); + } + } + + public Icon(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + if (!isInEditMode()){ + setupAttrs(context, attrs, defStyleAttr); + init(); + } + } + + private void setupAttrs(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Icon, defStyleAttr, 0); + try { + int color = a.getColor(R.styleable.Icon_icon_color, 0); + if (color != 0) { + setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + } + } finally { + a.recycle(); + } + } + + public void setColor(int color) { + if (color != 0) { + setColorFilter(LocalController.getColor(color), PorterDuff.Mode.SRC_ATOP); + } + invalidate(); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { +// if (hasBounceAnimation) +// AnimationHelper.startPushButtonAnimation(this); + } + return super.onTouchEvent(event); + + } + + public void changeColor(int color) { + if (color != 0) { + setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + } + } + + private void init() { + } +} \ No newline at end of file diff --git a/app/src/main/java/org/bepass/oblivion/TouchAwareSwitch.java b/app/src/main/java/org/bepass/oblivion/component/TouchAwareSwitch.java similarity index 96% rename from app/src/main/java/org/bepass/oblivion/TouchAwareSwitch.java rename to app/src/main/java/org/bepass/oblivion/component/TouchAwareSwitch.java index 82370658..2b17126c 100644 --- a/app/src/main/java/org/bepass/oblivion/TouchAwareSwitch.java +++ b/app/src/main/java/org/bepass/oblivion/component/TouchAwareSwitch.java @@ -1,4 +1,4 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.component; import android.annotation.SuppressLint; import android.content.Context; diff --git a/app/src/main/java/org/bepass/oblivion/ConnectionState.java b/app/src/main/java/org/bepass/oblivion/enums/ConnectionState.java similarity index 86% rename from app/src/main/java/org/bepass/oblivion/ConnectionState.java rename to app/src/main/java/org/bepass/oblivion/enums/ConnectionState.java index 97f64c07..d32aba9d 100644 --- a/app/src/main/java/org/bepass/oblivion/ConnectionState.java +++ b/app/src/main/java/org/bepass/oblivion/enums/ConnectionState.java @@ -1,4 +1,4 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.enums; public enum ConnectionState { CONNECTING, CONNECTED, DISCONNECTED; diff --git a/app/src/main/java/org/bepass/oblivion/SplitTunnelMode.java b/app/src/main/java/org/bepass/oblivion/enums/SplitTunnelMode.java similarity index 84% rename from app/src/main/java/org/bepass/oblivion/SplitTunnelMode.java rename to app/src/main/java/org/bepass/oblivion/enums/SplitTunnelMode.java index e4863f19..8d6d8833 100644 --- a/app/src/main/java/org/bepass/oblivion/SplitTunnelMode.java +++ b/app/src/main/java/org/bepass/oblivion/enums/SplitTunnelMode.java @@ -1,4 +1,6 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.enums; + +import org.bepass.oblivion.utils.FileManager; public enum SplitTunnelMode { DISABLED, diff --git a/app/src/main/java/org/bepass/oblivion/ConnectionStateChangeListener.java b/app/src/main/java/org/bepass/oblivion/interfaces/ConnectionStateChangeListener.java similarity index 50% rename from app/src/main/java/org/bepass/oblivion/ConnectionStateChangeListener.java rename to app/src/main/java/org/bepass/oblivion/interfaces/ConnectionStateChangeListener.java index e64472f5..442f0364 100644 --- a/app/src/main/java/org/bepass/oblivion/ConnectionStateChangeListener.java +++ b/app/src/main/java/org/bepass/oblivion/interfaces/ConnectionStateChangeListener.java @@ -1,4 +1,6 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.interfaces; + +import org.bepass.oblivion.enums.ConnectionState; public interface ConnectionStateChangeListener { void onChange(ConnectionState state); diff --git a/app/src/main/java/org/bepass/oblivion/SheetsCallBack.java b/app/src/main/java/org/bepass/oblivion/interfaces/SheetsCallBack.java similarity index 61% rename from app/src/main/java/org/bepass/oblivion/SheetsCallBack.java rename to app/src/main/java/org/bepass/oblivion/interfaces/SheetsCallBack.java index 2e32fe85..9632f71d 100644 --- a/app/src/main/java/org/bepass/oblivion/SheetsCallBack.java +++ b/app/src/main/java/org/bepass/oblivion/interfaces/SheetsCallBack.java @@ -1,4 +1,4 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.interfaces; public interface SheetsCallBack { void onSheetClosed(); diff --git a/app/src/main/java/org/bepass/oblivion/IPDetails.java b/app/src/main/java/org/bepass/oblivion/model/IPDetails.java similarity index 74% rename from app/src/main/java/org/bepass/oblivion/IPDetails.java rename to app/src/main/java/org/bepass/oblivion/model/IPDetails.java index 8996d5c0..7125e111 100644 --- a/app/src/main/java/org/bepass/oblivion/IPDetails.java +++ b/app/src/main/java/org/bepass/oblivion/model/IPDetails.java @@ -1,4 +1,4 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.model; public class IPDetails { public String ip; diff --git a/app/src/main/java/org/bepass/oblivion/OblivionVpnService.java b/app/src/main/java/org/bepass/oblivion/service/OblivionVpnService.java similarity index 98% rename from app/src/main/java/org/bepass/oblivion/OblivionVpnService.java rename to app/src/main/java/org/bepass/oblivion/service/OblivionVpnService.java index 682acf92..3071b196 100644 --- a/app/src/main/java/org/bepass/oblivion/OblivionVpnService.java +++ b/app/src/main/java/org/bepass/oblivion/service/OblivionVpnService.java @@ -1,4 +1,4 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.service; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED; @@ -26,6 +26,13 @@ import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; +import org.bepass.oblivion.enums.ConnectionState; +import org.bepass.oblivion.interfaces.ConnectionStateChangeListener; +import org.bepass.oblivion.R; +import org.bepass.oblivion.enums.SplitTunnelMode; +import org.bepass.oblivion.ui.MainActivity; +import org.bepass.oblivion.utils.FileManager; + import java.io.FileOutputStream; import java.io.IOException; import java.lang.ref.WeakReference; diff --git a/app/src/main/java/org/bepass/oblivion/QuickStartService.java b/app/src/main/java/org/bepass/oblivion/service/QuickStartService.java similarity index 98% rename from app/src/main/java/org/bepass/oblivion/QuickStartService.java rename to app/src/main/java/org/bepass/oblivion/service/QuickStartService.java index 48d61dc1..47d5c846 100644 --- a/app/src/main/java/org/bepass/oblivion/QuickStartService.java +++ b/app/src/main/java/org/bepass/oblivion/service/QuickStartService.java @@ -1,4 +1,4 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.service; import android.content.ComponentName; import android.content.Context; @@ -14,6 +14,8 @@ import androidx.annotation.RequiresApi; +import org.bepass.oblivion.R; + @RequiresApi(api = Build.VERSION_CODES.N) public class QuickStartService extends TileService { private final static String CONNECTION_OBSERVER_KEY = "quickstartToggleButton"; diff --git a/app/src/main/java/org/bepass/oblivion/ui/InfoActivity.java b/app/src/main/java/org/bepass/oblivion/ui/InfoActivity.java new file mode 100644 index 00000000..9dc6a050 --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/ui/InfoActivity.java @@ -0,0 +1,39 @@ +package org.bepass.oblivion.ui; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +import androidx.appcompat.app.AppCompatActivity; + +import org.bepass.oblivion.R; +import org.bepass.oblivion.base.BaseActivity; +import org.bepass.oblivion.databinding.ActivityInfoBinding; + +public class InfoActivity extends BaseActivity { + + @Override + protected int getLayoutResourceId() { + return R.layout.activity_info; + } + + @Override + protected int getStatusBarColor() { + return R.color.status_bar_color; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding.githubLayout.setOnClickListener(v -> { + Uri uri = Uri.parse("https://github.com/bepass-org/oblivion"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + }); + + binding.back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); + } +} diff --git a/app/src/main/java/org/bepass/oblivion/LogActivity.java b/app/src/main/java/org/bepass/oblivion/ui/LogActivity.java similarity index 74% rename from app/src/main/java/org/bepass/oblivion/LogActivity.java rename to app/src/main/java/org/bepass/oblivion/ui/LogActivity.java index de9e4cbd..1cb498e8 100644 --- a/app/src/main/java/org/bepass/oblivion/LogActivity.java +++ b/app/src/main/java/org/bepass/oblivion/ui/LogActivity.java @@ -1,4 +1,4 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.ui; import android.content.ClipData; import android.content.ClipboardManager; @@ -15,6 +15,10 @@ import com.google.android.material.button.MaterialButton; +import org.bepass.oblivion.R; +import org.bepass.oblivion.base.BaseActivity; +import org.bepass.oblivion.databinding.ActivityLogBinding; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -22,28 +26,31 @@ import java.util.Arrays; import java.util.Deque; -public class LogActivity extends AppCompatActivity { +public class LogActivity extends BaseActivity { private final Handler handler = new Handler(Looper.getMainLooper()); - private TextView logs; - private ScrollView logScrollView; private boolean isUserScrollingUp = false; private Runnable logUpdater; + @Override + protected int getLayoutResourceId() { + return R.layout.activity_log; + } + + @Override + protected int getStatusBarColor() { + return R.color.status_bar_color; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_log); - ImageView back = findViewById(R.id.back); - Button copyToClip = findViewById(R.id.copytoclip); - logs = findViewById(R.id.logs); - logScrollView = findViewById(R.id.logScrollView); - setupScrollListener(); - back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); + binding.back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); - copyToClip.setOnClickListener(v -> copyLast100LinesToClipboard()); + binding.copytoclip.setOnClickListener(v -> copyLast100LinesToClipboard()); logUpdater = new Runnable() { @Override @@ -55,9 +62,9 @@ public void run() { } private void setupScrollListener() { - logScrollView.getViewTreeObserver().addOnScrollChangedListener(() -> { - int scrollY = logScrollView.getScrollY(); - int maxScrollY = logs.getHeight() - logScrollView.getHeight(); + binding.logScrollView.getViewTreeObserver().addOnScrollChangedListener(() -> { + int scrollY = binding.logScrollView.getScrollY(); + int maxScrollY = binding.logs.getHeight() - binding.logScrollView.getHeight(); isUserScrollingUp = scrollY < maxScrollY; }); } @@ -85,9 +92,9 @@ private void readLogsFromFile() { String finalLog = sb.toString(); runOnUiThread(() -> { - logs.setText(finalLog); + binding.logs.setText(finalLog); if (!isUserScrollingUp) { - logScrollView.post(() -> logScrollView.fullScroll(ScrollView.FOCUS_DOWN)); + binding.logScrollView.post(() -> binding.logScrollView.fullScroll(ScrollView.FOCUS_DOWN)); } }); } catch (IOException e) { @@ -96,7 +103,7 @@ private void readLogsFromFile() { } private void copyLast100LinesToClipboard() { - String logText = logs.getText().toString(); + String logText = binding.logs.getText().toString(); String[] logLines = logText.split("\n"); int totalLines = logLines.length; diff --git a/app/src/main/java/org/bepass/oblivion/MainActivity.java b/app/src/main/java/org/bepass/oblivion/ui/MainActivity.java similarity index 56% rename from app/src/main/java/org/bepass/oblivion/MainActivity.java rename to app/src/main/java/org/bepass/oblivion/ui/MainActivity.java index 37a380f3..3fda035c 100644 --- a/app/src/main/java/org/bepass/oblivion/MainActivity.java +++ b/app/src/main/java/org/bepass/oblivion/ui/MainActivity.java @@ -1,7 +1,7 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.ui; -import static org.bepass.oblivion.OblivionVpnService.startVpnService; -import static org.bepass.oblivion.OblivionVpnService.stopVpnService; +import static org.bepass.oblivion.service.OblivionVpnService.startVpnService; +import static org.bepass.oblivion.service.OblivionVpnService.stopVpnService; import android.Manifest; import android.content.Context; @@ -15,10 +15,6 @@ import android.os.Bundle; import android.os.Handler; import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; import android.widget.Toast; import androidx.activity.OnBackPressedCallback; @@ -26,66 +22,68 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; -import com.google.android.material.floatingactionbutton.FloatingActionButton; +import org.bepass.oblivion.enums.ConnectionState; +import org.bepass.oblivion.utils.FileManager; +import org.bepass.oblivion.utils.LocaleHandler; +import org.bepass.oblivion.service.OblivionVpnService; +import org.bepass.oblivion.utils.PublicIPUtils; +import org.bepass.oblivion.R; +import org.bepass.oblivion.base.StateAwareBaseActivity; +import org.bepass.oblivion.databinding.ActivityMainBinding; +import org.bepass.oblivion.utils.ThemeHelper; import java.util.HashSet; import java.util.Set; -public class MainActivity extends StateAwareBaseActivity { - private TouchAwareSwitch switchButton; - private TextView stateText, publicIP; - private ProgressBar ipProgressBar; - private PublicIPUtils pIPUtils; +public class MainActivity extends StateAwareBaseActivity { private long backPressedTime; private Toast backToast; private LocaleHandler localeHandler; private final Handler handler = new Handler(); + public static void start(Context context) { + Intent starter = new Intent(context, MainActivity.class); + starter.putExtra("origin", context.getClass().getSimpleName()); + context.startActivity(starter); + } + + @Override + protected int getLayoutResourceId() { + return R.layout.activity_main; + } + + @Override + protected int getStatusBarColor() { + return R.color.status_bar_color; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); cleanOrMigrateSettings(); - - // Get the global PublicIPUtils instance - pIPUtils = PublicIPUtils.getInstance(getApplicationContext()); - + setupUI(); // Initialize the LocaleHandler and set the locale localeHandler = new LocaleHandler(this); - // Set the layout of the main activity - setContentView(R.layout.activity_main); + setupVPNConnection(); + // Request permission to create push notifications + requestNotificationPermission(); - // Handle language change based on floatingActionButton value - FloatingActionButton floatingActionButton = findViewById(R.id.floatingActionButton); - floatingActionButton.setOnClickListener(v -> localeHandler.showLanguageSelectionDialog(()-> - localeHandler.restartActivity(this))); - // Views - ImageView infoIcon = findViewById(R.id.info_icon); - ImageView logIcon = findViewById(R.id.bug_icon); - ImageView settingsIcon = findViewById(R.id.setting_icon); - - FrameLayout switchButtonFrame = findViewById(R.id.switch_button_frame); - switchButton = findViewById(R.id.switch_button); - stateText = findViewById(R.id.state_text); - publicIP = findViewById(R.id.publicIP); - ipProgressBar = findViewById(R.id.ipProgressBar); - - // Set listeners - infoIcon.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, InfoActivity.class))); - logIcon.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, LogActivity.class))); - settingsIcon.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, SettingsActivity.class))); - switchButtonFrame.setOnClickListener(v -> switchButton.toggle()); - - // Request for VPN creation - ActivityResultLauncher vpnPermissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - if (result.getResultCode() != RESULT_OK) { - Toast.makeText(this, "Really!?", Toast.LENGTH_LONG).show(); - } - switchButton.setChecked(false); - }); - // Listener for toggle switch - switchButton.setOnCheckedChangeListener((view, isChecked) -> { + // Set the behaviour of the back button + handleBackPress(); + } + + private void setupVPNConnection() { + ActivityResultLauncher vpnPermissionLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() != RESULT_OK) { + Toast.makeText(this, "Really!?", Toast.LENGTH_LONG).show(); + } + binding.switchButton.setChecked(false); + }); + + binding.switchButton.setOnCheckedChangeListener((view, isChecked) -> { if (!isChecked) { if (!lastKnownConnectionState.isDisconnected()) { stopVpnService(this); @@ -97,49 +95,27 @@ protected void onCreate(Bundle savedInstanceState) { vpnPermissionLauncher.launch(vpnIntent); return; } - // Handle the case when the VPN is in the connecting state and the user clicks to abort if (lastKnownConnectionState.isConnecting()) { stopVpnService(this); return; } - // Start the VPN service if it's disconnected if (lastKnownConnectionState.isDisconnected()) { startVpnService(this); } - // To check is Internet Connection is available - handler.postDelayed(new Runnable() { - @Override - public void run() { - if(!lastKnownConnectionState.isDisconnected()) { - checkInternetConnectionAndDisconnectVPN(); - handler.postDelayed(this, 3000); // Check every 3 seconds - } - } - }, 5000); // Start checking after 5 seconds + monitorInternetConnection(); }); + } - - // Request permission to create push notifications - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - ActivityResultLauncher pushNotificationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { - if (!isGranted) { - Toast.makeText(this, "Permission denied", Toast.LENGTH_LONG).show(); - } - }); - pushNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); - } - - // Set the behaviour of the back button + private void handleBackPress() { getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { - // Custom back pressed logic here if (backPressedTime + 2000 > System.currentTimeMillis()) { if (backToast != null) backToast.cancel(); - finish(); // or super.handleOnBackPressed() if you want to keep default behavior alongside + finish(); } else { if (backToast != null) - backToast.cancel(); // Cancel the existing toast to avoid stacking + backToast.cancel(); backToast = Toast.makeText(MainActivity.this, "برای خروج، دوباره بازگشت را فشار دهید.", Toast.LENGTH_SHORT); backToast.show(); } @@ -148,6 +124,39 @@ public void handleOnBackPressed() { }); } + private void requestNotificationPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ActivityResultLauncher pushNotificationPermissionLauncher = registerForActivityResult( + new ActivityResultContracts.RequestPermission(), isGranted -> { + if (!isGranted) { + Toast.makeText(this, "Permission denied", Toast.LENGTH_LONG).show(); + } + }); + pushNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); + } + } + + private void setupUI() { + binding.floatingActionButton.setOnClickListener(v -> localeHandler.showLanguageSelectionDialog(() -> + localeHandler.restartActivity(this))); + binding.infoIcon.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, InfoActivity.class))); + binding.bugIcon.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, LogActivity.class))); + binding.settingIcon.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, SettingsActivity.class))); + binding.switchButtonFrame.setOnClickListener(v -> binding.switchButton.toggle()); + } + + private void monitorInternetConnection() { + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (!lastKnownConnectionState.isDisconnected()) { + checkInternetConnectionAndDisconnectVPN(); + handler.postDelayed(this, 3000); // Check every 3 seconds + } + } + }, 5000); // Start checking after 5 seconds + } + // Check internet connectivity private boolean isConnectedToInternet() { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); @@ -175,6 +184,7 @@ private void checkInternetConnectionAndDisconnectVPN() { stopVpnService(this); } } + protected void cleanOrMigrateSettings() { // Get the global FileManager instance FileManager fileManager = FileManager.getInstance(getApplicationContext()); @@ -205,40 +215,52 @@ protected void cleanOrMigrateSettings() { @NonNull @Override - String getKey() { + public String getKey() { return "mainActivity"; } @Override - void onConnectionStateChange(ConnectionState state) { + public void onConnectionStateChange(ConnectionState state) { switch (state) { case DISCONNECTED: - publicIP.setVisibility(View.GONE); - stateText.setText(R.string.notConnected); - ipProgressBar.setVisibility(View.GONE); - switchButton.setEnabled(true); - switchButton.setChecked(false, false); + updateUIForDisconnectedState(); break; case CONNECTING: - stateText.setText(R.string.connecting); - publicIP.setVisibility(View.GONE); - ipProgressBar.setVisibility(View.VISIBLE); - switchButton.setChecked(true, false); - switchButton.setEnabled(true); + updateUIForConnectingState(); break; case CONNECTED: - switchButton.setEnabled(true); - stateText.setText(R.string.connected); - switchButton.setChecked(true, false); - ipProgressBar.setVisibility(View.GONE); - pIPUtils.getIPDetails((details) -> { - if (details.ip != null) { - String ipString = details.ip+ " " + details.flag; - publicIP.setText(ipString); - publicIP.setVisibility(View.VISIBLE); - } - }); + updateUIForConnectedState(); break; } } + + private void updateUIForDisconnectedState() { + binding.publicIP.setVisibility(View.GONE); + binding.stateText.setText(R.string.notConnected); + binding.ipProgressBar.setVisibility(View.GONE); + binding.switchButton.setEnabled(true); + binding.switchButton.setChecked(false, false); + } + + private void updateUIForConnectingState() { + binding.stateText.setText(R.string.connecting); + binding.publicIP.setVisibility(View.GONE); + binding.ipProgressBar.setVisibility(View.VISIBLE); + binding.switchButton.setChecked(true, false); + binding.switchButton.setEnabled(true); + } + + private void updateUIForConnectedState() { + binding.switchButton.setEnabled(true); + binding.stateText.setText(R.string.connected); + binding.switchButton.setChecked(true, false); + binding.ipProgressBar.setVisibility(View.GONE); + PublicIPUtils.getInstance().getIPDetails((details) -> { + if (details.ip != null) { + String ipString = details.ip + " " + details.flag; + binding.publicIP.setText(ipString); + binding.publicIP.setVisibility(View.VISIBLE); + } + }); + } } diff --git a/app/src/main/java/org/bepass/oblivion/ui/SettingsActivity.java b/app/src/main/java/org/bepass/oblivion/ui/SettingsActivity.java new file mode 100644 index 00000000..8c1144fe --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/ui/SettingsActivity.java @@ -0,0 +1,201 @@ +package org.bepass.oblivion.ui; + +import static org.bepass.oblivion.utils.BatteryOptimizationKt.isBatteryOptimizationEnabled; +import static org.bepass.oblivion.utils.BatteryOptimizationKt.showBatteryOptimizationDialog; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Pair; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.Spinner; + +import androidx.activity.OnBackPressedCallback; + +import org.bepass.oblivion.enums.ConnectionState; +import org.bepass.oblivion.utils.CountryUtils; +import org.bepass.oblivion.EditSheet; +import org.bepass.oblivion.utils.FileManager; +import org.bepass.oblivion.utils.LocaleHelper; +import org.bepass.oblivion.service.OblivionVpnService; +import org.bepass.oblivion.R; +import org.bepass.oblivion.interfaces.SheetsCallBack; +import org.bepass.oblivion.base.ApplicationLoader; +import org.bepass.oblivion.base.StateAwareBaseActivity; +import org.bepass.oblivion.databinding.ActivitySettingsBinding; +import org.bepass.oblivion.utils.ThemeHelper; + +public class SettingsActivity extends StateAwareBaseActivity { + private FileManager fileManager; + private CheckBox.OnCheckedChangeListener psiphonListener; + private CheckBox.OnCheckedChangeListener goolListener; + + private void setCheckBoxWithoutTriggeringListener(CheckBox checkBox, boolean isChecked, CheckBox.OnCheckedChangeListener listener) { + checkBox.setOnCheckedChangeListener(null); // Temporarily detach the listener + checkBox.setChecked(isChecked); // Set the checked state + checkBox.setOnCheckedChangeListener(listener); // Reattach the listener + } + + @Override + protected int getLayoutResourceId() { + return R.layout.activity_settings; + } + + @Override + protected int getStatusBarColor() { + return R.color.status_bar_color; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + fileManager = FileManager.getInstance(this); + + if (isBatteryOptimizationEnabled(this)) { + binding.batteryOptimizationLayout.setOnClickListener(view -> { + showBatteryOptimizationDialog(this); + }); + } else { + binding.batteryOptimizationLayout.setVisibility(View.GONE); + binding.batteryOptLine.setVisibility(View.GONE); + } + + + binding.back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); + + getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + if (StateAwareBaseActivity.getRequireRestartVpnService()) { + StateAwareBaseActivity.setRequireRestartVpnService(false); + if (!lastKnownConnectionState.isDisconnected()) { + OblivionVpnService.stopVpnService(SettingsActivity.this); + OblivionVpnService.startVpnService(SettingsActivity.this); + } + } + finish(); + } + }); + + SheetsCallBack sheetsCallBack = this::settingBasicValuesFromSPF; + // Listen to Changes + binding.endpointLayout.setOnClickListener(v -> (new EditSheet(this, getString(R.string.endpointText), "endpoint", sheetsCallBack)).start()); + binding.portLayout.setOnClickListener(v -> (new EditSheet(this, getString(R.string.portTunText), "port", sheetsCallBack)).start()); + binding.licenseLayout.setOnClickListener(v -> (new EditSheet(this, getString(R.string.licenseText), "license", sheetsCallBack)).start()); + + ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.countries, R.layout.country_item_layout); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + binding.country.setAdapter(adapter); + + binding.country.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String name = parent.getItemAtPosition(position).toString(); + Pair codeAndName = CountryUtils.getCountryCode(ApplicationLoader.getAppCtx(), name); + fileManager.set("USERSETTING_country", codeAndName.first); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + + binding.splitTunnelLayout.setOnClickListener(v -> startActivity(new Intent(this, SplitTunnelActivity.class))); + + // Set Current Values + settingBasicValuesFromSPF(); + + binding.goolLayout.setOnClickListener(v -> binding.gool.setChecked(!binding.gool.isChecked())); + binding.lanLayout.setOnClickListener(v -> binding.lan.setChecked(!binding.lan.isChecked())); + binding.psiphonLayout.setOnClickListener(v -> binding.psiphon.setChecked(!binding.psiphon.isChecked())); + + binding.lan.setOnCheckedChangeListener((buttonView, isChecked) -> fileManager.set("USERSETTING_lan", isChecked)); + // Initialize the listeners + psiphonListener = (buttonView, isChecked) -> { + fileManager.set("USERSETTING_psiphon", isChecked); + if (isChecked && binding.gool.isChecked()) { + setCheckBoxWithoutTriggeringListener(binding.gool, false, goolListener); + fileManager.set("USERSETTING_gool", false); + } + binding.countryLayout.setAlpha(isChecked ? 1f : 0.2f); + binding.country.setEnabled(isChecked); + }; + + goolListener = (buttonView, isChecked) -> { + fileManager.set("USERSETTING_gool", isChecked); + if (isChecked && binding.psiphon.isChecked()) { + setCheckBoxWithoutTriggeringListener(binding.psiphon, false, psiphonListener); + fileManager.set("USERSETTING_psiphon", false); + binding.countryLayout.setAlpha(0.2f); + binding.country.setEnabled(false); + } + }; + binding.checkBoxDarkMode.setChecked(ThemeHelper.getInstance().getCurrentTheme() == ThemeHelper.Theme.DARK); + binding.checkBoxDarkMode.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + ThemeHelper.getInstance().select(ThemeHelper.Theme.DARK); + } else { + ThemeHelper.getInstance().select(ThemeHelper.Theme.LIGHT); + } + } + }); + // Set the listeners to the checkboxes + binding.psiphon.setOnCheckedChangeListener(psiphonListener); + binding.gool.setOnCheckedChangeListener(goolListener); + } + + private int getIndexFromName(Spinner spinner, String name) { + String ccn = CountryUtils.getCountryName(name); + String newname = LocaleHelper.restoreText(this, ccn); + for (int i = 0; i < spinner.getCount(); i++) { + if (spinner.getItemAtPosition(i).toString().equalsIgnoreCase(newname)) { + return i; + } + } + + return 0; + } + + private void settingBasicValuesFromSPF() { + binding.endpoint.setText(fileManager.getString("USERSETTING_endpoint")); + binding.port.setText(fileManager.getString("USERSETTING_port")); + binding.license.setText(fileManager.getString("USERSETTING_license")); + + String countryCode = fileManager.getString("USERSETTING_country"); + int index = 0; + if (!countryCode.isEmpty()) { + LocaleHelper.goEn(this); + String countryName = CountryUtils.getCountryName(countryCode); + index = getIndexFromName(binding.country, countryName); + LocaleHelper.restoreLocale(this); + } + binding.country.setSelection(index); + + binding.psiphon.setChecked(fileManager.getBoolean("USERSETTING_psiphon")); + binding.lan.setChecked(fileManager.getBoolean("USERSETTING_lan")); + binding.gool.setChecked(fileManager.getBoolean("USERSETTING_gool")); + + + if (!binding.psiphon.isChecked()) { + binding.countryLayout.setAlpha(0.2f); + binding.country.setEnabled(false); + } else { + binding.countryLayout.setAlpha(1f); + binding.country.setEnabled(true); + } + } + + @Override + public String getKey() { + return "settingsActivity"; + } + + @Override + public void onConnectionStateChange(ConnectionState state) { + } +} diff --git a/app/src/main/java/org/bepass/oblivion/ui/SplashScreenActivity.java b/app/src/main/java/org/bepass/oblivion/ui/SplashScreenActivity.java new file mode 100644 index 00000000..441aa1e7 --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/ui/SplashScreenActivity.java @@ -0,0 +1,70 @@ +package org.bepass.oblivion.ui; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.os.Handler; +import android.view.View; + +import org.bepass.oblivion.R; +import org.bepass.oblivion.base.BaseActivity; +import org.bepass.oblivion.databinding.ActivitySplashScreenBinding; + +/** + * A simple splash screen activity that shows a splash screen for a short duration before navigating + * to the main activity. This class extends {@link BaseActivity} and uses data binding. + */ +@SuppressLint("CustomSplashScreen") +public class SplashScreenActivity extends BaseActivity { + + /** + * Returns the layout resource ID for the splash screen activity. + * + * @return The layout resource ID. + */ + @Override + protected int getLayoutResourceId() { + return R.layout.activity_splash_screen; + } + + @Override + protected int getStatusBarColor() { + return R.color.status_bar_color; + } + + /** + * Called when the activity is first created. This method sets up the splash screen and + * schedules the transition to the main activity. + * + * @param savedInstanceState If the activity is being re-initialized after previously being shut down + * then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding.setHandler(new ClickHandler()); + // 1 second + int SHORT_SPLASH_DISPLAY_LENGTH = 1000; + new Handler().postDelayed(() -> { + MainActivity.start(this); + finish(); + }, SHORT_SPLASH_DISPLAY_LENGTH); + } + + /** + * A click handler for handling user interactions with the splash screen. + */ + public class ClickHandler { + + /** + * Called when the root view is pressed. This method immediately navigates to the main activity + * and finishes the splash screen activity. + * + * @param view The view that was clicked. + */ + public void OnRootPressed(View view) { + MainActivity.start(SplashScreenActivity.this); + finish(); + } + } + +} diff --git a/app/src/main/java/org/bepass/oblivion/SplitTunnelActivity.java b/app/src/main/java/org/bepass/oblivion/ui/SplitTunnelActivity.java similarity index 53% rename from app/src/main/java/org/bepass/oblivion/SplitTunnelActivity.java rename to app/src/main/java/org/bepass/oblivion/ui/SplitTunnelActivity.java index add17f9a..3a73d510 100644 --- a/app/src/main/java/org/bepass/oblivion/SplitTunnelActivity.java +++ b/app/src/main/java/org/bepass/oblivion/ui/SplitTunnelActivity.java @@ -1,41 +1,44 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.ui; import android.os.Bundle; import android.view.View; -import android.widget.ImageView; -import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.recyclerview.widget.ConcatAdapter; -import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.progressindicator.CircularProgressIndicator; +import org.bepass.oblivion.BypassListAppsAdapter; +import org.bepass.oblivion.enums.ConnectionState; +import org.bepass.oblivion.utils.FileManager; +import org.bepass.oblivion.R; +import org.bepass.oblivion.enums.SplitTunnelMode; +import org.bepass.oblivion.SplitTunnelOptionsAdapter; +import org.bepass.oblivion.base.StateAwareBaseActivity; +import org.bepass.oblivion.databinding.ActivitySplitTunnelBinding; -public class SplitTunnelActivity extends StateAwareBaseActivity { - private RecyclerView appsRecycler; - private CircularProgressIndicator progress; +public class SplitTunnelActivity extends StateAwareBaseActivity { @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Sets the contents from the layout - setContentView(R.layout.activity_split_tunnel); + protected int getLayoutResourceId() { + return R.layout.activity_split_tunnel; + } - // Find UI elements and assign them to vars - ImageView back = findViewById(R.id.back); - appsRecycler = findViewById(R.id.appsRecycler); - progress = findViewById(R.id.progress); + @Override + protected int getStatusBarColor() { + return R.color.status_bar_color; + } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); // Handles the back button behaviour - back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); + binding.back.setOnClickListener(v -> getOnBackPressedDispatcher().onBackPressed()); // Set up the app list BypassListAppsAdapter bypassListAppsAdapter = new BypassListAppsAdapter(this, loading -> { - appsRecycler.setVisibility(loading ? View.INVISIBLE : View.VISIBLE); - if (loading) progress.show(); - else progress.hide(); + binding.appsRecycler.setVisibility(loading ? View.INVISIBLE : View.VISIBLE); + if (loading) binding.progress.show(); + else binding.progress.hide(); }); // Signal the need to restart the VPN service on app selection change @@ -57,13 +60,13 @@ public void shouldShowSystemApps(boolean show) { } }); - appsRecycler.setAdapter(new ConcatAdapter(optionsAdapter, bypassListAppsAdapter)); + binding.appsRecycler.setAdapter(new ConcatAdapter(optionsAdapter, bypassListAppsAdapter)); } @Override - void onConnectionStateChange(ConnectionState state) { } + public void onConnectionStateChange(ConnectionState state) { } @NonNull @Override - String getKey() { return "splitTunnelActivity"; } + public String getKey() { return "splitTunnelActivity"; } } diff --git a/app/src/main/java/org/bepass/oblivion/BatteryOptimization.kt b/app/src/main/java/org/bepass/oblivion/utils/BatteryOptimization.kt similarity index 97% rename from app/src/main/java/org/bepass/oblivion/BatteryOptimization.kt rename to app/src/main/java/org/bepass/oblivion/utils/BatteryOptimization.kt index 2c1364b2..b6df2a69 100644 --- a/app/src/main/java/org/bepass/oblivion/BatteryOptimization.kt +++ b/app/src/main/java/org/bepass/oblivion/utils/BatteryOptimization.kt @@ -1,4 +1,4 @@ -package org.bepass.oblivion +package org.bepass.oblivion.utils import android.annotation.SuppressLint import android.app.AlertDialog @@ -11,6 +11,7 @@ import android.provider.Settings import android.view.LayoutInflater import android.widget.Button import android.widget.TextView +import org.bepass.oblivion.R /** * Checks if the app is running in restricted background mode. diff --git a/app/src/main/java/org/bepass/oblivion/utils/ColorUtils.java b/app/src/main/java/org/bepass/oblivion/utils/ColorUtils.java new file mode 100644 index 00000000..324bf27d --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/utils/ColorUtils.java @@ -0,0 +1,11 @@ +package org.bepass.oblivion.utils; + +import android.graphics.Color; + +public class ColorUtils { + + public static boolean isColorDark(int color) { + double luminance = (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255; + return luminance <= 0.5; + } +} diff --git a/app/src/main/java/org/bepass/oblivion/CountryUtils.java b/app/src/main/java/org/bepass/oblivion/utils/CountryUtils.java similarity index 94% rename from app/src/main/java/org/bepass/oblivion/CountryUtils.java rename to app/src/main/java/org/bepass/oblivion/utils/CountryUtils.java index 61053149..a59fbed4 100644 --- a/app/src/main/java/org/bepass/oblivion/CountryUtils.java +++ b/app/src/main/java/org/bepass/oblivion/utils/CountryUtils.java @@ -1,12 +1,11 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.utils; import android.content.Context; -import android.content.res.Configuration; import android.content.res.Resources; -import android.util.Log; import android.util.Pair; -import java.util.Arrays; +import org.bepass.oblivion.R; + import java.util.Locale; public class CountryUtils { diff --git a/app/src/main/java/org/bepass/oblivion/utils/FileManager.java b/app/src/main/java/org/bepass/oblivion/utils/FileManager.java new file mode 100644 index 00000000..c59df4a3 --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/utils/FileManager.java @@ -0,0 +1,240 @@ +package org.bepass.oblivion.utils; + +import android.content.Context; +import android.content.SharedPreferences; + +import java.util.Set; + +/** + * This class provides a singleton instance for managing user data within the application. + * It utilizes SharedPreferences to store and retrieve various data types securely. + */ +public class FileManager { + private static FileManager instance; + private final SharedPreferences sharedPreferences; + + public static class KeyHolder { + public static String DARK_MODE = "setting_dark_mode"; + } + /** + * Private constructor to enforce singleton pattern. + * + * @param context The application context used to access SharedPreferences. + */ + private FileManager(Context context) { + sharedPreferences = context.getSharedPreferences("UserData", Context.MODE_PRIVATE); + } + + /** + * Public method to retrieve the singleton instance of FileManager. + * This ensures only one instance exists throughout the application. + * + * @param context The application context required for SharedPreferences. + * @return The singleton instance of FileManager. + */ + public static synchronized FileManager getInstance(Context context) { + if (instance == null) { + instance = new FileManager(context.getApplicationContext()); + } + return instance; + } + + // =========================================================================== + // Methods for setting various data types + + /** + * Stores a String value associated with a key in SharedPreferences. + * + * @param name The key to identify the data. + * @param value The String value to be stored. + */ + public void set(String name, String value) { + sharedPreferences.edit().putString(name, value).apply(); + } + + /** + * Stores a boolean value associated with a key in SharedPreferences. + * + * @param name The key to identify the data. + * @param value The boolean value to be stored. + */ + public void set(String name, boolean value) { + sharedPreferences.edit().putBoolean(name, value).apply(); + } + + /** + * Stores a Set of Strings associated with a key in SharedPreferences. + * + * @param name The key to identify the data. + * @param value The Set of Strings to be stored. + */ + public void set(String name, Set value) { + sharedPreferences.edit().putStringSet(name, value).apply(); + } + + /** + * Stores an integer value associated with a key in SharedPreferences. + * + * @param name The key to identify the data. + * @param value The integer value to be stored. + */ + public void set(String name, int value) { + sharedPreferences.edit().putInt(name, value).apply(); + } + + /** + * Stores a float value associated with a key in SharedPreferences. + * + * @param name The key to identify the data. + * @param value The float value to be stored. + */ + public void set(String name, float value) { + sharedPreferences.edit().putFloat(name, value).apply(); + } + + /** + * Stores a long value associated with a key in SharedPreferences. + * + * @param name The key to identify the data. + * @param value The long value to be stored. + */ + public void set(String name, long value) { + sharedPreferences.edit().putLong(name, value).apply(); + } + + /** + * Stores a double value associated with a key in SharedPreferences. + * Double values are converted to longs for storage due to limitations. + * + * @param name The key to identify the data. + * @param value The double value to be stored. + */ + public void setDouble(String name, double value) { + sharedPreferences.edit().putLong(name, Double.doubleToRawLongBits(value)).apply(); + } + + /** + * Retrieves a String value associated with a key from SharedPreferences. + * If the key doesn't exist, an empty string is returned. + * + * @param name The key to identify the data. + * @return The String value associated with the key, + * * @return "" (empty string) if the key doesn't exist. + */ + public String getString(String name) { + return sharedPreferences.getString(name, ""); + } + + /** + * Retrieves a String value associated with a key from SharedPreferences. + * If the key doesn't exist, the provided default value is returned. + * + * @param name The key to identify the data. + * @param defaultValue The default value to return if the key doesn't exist. + * @return The String value associated with the key, or the default value if not found. + */ + public String getString(String name, String defaultValue) { + return sharedPreferences.getString(name, defaultValue); + } + + /** + * Retrieves a Set of Strings associated with a key from SharedPreferences. + * If the key doesn't exist, the provided default set is returned. + * + * @param name The key to identify the data. + * @param def The default Set of Strings to return if the key doesn't exist. + * @return The Set of Strings associated with the key, or the default set if not found. + */ + public Set getStringSet(String name, Set def) { + return sharedPreferences.getStringSet(name, def); + } + + /** + * Retrieves a boolean value associated with a key from SharedPreferences. + * If the key doesn't exist, false is returned. + * + * @param name The key to identify the data. + * @return The boolean value associated with the key, or false if not found. + */ + public boolean getBoolean(String name) { + return sharedPreferences.getBoolean(name, false); + } + + /** + * Retrieves a boolean value associated with a key from SharedPreferences. + * If the key doesn't exist, the provided default value is returned. + * + * @param name The key to identify the data. + * @param defaultValue The default boolean value to return if the key doesn't exist. + * @return The boolean value associated with the key, or the default value if not found. + */ + public boolean getBoolean(String name, boolean defaultValue) { + return sharedPreferences.getBoolean(name, defaultValue); + } + + /** + * Retrieves an integer value associated with a key from SharedPreferences. + * If the key doesn't exist, 0 is returned. + * + * @param name The key to identify the data. + * @return The integer value associated with the key, or 0 if not found. + */ + public int getInt(String name) { + return sharedPreferences.getInt(name, 0); + } + + /** + * Retrieves a float value associated with a key from SharedPreferences. + * If the key doesn't exist, 0.0f is returned. + * + * @param name The key to identify the data. + * @return The float value associated with the key, or 0.0f if not found. + */ + public float getFloat(String name) { + return sharedPreferences.getFloat(name, 0f); + } + + /** + * Retrieves a long value associated with a key from SharedPreferences. + * If the key doesn't exist, 0L is returned. + * + * @param name The key to identify the data. + * @return The long value associated with the key, or 0L if not found. + */ + public long getLong(String name) { + return sharedPreferences.getLong(name, 0L); + } + + /** + * Retrieves a double value associated with a key from SharedPreferences. + * Since doubles are stored as longs, the retrieved long is converted back to a double. + * If the key doesn't exist, 0.0 is returned. + * + * @param name The key to identify the data. + * @return The double value associated with the key, or 0.0 if not found. + */ + public double getDouble(String name) { + return Double.longBitsToDouble(sharedPreferences.getLong(name, 0)); + } + + // =========================================================================== + // Methods for handling logs (optional, based on your needs) + + /** + * Resets the log stored in SharedPreferences under the key "APP_LOG". + * This removes any existing log message and sets it to an empty string. + */ + public void resetLog() { + sharedPreferences.edit().putString("APP_LOG", "").apply(); + } + + /** + * Adds a new log message to SharedPreferences under the key "APP_LOG". + * The existing log message (if any) will be overwritten. + * + * @param log The String message to be stored as the application log. + */ + public void addLog(String log) { + sharedPreferences.edit().putString("APP_LOG", log).apply(); + } +} diff --git a/app/src/main/java/org/bepass/oblivion/utils/LocalController.java b/app/src/main/java/org/bepass/oblivion/utils/LocalController.java new file mode 100644 index 00000000..6deac01e --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/utils/LocalController.java @@ -0,0 +1,62 @@ +package org.bepass.oblivion.utils; + +import android.content.Context; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Build; + +import androidx.annotation.RequiresApi; +import androidx.core.content.ContextCompat; + +import org.bepass.oblivion.base.ApplicationLoader; + +public class LocalController { + + public static String getString(int resourceId) { + return ApplicationLoader.getAppCtx().getResources().getString(resourceId); + } + + + @RequiresApi(api = Build.VERSION_CODES.O) + public static Typeface getFont(int resourceId) { + return ApplicationLoader.getAppCtx().getResources().getFont(resourceId); + } + + public static Typeface getFont(String fontName) { + try { + return Typeface.createFromAsset(ApplicationLoader.getAppCtx().getAssets(), "fonts/" + fontName + ".ttf"); + // Use the typeface as needed + } catch (Exception e) { + e.printStackTrace(); + return null; + // Handle the exception, log or display an error message + } + } + + private static int getResourceIdByName(String name, String type) { + return ApplicationLoader.getAppCtx().getResources().getIdentifier( + name, + type, + ApplicationLoader.getAppCtx().getPackageName() + ); + } + + + public static int getColor(int resource) { + return ContextCompat.getColor(ApplicationLoader.getAppCtx(), resource); + } + + public static float getDimen(int resource) { + return ApplicationLoader.getAppCtx().getResources().getDimension(resource); + } + + + public static float getDimensionPixelSize(int resource) { + return ApplicationLoader.getAppCtx().getResources().getDimensionPixelSize(resource); + } + + public static Drawable getDrawable(int resource) { + return ContextCompat.getDrawable(ApplicationLoader.getAppCtx(), resource); + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/bepass/oblivion/LocaleHandler.java b/app/src/main/java/org/bepass/oblivion/utils/LocaleHandler.java similarity index 98% rename from app/src/main/java/org/bepass/oblivion/LocaleHandler.java rename to app/src/main/java/org/bepass/oblivion/utils/LocaleHandler.java index 170b843f..b7d156b3 100644 --- a/app/src/main/java/org/bepass/oblivion/LocaleHandler.java +++ b/app/src/main/java/org/bepass/oblivion/utils/LocaleHandler.java @@ -1,4 +1,4 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.utils; import android.annotation.SuppressLint; import android.app.Activity; @@ -10,7 +10,6 @@ import android.content.res.Resources; import android.os.Build; import android.util.DisplayMetrics; -import android.util.Log; import java.util.Locale; diff --git a/app/src/main/java/org/bepass/oblivion/LocaleHelper.java b/app/src/main/java/org/bepass/oblivion/utils/LocaleHelper.java similarity index 96% rename from app/src/main/java/org/bepass/oblivion/LocaleHelper.java rename to app/src/main/java/org/bepass/oblivion/utils/LocaleHelper.java index f627130e..5156fb87 100644 --- a/app/src/main/java/org/bepass/oblivion/LocaleHelper.java +++ b/app/src/main/java/org/bepass/oblivion/utils/LocaleHelper.java @@ -1,10 +1,10 @@ -package org.bepass.oblivion; +package org.bepass.oblivion.utils; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; -import android.util.Log; -import android.util.Pair; + +import org.bepass.oblivion.R; import java.util.Locale; diff --git a/app/src/main/java/org/bepass/oblivion/utils/PublicIPUtils.java b/app/src/main/java/org/bepass/oblivion/utils/PublicIPUtils.java new file mode 100644 index 00000000..23404ebc --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/utils/PublicIPUtils.java @@ -0,0 +1,129 @@ +package org.bepass.oblivion.utils; + +import android.content.Context; +import android.os.Handler; +import android.util.Log; + +import com.vdurmont.emoji.EmojiManager; + +import org.bepass.oblivion.model.IPDetails; +import org.bepass.oblivion.base.ApplicationLoader; +import org.json.JSONObject; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * A utility class for fetching public IP details including the country and its flag. + */ +public class PublicIPUtils { + private static final String TAG = "PublicIPUtils"; + private static PublicIPUtils instance; + private final FileManager fm; + private static final int TIMEOUT_SECONDS = 5; + private static final int RETRY_DELAY_MILLIS = 1000; + private static final int TIMEOUT_MILLIS = 30 * 1000; + private final ScheduledExecutorService scheduler; + private static final String URL_COUNTRY_API = "https://api.country.is/"; + + /** + * Private constructor to enforce singleton pattern. + * + * @param context The application context. + */ + private PublicIPUtils(Context context) { + this.fm = FileManager.getInstance(context); + this.scheduler = Executors.newSingleThreadScheduledExecutor(); + } + + /** + * Public method to get the singleton instance. + * + * @return The singleton instance of PublicIPUtils. + */ + public static synchronized PublicIPUtils getInstance() { + if (instance == null) { + instance = new PublicIPUtils(ApplicationLoader.getAppCtx()); + } + return instance; + } + + /** + * Fetches public IP details including the country and its flag. This method uses a + * retry mechanism with a specified timeout to ensure reliable fetching of the IP details. + * + * @param callback The callback to handle the IP details once fetched. + */ + public void getIPDetails(IPDetailsCallback callback) { + Handler handler = new Handler(); + long startTime = System.currentTimeMillis(); + IPDetails details = new IPDetails(); + + Log.d(TAG, "Starting getIPDetails process"); + + scheduler.schedule(() -> { + while (System.currentTimeMillis() - startTime < TIMEOUT_MILLIS) { // 30 seconds + Log.d(TAG, "Attempting to fetch IP details"); + try { + int socksPort = Integer.parseInt(fm.getString("USERSETTING_port")); + Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("localhost", socksPort)); + + OkHttpClient client = new OkHttpClient.Builder() + .proxy(proxy) + .connectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) // 5 seconds connection timeout + .readTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) // 5 seconds read timeout + .build(); + + Request request = new Request.Builder() + .url(URL_COUNTRY_API) + .build(); + + try (Response response = client.newCall(request).execute()) { + if (response.body() != null) { + JSONObject jsonData = new JSONObject(Objects.requireNonNull(response.body()).string()); + details.ip = jsonData.getString("ip"); + details.country = jsonData.getString("country"); + details.flag = EmojiManager.getForAlias(details.country.toLowerCase(Locale.ROOT)).getUnicode(); + Log.d(TAG, "IP details retrieved successfully"); + } + handler.post(() -> callback.onDetailsReceived(details)); + return; // Exit the loop if details retrieved + } catch (Exception e) { + Log.e(TAG, "Error parsing the response or setting details", e); + } + } catch (Exception e) { + Log.e(TAG, "Error fetching IP details", e); + } finally { + // Schedule next retry even if an error occurred + Log.d(TAG, "Scheduling next retry after delay"); + scheduler.schedule(() -> getIPDetails(callback), RETRY_DELAY_MILLIS, TimeUnit.MILLISECONDS); + } + } + Log.d(TAG, "Timeout reached without successful IP details retrieval"); + // Timeout reached, no details retrieved + handler.post(() -> callback.onDetailsReceived(details)); + }, 0, TimeUnit.MILLISECONDS); // Schedule initial attempt immediately + } + + /** + * Callback interface for receiving IP details. + */ + public interface IPDetailsCallback { + + /** + * Called when IP details are received. + * + * @param details The IP details. + */ + void onDetailsReceived(IPDetails details); + } +} diff --git a/app/src/main/java/org/bepass/oblivion/utils/SystemUtils.java b/app/src/main/java/org/bepass/oblivion/utils/SystemUtils.java new file mode 100644 index 00000000..54da7252 --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/utils/SystemUtils.java @@ -0,0 +1,42 @@ +package org.bepass.oblivion.utils; + +import android.app.Activity; +import android.content.res.Resources; +import android.os.Build; +import android.util.Log; +import android.view.View; +import android.view.Window; + +import androidx.annotation.RequiresApi; +import androidx.core.content.ContextCompat; + +public class SystemUtils { + + @RequiresApi(api = Build.VERSION_CODES.M) + public static void setStatusBarColor(Activity activity, int color, boolean isDark) { + try { + int statusBarColor = ContextCompat.getColor(activity, color); + activity.getWindow().setStatusBarColor(statusBarColor); + + // Adjust status bar icon color based on theme + changeStatusBarIconColor(activity, isDark); + } catch (Resources.NotFoundException e) { + Log.e("ThemeHelper", "Failed to find color resource for status bar", e); + } + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private static void changeStatusBarIconColor(Activity activity, boolean isDark) { + Window window = activity.getWindow(); + View decorView = window.getDecorView(); + int flags = decorView.getSystemUiVisibility(); + if (!isDark) { + // Make status bar icons dark (e.g., for light background) + flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + // Make status bar icons light (e.g., for dark background) + flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + decorView.setSystemUiVisibility(flags); + } +} diff --git a/app/src/main/java/org/bepass/oblivion/utils/ThemeHelper.java b/app/src/main/java/org/bepass/oblivion/utils/ThemeHelper.java new file mode 100644 index 00000000..c427925e --- /dev/null +++ b/app/src/main/java/org/bepass/oblivion/utils/ThemeHelper.java @@ -0,0 +1,88 @@ +package org.bepass.oblivion.utils; + +import android.content.Context; + +import androidx.appcompat.app.AppCompatDelegate; + +import org.bepass.oblivion.base.ApplicationLoader; +public class ThemeHelper { + + // Enum to define theme constants + public enum Theme { + LIGHT(AppCompatDelegate.MODE_NIGHT_NO), + DARK(AppCompatDelegate.MODE_NIGHT_YES), + FOLLOW_SYSTEM(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM), + UNSPECIFIED(AppCompatDelegate.MODE_NIGHT_UNSPECIFIED), + AUTO_BATTERY(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY); + + private final int nightMode; + + Theme(int nightMode) { + this.nightMode = nightMode; + } + + @AppCompatDelegate.NightMode + public int getNightMode() { + return nightMode; + } + + // Get Theme by night mode value + public static Theme fromNightMode(@AppCompatDelegate.NightMode int nightMode) { + for (Theme theme : values()) { + if (theme.getNightMode() == nightMode) { + return theme; + } + } + return LIGHT; // Default to LIGHT if not found + } + } + + private static ThemeHelper instance; + private Theme currentTheme = Theme.LIGHT; + + // Private constructor to prevent instantiation + private ThemeHelper() { + } + + // Method to get the single instance of ThemeHelper + public static synchronized ThemeHelper getInstance() { + if (instance == null) { + instance = new ThemeHelper(); + } + return instance; + } + + + public void init() { + int themeMode = FileManager.getInstance(ApplicationLoader.getAppCtx()).getInt(FileManager.KeyHolder.DARK_MODE); + currentTheme = Theme.fromNightMode(themeMode); + applyTheme(); + } + + /** + * Applies the current theme. + */ + public void applyTheme() { + AppCompatDelegate.setDefaultNightMode(currentTheme.getNightMode()); + } + + /** + * Sets the selected theme and applies it. + * + * @param theme the theme to be applied + */ + public void select(Theme theme) { + currentTheme = theme; + FileManager.getInstance(ApplicationLoader.getAppCtx()).set(FileManager.KeyHolder.DARK_MODE , theme.nightMode); + applyTheme(); + } + + /** + * Retrieves the current theme. + * + * @return the current theme + */ + public Theme getCurrentTheme() { + return currentTheme; + } +} \ No newline at end of file diff --git a/app/src/main/res/color/checkbox_tint.xml b/app/src/main/res/color/checkbox_tint.xml index c16dcc3a..850f2990 100644 --- a/app/src/main/res/color/checkbox_tint.xml +++ b/app/src/main/res/color/checkbox_tint.xml @@ -1,7 +1,7 @@ - + - + diff --git a/app/src/main/res/drawable-anydpi/bottom_sheet_closer.xml b/app/src/main/res/drawable-anydpi/bottom_sheet_closer.xml index 448713ee..bc61223e 100644 --- a/app/src/main/res/drawable-anydpi/bottom_sheet_closer.xml +++ b/app/src/main/res/drawable-anydpi/bottom_sheet_closer.xml @@ -2,6 +2,6 @@ - + diff --git a/app/src/main/res/drawable/button.xml b/app/src/main/res/drawable/button.xml index ec3a2e1d..0adab4ef 100644 --- a/app/src/main/res/drawable/button.xml +++ b/app/src/main/res/drawable/button.xml @@ -3,6 +3,6 @@ + android:color="#00FFFFFF" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_info.xml b/app/src/main/res/layout/activity_info.xml index 3368e06b..6db6a8db 100644 --- a/app/src/main/res/layout/activity_info.xml +++ b/app/src/main/res/layout/activity_info.xml @@ -1,111 +1,119 @@ - - - - - + - - - - - - - - - + + + + + + + + + + + + + + android:layout_gravity="end" + android:layout_marginTop="16dp" + android:fontFamily="@font/shabnammedium" + android:paddingHorizontal="12dp" + android:text='@string/aboutAppDesc' + android:textAlignment="center" + android:textColor="@color/subtitle_color" + android:textSize="16sp" + tools:ignore="RtlCompat" /> - + android:layout_marginHorizontal="16dp" + android:layout_marginTop="32dp" + android:background="@drawable/about_item" + android:gravity="center" + android:paddingVertical="8dp"> - + - + + + + + + + - + + diff --git a/app/src/main/res/layout/activity_log.xml b/app/src/main/res/layout/activity_log.xml index bef38151..a3042a5e 100644 --- a/app/src/main/res/layout/activity_log.xml +++ b/app/src/main/res/layout/activity_log.xml @@ -1,76 +1,83 @@ - + xmlns:tools="http://schemas.android.com/tools"> - + + + + + android:layout_height="match_parent" + android:background="@color/background" + tools:context="org.bepass.oblivion.ui.LogActivity"> - + - - + - + + - - + android:layout_height="0dp" + android:layout_marginHorizontal="16dp" + android:layout_marginTop="16dp" + app:layout_constraintTop_toBottomOf="@id/top_bar" + app:layout_constraintBottom_toTopOf="@id/copytoclip" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> -