├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── themes.xml
│ │ │ ├── drawable
│ │ │ │ ├── back.webp
│ │ │ │ ├── logo.webp
│ │ │ │ ├── greeting.webp
│ │ │ │ ├── notification_logo.xml
│ │ │ │ ├── ic_mahdisml_emblem.xml
│ │ │ │ ├── ic_mahdisml_emblem_splash.xml
│ │ │ │ ├── heart.xml
│ │ │ │ ├── ic_settings.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ └── ic_mahdisml_logo.xml
│ │ │ ├── font
│ │ │ │ ├── roboto_medium.ttf
│ │ │ │ └── ubuntu_medium.ttf
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── xml
│ │ │ │ ├── network_security_config.xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── ic_launcher-playstore.png
│ │ ├── java
│ │ │ └── dev
│ │ │ │ └── mahdisml
│ │ │ │ └── webimmortalguards
│ │ │ │ ├── Core.kt
│ │ │ │ ├── data
│ │ │ │ └── SettingsRepository.kt
│ │ │ │ ├── net
│ │ │ │ ├── VpnAdapter.kt
│ │ │ │ ├── VpnController.kt
│ │ │ │ └── VpnService.kt
│ │ │ │ ├── Strings.kt
│ │ │ │ ├── ui
│ │ │ │ ├── theme
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ ├── Type.kt
│ │ │ │ │ └── Theme.kt
│ │ │ │ └── MainViewModel.kt
│ │ │ │ ├── core
│ │ │ │ ├── AppCore.kt
│ │ │ │ └── Chayi.kt
│ │ │ │ └── MainActivity.kt
│ │ ├── AndroidManifest.xml
│ │ └── python
│ │ │ └── smlwb.py
│ ├── test
│ │ └── java
│ │ │ └── dev
│ │ │ └── mahdisml
│ │ │ └── webimmortalguards
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── dev
│ │ └── mahdisml
│ │ └── webimmortalguards
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── .idea
├── .name
├── .gitignore
├── compiler.xml
├── kotlinc.xml
├── vcs.xml
├── misc.xml
├── gradle.xml
└── inspectionProfiles
│ └── Project_Default.xml
├── showcase.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── LICENSE
├── README.md
├── gradle.properties
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Web Immortal Guards
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/showcase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/showcase.png
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Web Immortal Guards
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/back.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/drawable/back.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/drawable/logo.webp
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/greeting.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/drawable/greeting.webp
--------------------------------------------------------------------------------
/app/src/main/res/font/roboto_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/font/roboto_medium.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/ubuntu_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/font/ubuntu_medium.ttf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mahdisml/WebImmortalGuards/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #16131A
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon May 22 00:26:33 GMT+03:30 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/Core.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards
2 |
3 | import android.content.Context
4 | import dev.mahdisml.webimmortalguards.core.AppCore
5 |
6 | class Core(private val ctx: Context): AppCore(ctx,"prefsKey") {
7 | companion object{}
8 | init {}
9 | }
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Web Immortal Guards"
16 | include ':app'
17 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 127.0.0.1
10 |
11 |
--------------------------------------------------------------------------------
/app/src/test/java/dev/mahdisml/webimmortalguards/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notification_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/data/SettingsRepository.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards.data
2 |
3 | import android.content.Context
4 | import dev.mahdisml.webimmortalguards.Core
5 |
6 | class SettingsRepository (ctx:Context) {
7 | private val core = Core(ctx)
8 |
9 | suspend fun getGreetingsState():Boolean{
10 | core.load("greeting_passed").let {
11 | return if (it != null) {
12 | return (it == "true")
13 | } else {
14 | false
15 | }
16 | }
17 | }
18 | suspend fun setGreetingsState(state:Boolean){
19 | core.save("greeting_passed",state.toString())
20 | }
21 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mahdisml_emblem.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/dev/mahdisml/webimmortalguards/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("dev.mahdisml.webimmortalguards", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mahdisml_emblem_splash.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/heart.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Mahdi Safarmohammadloo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/net/VpnAdapter.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards.net
2 |
3 | import com.chaquo.python.PyObject
4 | import com.chaquo.python.Python
5 | import kotlinx.coroutines.CoroutineScope
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.Job
8 | import kotlinx.coroutines.cancel
9 | import kotlinx.coroutines.launch
10 |
11 | class VpnAdapter () {
12 | private var pythonJob: Job? = null
13 | private var smlwb: PyObject? = null
14 |
15 | fun start(){
16 | stop()
17 | try {
18 | pythonJob = CoroutineScope(Dispatchers.IO).launch {
19 | try {
20 | smlwb = Python.getInstance().getModule("smlwb")
21 | smlwb!!.callAttr("main")
22 | } catch (e: Exception) {
23 | stop()
24 | }
25 | }
26 | } catch (e: Exception) {
27 | stop()
28 | }
29 | }
30 | fun stop(){
31 | pythonJob?.cancel("stop")
32 | smlwb = null
33 | pythonJob = null
34 | }
35 |
36 | fun isOn():Boolean{
37 | return (pythonJob != null) && (smlwb != null)
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/Strings.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards
2 |
3 | object Strings {
4 | const val version_code = 1
5 | const val version = "1.0.0"
6 | const val app_name = "Web Immortal Guards"
7 | const val greeting_image_des = "Greeting"
8 | const val greeting_text = "Web Immortal Guards is an experimental tool from Persia that protects you from DNS manipulation, It's not a VPN, and It will not hide your IP address. USE IT ONLY WHEN YOU NEED IT, NOT ALL THE TIME."
9 | const val greeting_button_text = "OK, I got it."
10 | const val home_image_des = "Web Immortal Guards"
11 | const val home_setting_des = "Settings"
12 | const val home_text_on = "Service is ON"
13 | const val home_text_off = "Service is OFF"
14 | const val setting_back = "Back"
15 | const val setting_mahdisml = "Mahdisml Logo"
16 | const val setting_1 = "DNS Server (DoH) :"
17 | const val setting_1_1 = "Auto Mode"
18 |
19 | //constraints
20 |
21 | const val package_name = "dev.mahdisml.webimmortalguards"
22 | const val channel_id = "web_guards_channel"
23 | const val vpn_address = "10.0.0.2"
24 | const val start_action = "dev.mahdisml.webimmortalguards.action.startforeground"
25 | const val stop_action = "dev.mahdisml.webimmortalguards.action.stopforeground"
26 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Web Immortal Guards [](https://github.com/mahdisml/WebImmortalGuards/blob/master/LICENSE)
6 |
7 | Web Immortal Guards is an experimental tool from Persia that protects you from DNS manipulation.
8 |
9 | It's not a VPN, and It will not hide your IP address.
10 |
11 | USE IT ONLY WHEN YOU NEED IT, NOT ALL THE TIME.
12 |
13 | Python infrastructure and DoH : [https://github.com/GFW-knocker/gfw_resist_HTTPS_proxy](https://github.com/GFW-knocker/gfw_resist_HTTPS_proxy)
14 |
15 | # Available in GooglePlay
16 |
17 |
18 |
19 |
20 | another way to download : [https://apkpure.com/web-immortal-guards/dev.mahdisml.webimmortalguards](https://apkpure.com/web-immortal-guards/dev.mahdisml.webimmortalguards)
21 |
22 | # Persian
23 |
24 | این اپلیکیشن فیلترشکن نیست و نمیتونه آی پی شمارو مخفی کنه
25 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards.ui.theme
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.ui.graphics.Color
5 |
6 | val Purple80 = Color(0xFFD0BCFF)
7 | val PurpleGrey80 = Color(0xFFCCC2DC)
8 | val Pink80 = Color(0xFFEFB8C8)
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
12 | val Teal200 = Color(0xFF03DAC5)
13 | val MainColor = Color(0xFF0396FF)
14 | val MainLight = Color(0xFF0357FF)
15 | val Black = Color(0xFF000000)
16 | val Gray = Color(0xFF888888)
17 | val GrayDark = Color(0xFF444444)
18 | val GrayLight = Color(0xFFCCCCCC)
19 | val Milky = Color(0xFFF5F5F7)
20 | val White = Color(0xFFFFFFFF)
21 | val Red = Color(0xFFFF0000)
22 | val Green = Color(0xFF00FF00)
23 | val Blue = Color(0xFF0000FF)
24 | val Yellow = Color(0xFFFFFF00)
25 | val Cyan = Color(0xFF00FFFF)
26 | val Magenta = Color(0xFFFF00FF)
27 |
28 | val Silver = Color(0xFFF5F5F7)
29 | val SilverDark = Color(0xFFCFCFCF)
30 | val SilverDarker = Color(0xFF757575)
31 | val DeepDark = Color(0xFF171717)
32 | val GoodDark = Color(0xFF222222)
33 | val lightDark = Color(0xFF5F5F5F)
34 | val Gold = Color(0xFFFFC800)
35 | val GoldDark = Color(0xFFBF8B29)
36 | val GoldDarker = Color(0x59FFC800)
37 | val TransparentDark = Color(0x93000000)
38 | val TransparentGoodDark = Color(0x1F000000)
39 | val Cinder = Color(0xFF16131A)
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.Font
6 | import androidx.compose.ui.text.font.FontFamily
7 | import androidx.compose.ui.text.font.FontWeight
8 | import androidx.compose.ui.unit.sp
9 | import dev.mahdisml.webimmortalguards.R
10 |
11 | val Ubuntu_Regular = FontFamily(
12 | Font(R.font.ubuntu_medium)
13 | )
14 | val Roboto_Medium = FontFamily(
15 | Font(R.font.roboto_medium)
16 | )
17 |
18 | // Set of Material typography styles to start with
19 | val Typography = Typography(
20 | bodyLarge = TextStyle(
21 | fontFamily = Roboto_Medium,
22 | fontWeight = FontWeight.Normal,
23 | fontSize = 16.sp,
24 | lineHeight = 24.sp,
25 | letterSpacing = 0.5.sp
26 | )
27 | /* Other default text styles to override
28 | titleLarge = TextStyle(
29 | fontFamily = FontFamily.Default,
30 | fontWeight = FontWeight.Normal,
31 | fontSize = 22.sp,
32 | lineHeight = 28.sp,
33 | letterSpacing = 0.sp
34 | ),
35 | labelSmall = TextStyle(
36 | fontFamily = FontFamily.Default,
37 | fontWeight = FontWeight.Medium,
38 | fontSize = 11.sp,
39 | lineHeight = 16.sp,
40 | letterSpacing = 0.5.sp
41 | )
42 | */
43 | )
44 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/net/VpnController.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards.net
2 |
3 | import android.app.Activity
4 | import android.app.NotificationManager
5 | import android.content.Context
6 | import android.content.Intent
7 | import androidx.core.content.ContextCompat
8 | import dev.mahdisml.webimmortalguards.Strings.package_name
9 | import dev.mahdisml.webimmortalguards.Strings.start_action
10 | import dev.mahdisml.webimmortalguards.Strings.stop_action
11 | import kotlinx.coroutines.CoroutineScope
12 | import kotlinx.coroutines.cancel
13 |
14 | class VpnController (private val ctx: Context) {
15 | private var controllerScope: CoroutineScope? = null
16 |
17 | fun startVpn(){
18 | controllerScope?.cancel("stop")
19 | val startServiceIntent = Intent(ctx, VpnService::class.java)
20 | startServiceIntent.action = start_action
21 | ContextCompat.startForegroundService(ctx, startServiceIntent)
22 | }
23 | fun endVpn(){
24 | controllerScope?.cancel("stop")
25 | val stopServiceIntent = Intent(ctx, VpnService::class.java)
26 | stopServiceIntent.action = stop_action
27 | ContextCompat.startForegroundService(ctx, stopServiceIntent)
28 | }
29 | fun isOn(): Boolean {
30 | var isFound = false
31 | val notificationManager = (ctx as Activity).getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
32 | val notifications = notificationManager.activeNotifications
33 | notifications?.let {
34 | for (i in notifications){
35 | if (package_name == i.packageName){
36 | isFound = true
37 | }
38 | }
39 | }
40 | return isFound
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.dynamicDarkColorScheme
8 | import androidx.compose.material3.dynamicLightColorScheme
9 | import androidx.compose.material3.lightColorScheme
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.SideEffect
12 | import androidx.compose.ui.graphics.toArgb
13 | import androidx.compose.ui.platform.LocalContext
14 | import androidx.compose.ui.platform.LocalView
15 | import androidx.core.view.WindowCompat
16 |
17 | private val LightColorScheme = lightColorScheme(
18 | primary = GoldDark,
19 | secondary = Gold,
20 | tertiary = SilverDark
21 |
22 | /* Other default colors to override
23 | background = Color(0xFFFFFBFE),
24 | surface = Color(0xFFFFFBFE),
25 | onPrimary = Color.White,
26 | onSecondary = Color.White,
27 | onTertiary = Color.White,
28 | onBackground = Color(0xFF1C1B1F),
29 | onSurface = Color(0xFF1C1B1F),
30 | */
31 | )
32 |
33 | @Composable
34 | fun WebImmortalGuardsTheme(
35 | darkTheme: Boolean = isSystemInDarkTheme(),
36 | // Dynamic color is available on Android 12+
37 | dynamicColor: Boolean = false,
38 | content: @Composable () -> Unit
39 | ) {
40 | val colorScheme = when {
41 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
42 | val context = LocalContext.current
43 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
44 | }
45 |
46 | darkTheme -> LightColorScheme
47 | else -> LightColorScheme
48 | }
49 | val view = LocalView.current
50 | if (!view.isInEditMode) {
51 | SideEffect {
52 | val window = (view.context as Activity).window
53 | window.statusBarColor = Cinder.toArgb()
54 | window.navigationBarColor = Cinder.toArgb()
55 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
56 | }
57 | }
58 |
59 | MaterialTheme(
60 | colorScheme = colorScheme,
61 | typography = Typography,
62 | content = content
63 | )
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/ui/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards.ui
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.setValue
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.viewModelScope
10 | import dev.mahdisml.webimmortalguards.data.SettingsRepository
11 | import dev.mahdisml.webimmortalguards.net.VpnController
12 | import kotlinx.coroutines.Job
13 | import kotlinx.coroutines.launch
14 |
15 | class MainViewModel : ViewModel() {
16 | var pageState :Int? by mutableStateOf(null) //0=greeting,1=home,2=settings
17 | private var checkPageStateJob: Job? = null
18 | private var greetingsDoneJob: Job? = null
19 | private var vpnController : VpnController? = null
20 |
21 | var vpnState :Boolean by mutableStateOf(false)
22 | private set
23 |
24 | fun setVpnState(ctx:Context){
25 | val prepared = android.net.VpnService.prepare(ctx)
26 | if (prepared == null) {
27 | if (vpnController == null) {
28 | vpnController = VpnController(ctx)
29 | }
30 | vpnState = if (vpnState) {
31 | vpnController?.endVpn()
32 | false
33 | } else {
34 | vpnController?.startVpn()
35 | true
36 | }
37 | }else{
38 | (ctx as Activity).startActivityForResult(prepared,0x0F)
39 | }
40 | }
41 | fun checkVpnState(ctx:Context){
42 | viewModelScope.launch {
43 | if(vpnController == null){
44 | vpnController = VpnController(ctx)
45 | }
46 | vpnController?.let {
47 | vpnState = it.isOn()
48 | }
49 | }
50 | }
51 |
52 | fun checkPageState(ctx:Context){
53 | checkPageStateJob?.cancel()
54 | checkPageStateJob = viewModelScope.launch{
55 | pageState = if (SettingsRepository(ctx).getGreetingsState()){
56 | 1
57 | }else{
58 | 0
59 | }
60 |
61 | }
62 | }
63 | fun greetingsDone(ctx:Context){
64 | greetingsDoneJob?.cancel()
65 | greetingsDoneJob = viewModelScope.launch{
66 | SettingsRepository(ctx).setGreetingsState(true)
67 | checkPageState(ctx)
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'com.chaquo.python'
5 | }
6 |
7 | android {
8 | namespace 'dev.mahdisml.webimmortalguards'
9 | compileSdk 33
10 |
11 | defaultConfig {
12 | applicationId "dev.mahdisml.webimmortalguards"
13 | minSdk 29
14 | targetSdk 33
15 | versionCode 1
16 | versionName "1.0"
17 |
18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
19 | vectorDrawables {
20 | useSupportLibrary true
21 | }
22 | ndk {
23 | abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
24 | }
25 | python {
26 | version "3.8"
27 | pyc {
28 | src false
29 | }
30 | pip {
31 | install "dnspython"
32 | install "requests"
33 | }
34 | }
35 | }
36 |
37 | buildTypes {
38 | release {
39 | minifyEnabled true
40 | shrinkResources true
41 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
42 | }
43 | }
44 | compileOptions {
45 | sourceCompatibility JavaVersion.VERSION_1_8
46 | targetCompatibility JavaVersion.VERSION_1_8
47 | }
48 | kotlinOptions {
49 | jvmTarget = '1.8'
50 | }
51 | buildFeatures {
52 | compose true
53 | }
54 | composeOptions {
55 | kotlinCompilerExtensionVersion '1.4.7'
56 | }
57 | packagingOptions {
58 | resources {
59 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
60 | }
61 | }
62 | }
63 |
64 | dependencies {
65 |
66 | implementation 'androidx.core:core-ktx:1.10.1'
67 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
68 | implementation 'androidx.activity:activity-compose:1.7.1'
69 | implementation platform('androidx.compose:compose-bom:2023.05.01')
70 | implementation 'androidx.compose.ui:ui'
71 | implementation 'androidx.compose.ui:ui-graphics'
72 | implementation 'androidx.compose.ui:ui-tooling-preview'
73 | implementation 'androidx.compose.material3:material3'
74 | implementation 'androidx.core:core-ktx:1.10.1'
75 | testImplementation 'junit:junit:4.13.2'
76 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
77 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
78 | androidTestImplementation platform('androidx.compose:compose-bom:2023.05.01')
79 | androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
80 | debugImplementation 'androidx.compose.ui:ui-tooling'
81 | debugImplementation 'androidx.compose.ui:ui-test-manifest'
82 |
83 | //core
84 | implementation("androidx.datastore:datastore-preferences:1.0.0")
85 |
86 | implementation 'androidx.core:core-splashscreen:1.0.1'
87 | implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1'
88 |
89 |
90 |
91 |
92 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/net/VpnService.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards.net
2 |
3 | import android.app.NotificationChannel
4 | import android.app.NotificationManager
5 | import android.app.PendingIntent
6 | import android.app.PendingIntent.FLAG_CANCEL_CURRENT
7 | import android.app.PendingIntent.FLAG_IMMUTABLE
8 | import android.app.PendingIntent.FLAG_NO_CREATE
9 | import android.app.PendingIntent.FLAG_UPDATE_CURRENT
10 | import android.content.Intent
11 | import android.graphics.BitmapFactory
12 | import android.net.ProxyInfo
13 | import android.net.VpnService
14 | import android.os.IBinder
15 | import android.os.ParcelFileDescriptor
16 | import android.util.Log
17 | import androidx.core.app.NotificationCompat
18 | import dev.mahdisml.webimmortalguards.MainActivity
19 | import dev.mahdisml.webimmortalguards.R
20 | import dev.mahdisml.webimmortalguards.Strings.app_name
21 | import dev.mahdisml.webimmortalguards.Strings.channel_id
22 | import dev.mahdisml.webimmortalguards.Strings.home_text_on
23 | import dev.mahdisml.webimmortalguards.Strings.package_name
24 | import dev.mahdisml.webimmortalguards.Strings.stop_action
25 | import dev.mahdisml.webimmortalguards.Strings.vpn_address
26 | import java.io.IOException
27 | import kotlin.system.exitProcess
28 |
29 |
30 | class VpnService : VpnService() {
31 |
32 | private val vpnAdapter = VpnAdapter()
33 | private var vpnInterface: ParcelFileDescriptor? = null
34 |
35 | override fun onCreate() {
36 | super.onCreate()
37 | startVpn()
38 | }
39 |
40 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
41 | if (intent != null){
42 | intent.action?.let {
43 | if (it == stop_action){
44 | stopVpn()
45 | }
46 | }
47 | }
48 | return super.onStartCommand(intent, flags, startId)
49 | }
50 | override fun onBind(intent: Intent?): IBinder? {
51 | return null
52 | }
53 |
54 | override fun onDestroy() {
55 | super.onDestroy()
56 | stopVpn()
57 | }
58 |
59 | private fun stopVpn(){
60 | vpnAdapter.stop()
61 | try {
62 | vpnInterface?.close()
63 | } catch (e: IOException) {
64 | Log.e("Sml","parcelFileDescriptor.close()", e)
65 | }
66 | try {
67 | stopForeground(STOP_FOREGROUND_REMOVE)
68 | } catch (_: Exception) { }
69 | try {
70 | stopSelf()
71 | } catch (_: Exception) { }
72 |
73 | }
74 | private fun startVpn(){
75 | try {
76 | if (vpnInterface == null) {
77 | val channel = NotificationChannel(
78 | channel_id,
79 | app_name,
80 | NotificationManager.IMPORTANCE_DEFAULT
81 | )
82 | (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
83 | val intent = packageManager.getLaunchIntentForPackage(package_name)
84 | val pendingIntent = if (intent != null) {
85 | PendingIntent.getActivity(
86 | this,
87 | 0,
88 | intent,
89 | FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
90 | )
91 | }else{
92 | PendingIntent.getActivity(
93 | this, 0,
94 | Intent(this, MainActivity::class.java), FLAG_IMMUTABLE or FLAG_NO_CREATE or FLAG_UPDATE_CURRENT or FLAG_CANCEL_CURRENT
95 | )
96 | }
97 | val notification = NotificationCompat.Builder(this, channel_id)
98 | .setContentTitle(app_name)
99 | .setSmallIcon(R.drawable.notification_logo)
100 | .setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.notification_logo))
101 | .setContentText(home_text_on)
102 | .setContentIntent(pendingIntent).build()
103 | startForeground(1, notification)
104 | vpnAdapter.start()
105 | val builder = Builder()
106 | builder.setUnderlyingNetworks(null)
107 | builder.setMetered(false)
108 | builder.addDisallowedApplication(package_name)
109 | builder.setHttpProxy(ProxyInfo.buildDirectProxy("127.0.0.1",4525))
110 | builder.addAddress(vpn_address, 32)
111 | builder.addDnsServer("8.8.8.8")
112 |
113 | vpnInterface = builder.setSession(app_name).establish()
114 | }
115 | } catch (e: Exception) {
116 | Log.e("Sml", "error", e)
117 | exitProcess(0)
118 | }
119 | }
120 | fun isOn(): Boolean {
121 | return vpnAdapter.isOn()
122 | }
123 |
124 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/core/AppCore.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards.core
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Activity
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.graphics.Typeface
8 | import android.net.Uri
9 | import android.view.WindowManager
10 | import android.widget.ImageView
11 | import android.widget.TextView
12 | import androidx.compose.foundation.clickable
13 | import androidx.compose.foundation.interaction.MutableInteractionSource
14 | import androidx.compose.foundation.layout.Box
15 | import androidx.compose.foundation.layout.Row
16 | import androidx.compose.foundation.layout.fillMaxWidth
17 | import androidx.compose.foundation.text.BasicTextField
18 | import androidx.compose.foundation.text.KeyboardActions
19 | import androidx.compose.foundation.text.KeyboardOptions
20 | import androidx.compose.material3.LocalTextStyle
21 | import androidx.compose.material3.Text
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.runtime.DisposableEffect
24 | import androidx.compose.runtime.remember
25 | import androidx.compose.runtime.rememberUpdatedState
26 | import androidx.compose.ui.Alignment
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.composed
29 | import androidx.compose.ui.draw.drawBehind
30 | import androidx.compose.ui.graphics.Brush
31 | import androidx.compose.ui.graphics.Color
32 | import androidx.compose.ui.graphics.Paint
33 | import androidx.compose.ui.graphics.SolidColor
34 | import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
35 | import androidx.compose.ui.graphics.toArgb
36 | import androidx.compose.ui.platform.LocalLifecycleOwner
37 | import androidx.compose.ui.semantics.Role
38 | import androidx.compose.ui.text.TextLayoutResult
39 | import androidx.compose.ui.text.TextStyle
40 | import androidx.compose.ui.text.input.VisualTransformation
41 | import androidx.compose.ui.unit.Dp
42 | import androidx.compose.ui.unit.dp
43 | import androidx.core.content.ContextCompat
44 | import androidx.datastore.core.DataStore
45 | import androidx.datastore.preferences.core.Preferences
46 | import androidx.datastore.preferences.core.edit
47 | import androidx.datastore.preferences.core.stringPreferencesKey
48 | import androidx.datastore.preferences.preferencesDataStore
49 | import androidx.lifecycle.Lifecycle
50 | import androidx.lifecycle.LifecycleEventObserver
51 | import androidx.lifecycle.LifecycleOwner
52 | import kotlinx.coroutines.CoroutineScope
53 | import kotlinx.coroutines.Job
54 | import kotlinx.coroutines.flow.Flow
55 | import kotlinx.coroutines.flow.first
56 | import kotlinx.coroutines.flow.map
57 | import kotlinx.coroutines.launch
58 | import kotlinx.coroutines.sync.Mutex
59 |
60 | val Context.dataStore: DataStore by preferencesDataStore(name = "prefs")
61 |
62 | open class AppCore (
63 | private val ctx: Context,
64 | private val preferencesKey:String
65 | ) {
66 | companion object {
67 | @SuppressLint("UnnecessaryComposedModifier")
68 | fun Modifier.coloredShadow(
69 | color: Color,
70 | alpha: Float = 0.2f,
71 | borderRadius: Dp = 0.dp,
72 | shadowRadius: Dp = 20.dp,
73 | offsetY: Dp = 0.dp,
74 | offsetX: Dp = 0.dp
75 | ) = composed {
76 | val shadowColor = color.copy(alpha = alpha).toArgb()
77 | val transparent = color.copy(alpha= 0f).toArgb()
78 | this.drawBehind {
79 | this.drawIntoCanvas {
80 | val paint = Paint()
81 | val frameworkPaint = paint.asFrameworkPaint()
82 | frameworkPaint.color = transparent
83 | frameworkPaint.setShadowLayer(
84 | shadowRadius.toPx(),
85 | offsetX.toPx(),
86 | offsetY.toPx(),
87 | shadowColor
88 | )
89 | it.drawRoundRect(
90 | 0f,
91 | 0f,
92 | this.size.width,
93 | this.size.height,
94 | borderRadius.toPx(),
95 | borderRadius.toPx(),
96 | paint
97 | )
98 | }
99 | }
100 | }
101 | @Composable
102 | fun OnLifecycleEvent(onEvent: (owner: LifecycleOwner, event: Lifecycle.Event) -> Unit) {
103 | val eventHandler = rememberUpdatedState(onEvent)
104 | val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current)
105 |
106 | DisposableEffect(lifecycleOwner.value) {
107 | val lifecycle = lifecycleOwner.value.lifecycle
108 | val observer = LifecycleEventObserver { owner, event ->
109 | eventHandler.value(owner, event)
110 | }
111 | lifecycle.addObserver(observer)
112 | onDispose {
113 | lifecycle.removeObserver(observer)
114 | }
115 | }
116 | }
117 |
118 | }
119 |
120 | //DataStore
121 |
122 | suspend fun saveIfNotExist(name: String, data: String){
123 | ctx.dataStore.edit { prefs ->
124 | if (prefs[stringPreferencesKey(name)] == null) {
125 | Chayi.encrypt(data,preferencesKey)?.let { it2 ->
126 | prefs[stringPreferencesKey(name)] = it2
127 | }
128 | }
129 | }
130 | }
131 | suspend fun save(name:String, data:String){
132 | ctx.dataStore.edit { prefs ->
133 | Chayi.encrypt(data,preferencesKey)?.let { it2 ->
134 | prefs[stringPreferencesKey(name)] = it2
135 | }
136 | }
137 | }
138 | suspend fun load(data:String) : String?{
139 | return loadFlow(data).first()
140 | }
141 | suspend fun remove(name:String){
142 | ctx.dataStore.edit { prefs ->
143 | prefs.remove(stringPreferencesKey(name))
144 | }
145 | }
146 |
147 | private fun loadFlow(name: String) : Flow {
148 | return ctx.dataStore.data.map { prefs ->
149 | prefs[stringPreferencesKey(name)].let {
150 | if (it != null){
151 | Chayi.decrypt(it,preferencesKey)
152 | }else {
153 | null
154 | }
155 | }
156 | }
157 | }
158 | suspend fun clearAllData(){
159 | ctx.dataStore.edit {
160 | it.clear()
161 | }
162 | }
163 |
164 | // Display
165 |
166 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/core/Chayi.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards.core
2 |
3 | object Chayi {
4 |
5 | // Mahdi Safarmohammadloo
6 | // www.MahdiSML.dev
7 |
8 | private const val DELTA = -0x61c88647
9 |
10 | fun encrypt(data: String, key: String): String? {
11 | val bytes = encryptToByte(data, key) ?: return null
12 | return Base64.encode(bytes)
13 | }
14 |
15 | fun decrypt(data: String, key: String): String? {
16 | return try {
17 | val bytes = decrypt(Base64.decode(data), key) ?: return null
18 | bytes.decodeToString()
19 | } catch (ex: Exception) {
20 | null
21 | }
22 | }
23 |
24 | private fun mx(sum: Int, y: Int, z: Int, p: Int, e: Int, k: IntArray): Int {
25 | return (z.ushr(5) xor (y shl 2)) + (y.ushr(3) xor (z shl 4)) xor (sum xor y) + (k[p and 3 xor e] xor z)
26 | }
27 |
28 | private fun encrypt(data: ByteArray, key: ByteArray): ByteArray =
29 | data.takeIf { it.isNotEmpty() }
30 | ?.let {
31 | encrypt(data.toIntArray(true), key.fixKey().toIntArray(false))
32 | .toByteArray(false)
33 | }
34 | ?: data
35 |
36 | private fun encryptToByte(data: String, key: String): ByteArray? =
37 | runCatching {
38 | encrypt(
39 | data.encodeToByteArray(throwOnInvalidSequence = true),
40 | key.encodeToByteArray(throwOnInvalidSequence = true)
41 | )
42 | }.getOrNull()
43 |
44 | private fun decrypt(data: ByteArray, key: ByteArray): ByteArray =
45 | data.takeIf { it.isNotEmpty() }
46 | ?.let {
47 | decrypt(data.toIntArray(false), key.fixKey().toIntArray(false))
48 | .toByteArray(true)
49 | } ?: data
50 |
51 | private fun decrypt(data: ByteArray, key: String): ByteArray? =
52 | kotlin.runCatching { decrypt(data, key.encodeToByteArray(throwOnInvalidSequence = true)) }.getOrNull()
53 |
54 | private fun encrypt(v: IntArray, k: IntArray): IntArray {
55 | val n = v.size - 1
56 | if (n < 1) {
57 | return v
58 | }
59 | var p: Int
60 | var q = 6 + 52 / (n + 1)
61 | var z = v[n]
62 | var y: Int
63 | var sum = 0
64 | var e: Int
65 | while (q-- > 0) {
66 | sum += DELTA
67 | e = sum.ushr(2) and 3
68 | p = 0
69 | while (p < n) {
70 | y = v[p + 1]
71 | v[p] += mx(sum, y, z, p, e, k)
72 | z = v[p]
73 | p++
74 | }
75 | y = v[0]
76 | v[n] += mx(sum, y, z, p, e, k)
77 | z = v[n]
78 | }
79 | return v
80 | }
81 |
82 | private fun decrypt(v: IntArray, k: IntArray): IntArray {
83 | val n = v.size - 1
84 | if (n < 1) {
85 | return v
86 | }
87 | var p: Int
88 | val q = 6 + 52 / (n + 1)
89 | var z: Int
90 | var y = v[0]
91 | var sum = q * DELTA
92 | var e: Int
93 | while (sum != 0) {
94 | e = sum.ushr(2) and 3
95 | p = n
96 | while (p > 0) {
97 | z = v[p - 1]
98 | v[p] -= mx(sum, y, z, p, e, k)
99 | y = v[p]
100 | p--
101 | }
102 | z = v[n]
103 | v[0] -= mx(sum, y, z, p, e, k)
104 | y = v[0]
105 | sum -= DELTA
106 | }
107 | return v
108 | }
109 | private fun ByteArray.fixKey(): ByteArray {
110 | if (size == 16) return this
111 | val fixedKey = ByteArray(16)
112 |
113 | if (size < 16) {
114 | copyInto(fixedKey)
115 | } else {
116 | copyInto(fixedKey, endIndex = 16)
117 | }
118 | return fixedKey
119 | }
120 |
121 | private fun ByteArray.toIntArray(includeLength: Boolean): IntArray {
122 | var n = if (size and 3 == 0)
123 | size.ushr(2)
124 | else
125 | size.ushr(2) + 1
126 | val result: IntArray
127 |
128 | if (includeLength) {
129 | result = IntArray(n + 1)
130 | result[n] = size
131 | } else {
132 | result = IntArray(n)
133 | }
134 | n = size
135 | for (i in 0 until n) {
136 | result[i.ushr(2)] = result[i.ushr(2)] or (0x000000ff and this[i].toInt() shl (i and 3 shl 3))
137 | }
138 | return result
139 | }
140 |
141 | private fun IntArray.toByteArray(includeLength: Boolean): ByteArray? {
142 | var n = size shl 2
143 |
144 | if (includeLength) {
145 | val m = this[size - 1]
146 | n -= 4
147 | if (m < n - 3 || m > n) {
148 | return null
149 | }
150 | n = m
151 | }
152 | val result = ByteArray(n)
153 |
154 | for (i in 0 until n) {
155 | result[i] = this[i.ushr(2)].ushr(i and 3 shl 3).toByte()
156 | }
157 | return result
158 | }
159 | private object Base64 {
160 | private val base64EncodeChars = charArrayOf(
161 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
162 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
163 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
164 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
165 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
166 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
167 | 'w', 'x', 'y', 'z', '0', '1', '2', '3',
168 | '4', '5', '6', '7', '8', '9', '+', '/'
169 | )
170 | private val base64DecodeChars = byteArrayOf(
171 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
172 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
173 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
174 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
175 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
176 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
177 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
178 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
179 | )
180 |
181 | fun encode(data: ByteArray): String {
182 | val sb = StringBuilder()
183 | val r = data.size % 3
184 | val len = data.size - r
185 | var i = 0
186 | var c: Int
187 | while (i < len) {
188 | c = 0x000000ff and data[i++].toInt() shl 16 or (
189 | 0x000000ff and data[i++].toInt() shl 8) or
190 | (0x000000ff and data[i++].toInt())
191 | sb.append(base64EncodeChars[c shr 18])
192 | sb.append(base64EncodeChars[c shr 12 and 0x3f])
193 | sb.append(base64EncodeChars[c shr 6 and 0x3f])
194 | sb.append(base64EncodeChars[c and 0x3f])
195 | }
196 | if (r == 1) {
197 | c = 0x000000ff and data[i].toInt()
198 | sb.append(base64EncodeChars[c shr 2])
199 | sb.append(base64EncodeChars[c and 0x03 shl 4])
200 | sb.append("==")
201 | } else if (r == 2) {
202 | c = 0x000000ff and data[i++].toInt() shl 8 or
203 | (0x000000ff and data[i].toInt())
204 | sb.append(base64EncodeChars[c shr 10])
205 | sb.append(base64EncodeChars[c shr 4 and 0x3f])
206 | sb.append(base64EncodeChars[c and 0x0f shl 2])
207 | sb.append("=")
208 | }
209 | return sb.toString()
210 | }
211 |
212 | fun decode(str: String): ByteArray {
213 | val data = str.encodeToByteArray()
214 | val len = data.size
215 | val buf = mutableListOf()
216 | var i = 0
217 | var b1: Int
218 | var b2: Int
219 | var b3: Int
220 | var b4: Int
221 | while (i < len) {
222 |
223 | /* b1 */do {
224 | b1 = base64DecodeChars[data[i++].toInt()].toInt()
225 | } while (i < len && b1 == -1)
226 | if (b1 == -1) {
227 | break
228 | }
229 |
230 | /* b2 */do {
231 | b2 = base64DecodeChars[data[i++].toInt()].toInt()
232 | } while (i < len && b2 == -1)
233 | if (b2 == -1) {
234 | break
235 | }
236 | buf.add((b1 shl 2 or (b2 and 0x30 ushr 4)).toByte())
237 |
238 | /* b3 */do {
239 | b3 = data[i++].toInt()
240 | if (b3 == 61) {
241 | return buf.toByteArray()
242 | }
243 | b3 = base64DecodeChars[b3].toInt()
244 | } while (i < len && b3 == -1)
245 | if (b3 == -1) {
246 | break
247 | }
248 | buf.add((b2 and 0x0f shl 4 or (b3 and 0x3c ushr 2)).toByte())
249 |
250 | /* b4 */do {
251 | b4 = data[i++].toInt()
252 | if (b4 == 61) {
253 | return buf.toByteArray()
254 | }
255 | b4 = base64DecodeChars[b4].toInt()
256 | } while (i < len && b4 == -1)
257 | if (b4 == -1) {
258 | break
259 | }
260 | buf.add((b3 and 0x03 shl 6 or b4).toByte())
261 | }
262 | return buf.toByteArray()
263 | }
264 | }
265 | }
--------------------------------------------------------------------------------
/app/src/main/python/smlwb.py:
--------------------------------------------------------------------------------
1 | import dns.message
2 | import dns.rdatatype
3 | import requests
4 | import base64
5 | import socket
6 | import threading
7 | import time
8 | import random
9 |
10 | listen_PORT = 4525
11 |
12 | num_fragment = 300
13 | fragment_sleep = 0.001
14 |
15 | log_every_N_sec = 30
16 |
17 | is_logging = False
18 |
19 | DNS_url = 'https://sky.rethinkdns.com/?dns='
20 |
21 | offline_DNS = {
22 | 'sky.rethinkdns.com': '172.67.162.27'
23 | }
24 |
25 | my_socket_timeout = 21
26 | first_time_sleep = 0.1
27 | accept_time_sleep = 0.01
28 |
29 | DNS_cache = {}
30 | IP_DL_traffic = {}
31 | IP_UL_traffic = {}
32 |
33 |
34 | class DnsOverFragment:
35 | def __init__(self):
36 | self.url = DNS_url
37 | self.req = requests.session()
38 | self.fragment_proxy = {
39 | 'https': 'http://127.0.0.1:' + str(listen_PORT)
40 | }
41 |
42 | def query(self, server_name):
43 |
44 | offline_ip = offline_DNS.get(server_name, None)
45 | if offline_ip is not None:
46 | if is_logging:
47 | print('offline DNS -->', server_name, offline_ip)
48 | return offline_ip
49 |
50 | cache_ip = DNS_cache.get(server_name, None)
51 | if cache_ip is not None:
52 | if is_logging:
53 | print('cached DNS -->', server_name, cache_ip)
54 | return cache_ip
55 |
56 | query_params = {
57 | 'type': 'A',
58 | 'ct': 'application/dns-message',
59 | }
60 | if is_logging:
61 | print(f'online DNS Query', server_name)
62 | try:
63 | query_message = dns.message.make_query(server_name, 'A')
64 | query_wire = query_message.to_wire()
65 | query_base64 = base64.urlsafe_b64encode(query_wire).decode('utf-8')
66 | query_base64 = query_base64.replace('=', '')
67 |
68 | query_url = self.url + query_base64
69 | ans = self.req.get(query_url, params=query_params, headers={'accept': 'application/dns-message'},
70 | proxies=self.fragment_proxy)
71 |
72 | if ans.status_code == 200 and ans.headers.get('content-type') == 'application/dns-message':
73 | answer_msg = dns.message.from_wire(ans.content)
74 |
75 | resolved_ip = None
76 | for x in answer_msg.answer:
77 | if x.rdtype == dns.rdatatype.A:
78 | resolved_ip = x[0].address
79 | DNS_cache[server_name] = resolved_ip
80 | if is_logging:
81 | print("################# DNS Cache is : ####################")
82 | print(DNS_cache)
83 | # later.
84 | print("#####################################################")
85 | break
86 | if is_logging:
87 | print(f'online DNS --> Resolved {server_name} to {resolved_ip}')
88 | return resolved_ip
89 | else:
90 | if is_logging:
91 | print(f'Error: {ans.status_code} {ans.reason}')
92 | except Exception as e:
93 | if is_logging:
94 | print(repr(e))
95 |
96 |
97 | def my_downstream(backend_sock, client_sock):
98 | this_ip = backend_sock.getpeername()[0]
99 | if this_ip not in IP_DL_traffic:
100 | IP_DL_traffic[this_ip] = 0
101 |
102 | first_flag = True
103 | while True:
104 | try:
105 | if first_flag:
106 | first_flag = False
107 | data = backend_sock.recv(16384)
108 | if data:
109 | client_sock.sendall(data)
110 | IP_DL_traffic[this_ip] = IP_DL_traffic[this_ip] + len(data)
111 | else:
112 | raise Exception('backend pipe close at first')
113 |
114 | else:
115 | data = backend_sock.recv(16384)
116 | if data:
117 | client_sock.sendall(data)
118 | IP_DL_traffic[this_ip] = IP_DL_traffic[this_ip] + len(data)
119 | else:
120 | raise Exception('backend pipe close')
121 |
122 | except Exception as e:
123 | time.sleep(2)
124 | backend_sock.close()
125 | client_sock.close()
126 | return False
127 |
128 |
129 | def extract_server_name_and_port(data):
130 | host_and_port = str(data).split()[1]
131 | host, port = host_and_port.split(':')
132 | return host, int(port)
133 |
134 |
135 | class ThreadedServer(object):
136 | def __init__(self, host, port):
137 | self.DoH = DnsOverFragment()
138 | self.host = host
139 | self.port = port
140 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
141 | self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
142 | self.sock.bind((self.host, self.port))
143 |
144 | def listen(self):
145 | self.sock.listen(
146 | 128)
147 |
148 | while True:
149 | client_sock, client_addr = self.sock.accept()
150 | client_sock.settimeout(my_socket_timeout)
151 |
152 | time.sleep(accept_time_sleep)
153 | thread_up = threading.Thread(target=self.my_upstream, args=(client_sock,))
154 | thread_up.daemon = True
155 | thread_up.start()
156 |
157 | def handle_client_request(self, client_socket):
158 | data = client_socket.recv(16384)
159 |
160 | if data[:7] == b'CONNECT':
161 | server_name, server_port = extract_server_name_and_port(data)
162 | elif (data[:3] == b'GET') or (data[:4] == b'POST'):
163 | q_line = str(data).split('\r\n')
164 | q_url = q_line[0].split()[1]
165 | q_url = q_url.replace('http://', 'https://')
166 | if is_logging:
167 | print('redirect http to HTTPS', q_url)
168 | response_data = 'HTTP/1.1 302 Found\r\nLocation: ' + q_url + '\r\nProxy-agent: MyProxy/1.0\r\n\r\n'
169 | client_socket.sendall(response_data.encode())
170 | client_socket.close()
171 | return None
172 | else:
173 | if is_logging:
174 | print('Unknown Method', str(data[:10]))
175 | response_data = b'HTTP/1.1 400 Bad Request\r\nProxy-agent: MyProxy/1.0\r\n\r\n'
176 | client_socket.sendall(response_data)
177 | client_socket.close()
178 | return None
179 |
180 | if is_logging:
181 | print(server_name, '-->', server_port)
182 |
183 | try:
184 | server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
185 | server_socket.settimeout(my_socket_timeout)
186 | server_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
187 | try:
188 | socket.inet_aton(server_name)
189 | server_ip = server_name
190 | except socket.error:
191 | server_ip = self.DoH.query(server_name)
192 |
193 | server_socket.connect((server_ip, server_port))
194 | response_data = b'HTTP/1.1 200 Connection established\r\nProxy-agent: MyProxy/1.0\r\n\r\n'
195 | client_socket.sendall(response_data)
196 | return server_socket
197 | except Exception as e:
198 | if is_logging:
199 | print(repr(e))
200 | response_data = b'HTTP/1.1 502 Bad Gateway\r\nProxy-agent: MyProxy/1.0\r\n\r\n'
201 | client_socket.sendall(response_data)
202 | client_socket.close()
203 | server_socket.close()
204 | return None
205 |
206 | def my_upstream(self, client_sock):
207 | first_flag = True
208 | backend_sock = self.handle_client_request(client_sock)
209 |
210 | if backend_sock is None:
211 | client_sock.close()
212 | return False
213 |
214 | this_ip = backend_sock.getpeername()[0]
215 | if this_ip not in IP_UL_traffic:
216 | IP_UL_traffic[this_ip] = 0
217 |
218 | while True:
219 | try:
220 | if first_flag:
221 | first_flag = False
222 |
223 | time.sleep(first_time_sleep)
224 | data = client_sock.recv(16384)
225 |
226 | if data:
227 | thread_down = threading.Thread(target=my_downstream, args=(backend_sock, client_sock))
228 | thread_down.daemon = True
229 | thread_down.start()
230 | send_data_in_fragment(data, backend_sock)
231 | IP_UL_traffic[this_ip] = IP_UL_traffic[this_ip] + len(data)
232 |
233 | else:
234 | raise Exception('cli syn close')
235 |
236 | else:
237 | data = client_sock.recv(16384)
238 | if data:
239 | backend_sock.sendall(data)
240 | IP_UL_traffic[this_ip] = IP_UL_traffic[this_ip] + len(data)
241 | else:
242 | raise Exception('cli pipe close')
243 |
244 | except Exception as e:
245 | time.sleep(2)
246 | client_sock.close()
247 | backend_sock.close()
248 | return False
249 |
250 |
251 | def send_data_in_fragment(data, sock):
252 | l_data = len(data)
253 | indices = random.sample(range(1, l_data - 1), num_fragment - 1)
254 | indices.sort()
255 |
256 | i_pre = 0
257 | for i in indices:
258 | fragment_data = data[i_pre:i]
259 | i_pre = i
260 | sock.sendall(fragment_data)
261 | time.sleep(fragment_sleep)
262 | fragment_data = data[i_pre:l_data]
263 | sock.sendall(fragment_data)
264 | if is_logging:
265 | print('----------finish------------')
266 |
267 |
268 | def main():
269 | print("Now listening at: 127.0.0.1:" + str(listen_PORT))
270 | ThreadedServer('', listen_PORT).listen()
--------------------------------------------------------------------------------
/app/src/main/java/dev/mahdisml/webimmortalguards/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.mahdisml.webimmortalguards
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.BackHandler
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.foundation.BorderStroke
8 | import androidx.compose.foundation.Image
9 | import androidx.compose.foundation.background
10 | import androidx.compose.foundation.clickable
11 | import androidx.compose.foundation.layout.Arrangement
12 | import androidx.compose.foundation.layout.Box
13 | import androidx.compose.foundation.layout.Column
14 | import androidx.compose.foundation.layout.Spacer
15 | import androidx.compose.foundation.layout.fillMaxHeight
16 | import androidx.compose.foundation.layout.fillMaxSize
17 | import androidx.compose.foundation.layout.fillMaxWidth
18 | import androidx.compose.foundation.layout.height
19 | import androidx.compose.foundation.layout.padding
20 | import androidx.compose.foundation.layout.wrapContentSize
21 | import androidx.compose.foundation.shape.RoundedCornerShape
22 | import androidx.compose.material3.Button
23 | import androidx.compose.material3.ButtonDefaults
24 | import androidx.compose.material3.Surface
25 | import androidx.compose.material3.Switch
26 | import androidx.compose.material3.SwitchDefaults
27 | import androidx.compose.material3.Text
28 | import androidx.compose.runtime.Composable
29 | import androidx.compose.runtime.LaunchedEffect
30 | import androidx.compose.runtime.getValue
31 | import androidx.compose.runtime.mutableStateOf
32 | import androidx.compose.runtime.remember
33 | import androidx.compose.runtime.setValue
34 | import androidx.compose.ui.Alignment
35 | import androidx.compose.ui.Modifier
36 | import androidx.compose.ui.draw.clip
37 | import androidx.compose.ui.draw.scale
38 | import androidx.compose.ui.graphics.Brush
39 | import androidx.compose.ui.graphics.ColorFilter
40 | import androidx.compose.ui.platform.LocalContext
41 | import androidx.compose.ui.res.painterResource
42 | import androidx.compose.ui.text.ExperimentalTextApi
43 | import androidx.compose.ui.text.TextStyle
44 | import androidx.compose.ui.text.style.TextAlign
45 | import androidx.compose.ui.tooling.preview.Preview
46 | import androidx.compose.ui.unit.dp
47 | import androidx.compose.ui.unit.sp
48 | import androidx.lifecycle.Lifecycle
49 | import androidx.lifecycle.viewmodel.compose.viewModel
50 | import dev.mahdisml.webimmortalguards.Strings.setting_1
51 | import dev.mahdisml.webimmortalguards.Strings.setting_1_1
52 | import dev.mahdisml.webimmortalguards.Strings.version
53 | import dev.mahdisml.webimmortalguards.core.AppCore
54 | import dev.mahdisml.webimmortalguards.core.AppCore.Companion.coloredShadow
55 | import dev.mahdisml.webimmortalguards.ui.MainViewModel
56 | import dev.mahdisml.webimmortalguards.ui.theme.Cinder
57 | import dev.mahdisml.webimmortalguards.ui.theme.Gold
58 | import dev.mahdisml.webimmortalguards.ui.theme.GoldDark
59 | import dev.mahdisml.webimmortalguards.ui.theme.GoldDarker
60 | import dev.mahdisml.webimmortalguards.ui.theme.Silver
61 | import dev.mahdisml.webimmortalguards.ui.theme.SilverDarker
62 | import dev.mahdisml.webimmortalguards.ui.theme.Ubuntu_Regular
63 | import dev.mahdisml.webimmortalguards.ui.theme.WebImmortalGuardsTheme
64 |
65 | class MainActivity : ComponentActivity() {
66 | override fun onCreate(savedInstanceState: Bundle?) {
67 | super.onCreate(savedInstanceState)
68 | setContent {
69 | val ctx = LocalContext.current
70 | val mainViewModel :MainViewModel = viewModel()
71 |
72 | LaunchedEffect(true) {
73 | mainViewModel.checkPageState(ctx)
74 | }
75 |
76 | WebImmortalGuardsTheme {
77 | Surface(
78 | modifier = Modifier.fillMaxSize(),
79 | color = Cinder
80 | ) {
81 | when(mainViewModel.pageState){
82 | 0 -> {Greeting()}
83 | 1 -> {Home()}
84 | 2 -> {Settings()}
85 | }
86 | }
87 | }
88 | }
89 | }
90 | }
91 | @OptIn(ExperimentalTextApi::class)
92 | @Composable
93 | fun Greeting() {
94 | val ctx = LocalContext.current
95 | val mainViewModel:MainViewModel = viewModel()
96 |
97 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
98 | Column(
99 | modifier = Modifier
100 | .fillMaxWidth()
101 | .fillMaxHeight(),
102 | verticalArrangement = Arrangement.Center,
103 | horizontalAlignment = Alignment.CenterHorizontally
104 | ) {
105 | Image(
106 | painter = painterResource(id = R.drawable.greeting),
107 | contentDescription = Strings.greeting_image_des,
108 | modifier = Modifier
109 | .coloredShadow(color = Gold, alpha = 0.6f)
110 | .fillMaxHeight(0.45f)
111 | .wrapContentSize()
112 | .clip(RoundedCornerShape(33.dp))
113 | )
114 | Spacer(modifier = Modifier.height(60.dp))
115 | Text(
116 | text = Strings.greeting_text,
117 | style = TextStyle(
118 | brush = Brush.linearGradient(
119 | colors = listOf(GoldDark, Gold,GoldDark)
120 | )
121 | ),
122 | textAlign = TextAlign.Center,
123 | modifier = Modifier.padding(horizontal = 33.dp),
124 | fontSize = 17.sp
125 | )
126 | Spacer(modifier = Modifier.height(30.dp))
127 | Button(
128 | onClick = {
129 | mainViewModel.greetingsDone(ctx)
130 | },
131 | colors = ButtonDefaults.buttonColors(
132 | containerColor = Cinder,
133 | contentColor = Silver
134 | ),
135 | border = BorderStroke(1.dp, Gold)
136 | ) {
137 | Text(
138 | text = Strings.greeting_button_text,
139 | textAlign = TextAlign.Center
140 | )
141 | }
142 | }
143 | }
144 |
145 |
146 |
147 | }
148 | @OptIn(ExperimentalTextApi::class)
149 | @Composable
150 | fun Home() {
151 | val ctx = LocalContext.current
152 | val mainViewModel:MainViewModel = viewModel()
153 |
154 | LaunchedEffect(true){
155 | mainViewModel.checkVpnState(ctx)
156 | }
157 |
158 | var firstResume by remember { mutableStateOf(true)}
159 | AppCore.OnLifecycleEvent { _, event ->
160 | when (event) {
161 | Lifecycle.Event.ON_RESUME -> {
162 | if (!firstResume) {
163 | mainViewModel.checkVpnState(ctx)
164 | } else {
165 | firstResume = false
166 | }
167 | }
168 | else -> {}
169 | }
170 | }
171 |
172 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
173 | Image(
174 | painter = painterResource(id = R.drawable.ic_settings),
175 | contentDescription = Strings.home_setting_des,
176 | modifier = Modifier
177 | .align(Alignment.TopStart)
178 | .padding(all = 20.dp)
179 | .clickable {
180 | mainViewModel.pageState = 2
181 | },
182 | colorFilter = ColorFilter.tint(GoldDarker)
183 | )
184 | Column(
185 | modifier = Modifier
186 | .fillMaxWidth()
187 | .fillMaxHeight()
188 | .padding(top = 70.dp),
189 | verticalArrangement = Arrangement.Top,
190 | horizontalAlignment = Alignment.CenterHorizontally
191 | ) {
192 | Image(
193 | painter = painterResource(id = R.drawable.logo),
194 | contentDescription = Strings.home_image_des,
195 | modifier = Modifier.fillMaxHeight(0.495f)
196 | )
197 | Text(
198 | text = Strings.app_name,
199 | style = TextStyle(
200 | brush = Brush.linearGradient(
201 | colors = listOf(GoldDark, Gold,GoldDark)
202 | ),
203 | fontFamily = Ubuntu_Regular
204 | ),
205 | textAlign = TextAlign.Center,
206 | modifier = Modifier
207 | .padding(horizontal = 33.dp)
208 | .padding(bottom = (40).dp),
209 | fontSize = 25.sp
210 | )
211 | Spacer(modifier = Modifier.height(10.dp))
212 | Switch(
213 | modifier = Modifier
214 | .scale(1.5f)
215 | .coloredShadow(
216 | color = Gold,
217 | alpha = if (mainViewModel.vpnState) 0.4f else 0.1f
218 | ),
219 | checked = mainViewModel.vpnState,
220 | onCheckedChange = {
221 | mainViewModel.setVpnState(ctx)
222 | },
223 | colors = SwitchDefaults.colors(
224 | uncheckedBorderColor = GoldDark,
225 | checkedBorderColor = Gold,
226 |
227 | uncheckedThumbColor = Gold,
228 | checkedThumbColor = Cinder,
229 |
230 | uncheckedTrackColor = Cinder,
231 | checkedTrackColor = Gold
232 |
233 | )
234 | )
235 | Spacer(modifier = Modifier.height(50.dp))
236 | Text(
237 | text = if (mainViewModel.vpnState) Strings.home_text_on else Strings.home_text_off,
238 | style = TextStyle(
239 | brush = Brush.linearGradient(
240 | colors = if (mainViewModel.vpnState) listOf(GoldDark, Gold,GoldDark) else listOf(GoldDarker,GoldDarker)
241 | )
242 | ),
243 | textAlign = TextAlign.Center,
244 | modifier = Modifier.padding(horizontal = 33.dp),
245 | fontSize = 17.sp
246 | )
247 | }
248 | }
249 | }
250 | @Preview(device = "id:pixel_6_pro")
251 | @Composable
252 | fun Settings() {
253 | val mainViewModel:MainViewModel = viewModel()
254 |
255 | var logoState by remember { mutableStateOf(0) }
256 |
257 | BackHandler {
258 | mainViewModel.pageState = 1
259 | }
260 |
261 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
262 | Image(
263 | painter = painterResource(id = R.drawable.back),
264 | contentDescription = Strings.setting_back,
265 | modifier = Modifier
266 | .align(Alignment.TopStart)
267 | .padding(all = 20.dp)
268 | .clickable {
269 | mainViewModel.pageState = 1
270 | },
271 | colorFilter = ColorFilter.tint(GoldDarker)
272 | )
273 | Box(
274 | modifier = Modifier
275 | .fillMaxHeight(0.70f)
276 | .fillMaxWidth(0.80f)
277 | .coloredShadow(
278 | color = Gold,
279 | alpha = 0.1f
280 | )
281 | .clip(RoundedCornerShape(33.dp))
282 | .background(Cinder),
283 | contentAlignment = Alignment.Center
284 | ){
285 | Column(modifier = Modifier.align(Alignment.TopCenter),horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top) {
286 | Spacer(modifier = Modifier.height(30.dp))
287 | Box(modifier = Modifier.fillMaxWidth(0.85f)) {
288 | Text(modifier = Modifier.align(Alignment.CenterStart),text = setting_1, color = Silver, textAlign = TextAlign.Center, fontSize = 14.sp)
289 | Button(
290 | onClick = {
291 |
292 | },
293 | enabled = false,
294 | modifier=Modifier.align(Alignment.CenterEnd) ,
295 | colors = ButtonDefaults.buttonColors(
296 | containerColor = Cinder,
297 | contentColor = Silver,
298 | disabledContainerColor = Cinder,
299 | disabledContentColor = SilverDarker
300 | ),
301 | border = BorderStroke(1.dp, Gold)
302 | ) {
303 | Text(
304 | text = setting_1_1,
305 | textAlign = TextAlign.Center
306 | )
307 | }
308 | }
309 | }
310 | Column(modifier = Modifier.align(Alignment.BottomCenter),horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
311 | Image(
312 | modifier= Modifier
313 | .fillMaxWidth(0.25f)
314 | .fillMaxHeight(0.1f)
315 | .wrapContentSize()
316 | .clickable {
317 | logoState++
318 | },
319 | painter = if (logoState >= 12) painterResource(id = R.drawable.heart) else painterResource(id = R.drawable.ic_mahdisml_emblem),
320 | contentDescription = Strings.setting_mahdisml
321 | )
322 | Spacer(modifier = Modifier.height(20.dp))
323 | Text(text = version, color = Silver, textAlign = TextAlign.Center)
324 | Spacer(modifier = Modifier.height(20.dp))
325 | }
326 | }
327 | }
328 |
329 | }
330 |
331 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mahdisml_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
13 |
17 |
21 |
25 |
29 |
30 |
--------------------------------------------------------------------------------