Public commit v2.1.5
BIN
.gradle/7.3.3/checksums/checksums.lock
Normal file
BIN
.gradle/7.3.3/dependencies-accessors/dependencies-accessors.lock
Normal file
0
.gradle/7.3.3/dependencies-accessors/gc.properties
Normal file
BIN
.gradle/7.3.3/executionHistory/executionHistory.bin
Normal file
BIN
.gradle/7.3.3/executionHistory/executionHistory.lock
Normal file
BIN
.gradle/7.3.3/fileChanges/last-build.bin
Normal file
BIN
.gradle/7.3.3/fileHashes/fileHashes.bin
Normal file
BIN
.gradle/7.3.3/fileHashes/fileHashes.lock
Normal file
BIN
.gradle/7.3.3/fileHashes/resourceHashesCache.bin
Normal file
0
.gradle/7.3.3/gc.properties
Normal file
BIN
.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
2
.gradle/buildOutputCleanup/cache.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
#Sun Feb 26 18:53:32 EET 2023
|
||||
gradle.version=7.3.3
|
||||
BIN
.gradle/buildOutputCleanup/outputFiles.bin
Normal file
BIN
.gradle/file-system.probe
Normal file
0
.gradle/vcs-1/gc.properties
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
Proxidize Android Legacy
|
||||
6
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
19
.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
35
LICENSE
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
|
||||
|
||||
ACADEMIC PUBLIC LICENSE
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, COPYING, DISTRIBUTION AND MODIFICATION
|
||||
Definitions
|
||||
“Program” means a copy of Proxidize Android or Proxidize Android Legacy, which is said to be distributed under this Academic Public License.
|
||||
“Work based on the Program” means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term “modification”.)
|
||||
“Using the Program” means any act of creating executables that contain or directly use libraries that are part of the Program, running any of the tools that are part of the Program, or creating works based on the Program.
|
||||
Each licensee is addressed as “you”.
|
||||
§1. Permission is hereby granted to use the Program free of charge for any noncommercial purpose, including teaching and research at universities, colleges and other educational institutions, research at non-profit research institutions, and personal non-profit purposes. For using the Program for commercial purposes, including but not restricted to consulting activities, design of commercial hardware or software networking products, and a commercial entity participating in research projects, you have to contact the Author for an appropriate license. Permission is also granted to use the Program for a reasonably limited period of time for the purpose of evaluating its usefulness for a particular purpose.
|
||||
§2. You may copy and distribute verbatim copies of the Program’s source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
|
||||
§3. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 2 above, provided that you also meet all of these conditions:
|
||||
a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
|
||||
b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
|
||||
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose regulations for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. (If the same, independent sections are distributed as part of a package that is otherwise reliant on, or is based on the Program, then the distribution of the whole package, including but not restricted to the independent section, must be on the unmodified terms of this License, regadless of who the author of the included sections was.)
|
||||
Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based or reliant on the Program.
|
||||
In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of storage or distribution medium does not bring the other work under the scope of this License.
|
||||
§4. You may copy and distribute the Program (or a work based on it, under Section 3) in object code or executable form under the terms of Sections 2 and 3 above provided that you also do one of the following:
|
||||
a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 2 and 3 above on a medium customarily used for software interchange; or,
|
||||
b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 2 and 3 above on a medium customarily used for software interchange; or,
|
||||
c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b) above.)
|
||||
The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
|
||||
If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
|
||||
§5. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
|
||||
§6. You are not required to accept this License, since you have not signed it. Nothing else grants you permission to modify or distribute the Program or its derivative works; law prohibits these actions if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License and all its terms and conditions for copying, distributing or modifying the Program or works based on it, to do so.
|
||||
§7. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients’ exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
|
||||
§8. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
|
||||
If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
|
||||
§9. If the distribution and/or use of the Program are restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
|
||||
NO WARRANTY
|
||||
§10. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
§11. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED ON IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
END OF TERMS AND CONDITIONS
|
||||
1
app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
97
app/build.gradle
Normal file
@@ -0,0 +1,97 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'com.google.gms.google-services'
|
||||
id 'kotlin-kapt'
|
||||
id 'dagger.hilt.android.plugin'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.proxidize.legacy.android"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 13
|
||||
versionName "2.1.5"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation files('libs/connlib.aar')
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.5.0'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.1'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.5.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
implementation 'com.google.firebase:firebase-analytics:21.1.0'
|
||||
|
||||
|
||||
//data store for liecence
|
||||
implementation "androidx.datastore:datastore-preferences:1.0.0"
|
||||
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.0-alpha01"
|
||||
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.2"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha01"
|
||||
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
|
||||
|
||||
// implementation of NANO_HTTPD server
|
||||
implementation "org.nanohttpd:nanohttpd:2.3.1"
|
||||
|
||||
//WORK MANAGER
|
||||
// implementation "android.arch.work:work-runtime:1.0.1"
|
||||
implementation 'androidx.work:work-runtime:2.7.1'
|
||||
implementation "androidx.concurrent:concurrent-futures:1.1.0"
|
||||
implementation 'androidx.work:work-runtime-ktx:2.7.1'
|
||||
implementation 'androidx.hilt:hilt-work:1.0.0'
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.0-alpha01"
|
||||
//
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||
kapt "androidx.hilt:hilt-compiler:1.0.0"
|
||||
//hilt
|
||||
implementation 'com.google.dagger:hilt-android:2.41'
|
||||
kapt 'com.google.dagger:hilt-android-compiler:2.41'
|
||||
kapt "androidx.hilt:hilt-compiler:1.0.0"
|
||||
|
||||
|
||||
//http request
|
||||
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2"
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation('com.squareup.retrofit2:converter-gson:2.9.0')
|
||||
|
||||
implementation "androidx.activity:activity-ktx:1.6.0-beta01"
|
||||
|
||||
implementation("com.github.haroldadmin:NetworkResponseAdapter:4.1.0")
|
||||
|
||||
}
|
||||
39
app/google-services.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "146829972367",
|
||||
"project_id": "proxidize-android-legacy",
|
||||
"storage_bucket": "proxidize-android-legacy.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:146829972367:android:b772d351e07a34f0fe31fa",
|
||||
"android_client_info": {
|
||||
"package_name": "com.proxidize.legacy.android"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "146829972367-t1hj0ce4sa2scqjrbh82321olll238qb.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDrq8rvJsOTOqTWleSFgSkO2GqK69sL6o8"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "146829972367-t1hj0ce4sa2scqjrbh82321olll238qb.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
BIN
app/libs/connlib-sources.jar
Normal file
BIN
app/libs/connlib.aar
Normal file
21
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
BIN
app/release/app-release.apk
Normal file
20
app/release/output-metadata.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "com.proxidize.legacy.android",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 12,
|
||||
"versionName": "2.1.4",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.legacy.android
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.legacy.android", appContext.packageName)
|
||||
}
|
||||
}
|
||||
70
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.legacy.android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:name=".MyApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
|
||||
android:theme="@style/Theme.ProxidizeAndroidLegacy"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".ui.NavDrawerActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_nav_drawer"
|
||||
android:theme="@style/Theme.ProxidizeAndroidLegacy" />
|
||||
<activity
|
||||
android:name=".CustomServerActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.ProxidizeAndroidLegacy" />
|
||||
|
||||
<service
|
||||
android:name=".NotificationService"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".service.VoiceInteractionServiceImp"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_VOICE_INTERACTION">
|
||||
<meta-data
|
||||
android:name="android.voice_interaction"
|
||||
android:resource="@xml/interaction_service" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.voice.VoiceInteractionService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".service.VoiceInteractionSessionService"
|
||||
android:permission="android.permission.BIND_VOICE_INTERACTION" />
|
||||
|
||||
<receiver
|
||||
android:name=".UpdateReceiver"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
0
app/src/main/assets/config.ini
Normal file
0
app/src/main/assets/connection.log
Normal file
19
app/src/main/java/com/legacy/android/CredentialsDatabase.kt
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
|
||||
class CredentialsDatabase(context: Context) :
|
||||
SQLiteOpenHelper(context, "savedCredentials.db", null, 1) {
|
||||
override fun onCreate(db: SQLiteDatabase?) {
|
||||
db?.execSQL("create table mycredentials(hostIp VARCHAR, region VARCHAR, port INTEGER, token VARCHAR, latitude DOUBLE, longitude DOUBLE)") //VARCHAR MAYBE WILL BE WRONG IN SQLITE IF(REPLACE IT WITH TEXT)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
43
app/src/main/java/com/legacy/android/LogFileManager.kt
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android
|
||||
|
||||
import android.content.Context
|
||||
import java.io.*
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class LogFileManager private constructor() {
|
||||
private lateinit var logFile: File
|
||||
|
||||
companion object {
|
||||
private var instance: LogFileManager? = null
|
||||
|
||||
fun getInstance(): LogFileManager {
|
||||
if (instance == null) {
|
||||
instance = LogFileManager()
|
||||
}
|
||||
return instance!!
|
||||
}
|
||||
}
|
||||
|
||||
fun createLogFile(context: Context) {
|
||||
logFile = File(context.getExternalFilesDir(null), "ProxidizeLegacyCredentialsLogs.txt")
|
||||
}
|
||||
|
||||
fun writeToLogFile(message: String) {
|
||||
val bos = BufferedOutputStream(FileOutputStream(logFile,true))
|
||||
|
||||
try {
|
||||
bos.write(message.toByteArray())
|
||||
bos.flush()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
bos.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
54
app/src/main/java/com/legacy/android/MainActivity.kt
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.legacy.android.ui.NavDrawerActivity
|
||||
|
||||
class MainActivity : Activity() {
|
||||
|
||||
//VARIABLES
|
||||
var tobAnim: Animation? = null
|
||||
var bottomAnim: Animation? = null
|
||||
var image: ImageView? = null
|
||||
var slogan: TextView? = null
|
||||
|
||||
var mHandler = Handler(Looper.getMainLooper())
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
window.statusBarColor = ContextCompat.getColor(this, R.color.yellowForSplash)
|
||||
}
|
||||
|
||||
|
||||
tobAnim = AnimationUtils.loadAnimation(this, R.anim.tob_anim)
|
||||
bottomAnim = AnimationUtils.loadAnimation(this, R.anim.bottom_anim)
|
||||
|
||||
image = findViewById(R.id.logo)
|
||||
slogan = findViewById(R.id.slogan1)
|
||||
|
||||
image?.animation = tobAnim
|
||||
slogan?.animation = bottomAnim
|
||||
|
||||
mHandler.postDelayed({
|
||||
val intent = Intent(this@MainActivity, NavDrawerActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
114
app/src/main/java/com/legacy/android/MyApplication.kt
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android
|
||||
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import android.app.Application
|
||||
import com.legacy.android.MyApplication
|
||||
import android.os.Build
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.res.AssetManager
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.logging.FileHandler
|
||||
import java.util.logging.Logger
|
||||
import java.util.logging.SimpleFormatter
|
||||
|
||||
@HiltAndroidApp
|
||||
class MyApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
mContext = applicationContext
|
||||
createnotficationchannel()
|
||||
copyToSD("config.ini")
|
||||
LogFileManager.getInstance().createLogFile(this)
|
||||
val dbHelper = CredentialsDatabase(this)
|
||||
val db = dbHelper.writableDatabase
|
||||
val values = ContentValues().apply {
|
||||
put("hostIp","0.0.0.0")
|
||||
put("region","Create Your Own")
|
||||
put("port",1000)
|
||||
put("token","00000000")
|
||||
put("latitude",0.0)
|
||||
put("longitude",0.0)
|
||||
}
|
||||
db.insert("mycredentials", null, values)
|
||||
}
|
||||
|
||||
private fun createnotficationchannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel1 = NotificationChannel(
|
||||
channel_1_id, "channel 1", NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
channel1.description = "give the user some important information about the connection"
|
||||
channel1.enableLights(true)
|
||||
channel1.enableVibration(true)
|
||||
val servicechannel = NotificationChannel(
|
||||
channel_2_id, "channel 1", NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
servicechannel.description =
|
||||
"this channel is for a foreground service to let the user aware " +
|
||||
"when he is connected to the proxy and that the application in working background"
|
||||
val manager = getSystemService(
|
||||
NotificationManager::class.java
|
||||
)
|
||||
manager.createNotificationChannel(channel1)
|
||||
manager.createNotificationChannel(servicechannel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyToSD(dbName: String) {
|
||||
var `in`: InputStream? = null
|
||||
var out: FileOutputStream? = null
|
||||
val file = File(this.filesDir, dbName)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
try {
|
||||
file.createNewFile()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
val assets = assets
|
||||
try {
|
||||
`in` = assets.open(dbName)
|
||||
out = FileOutputStream(file)
|
||||
val b = ByteArray(1024)
|
||||
var len = -1
|
||||
while (`in`.read(b).also { len = it } != -1) {
|
||||
out.write(b, 0, len)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
if (`in` != null) {
|
||||
try {
|
||||
`in`.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val channel_1_id = "channel1"
|
||||
const val channel_2_id = "servicechannel"
|
||||
var mContext: Context? = null
|
||||
var FILENAME = "config.ini"
|
||||
var LOGFILE = "connection.log"
|
||||
}
|
||||
}
|
||||
92
app/src/main/java/com/legacy/android/NotificationService.kt
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.legacy.android.service.ProxidizeServer
|
||||
import com.legacy.android.service.changeIp
|
||||
import com.legacy.android.service.toggleAirPlaneMode
|
||||
import com.legacy.android.ui.ui.home.HomeFragment.Companion.NOTIFICATION_ID
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
import java.io.IOException
|
||||
|
||||
const val ACTION_CLOSE = "CLOSE_SERVICE"
|
||||
const val ACTION_ROTATE = "ROTATE_IP"
|
||||
|
||||
class NotificationService : Service() {
|
||||
|
||||
|
||||
private val server by lazy { ProxidizeServer(this) }
|
||||
|
||||
private val wakeLock: PowerManager.WakeLock by lazy {
|
||||
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag")
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("WakelockTimeout")
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
wakeLock.acquire()
|
||||
|
||||
try {
|
||||
server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false)
|
||||
} catch (e: IOException) {
|
||||
Log.d("onCreate: ", "IOException", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
if (ACTION_CLOSE == intent.action) {
|
||||
stopSelf()
|
||||
System.exit(0)
|
||||
return START_NOT_STICKY
|
||||
} else if (ACTION_ROTATE == intent.action) {
|
||||
toggleAirPlaneMode(this)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
val intent1 = Intent(this, NotificationService::class.java)
|
||||
intent1.action = ACTION_CLOSE
|
||||
var pIntent: PendingIntent? = null
|
||||
pIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.getService(this, 0, intent1, PendingIntent.FLAG_MUTABLE)
|
||||
} else PendingIntent.getService(this, 0, intent1, 0)
|
||||
val stat = intent.getStringExtra("stat")
|
||||
val notification =
|
||||
NotificationCompat.Builder(applicationContext, MyApplication.channel_2_id)
|
||||
.setContentTitle("Service working")
|
||||
.setContentText(stat)
|
||||
.addAction(
|
||||
NotificationCompat.Action(
|
||||
R.drawable.ic_baseline_close_24,
|
||||
"Close",
|
||||
pIntent
|
||||
)
|
||||
)
|
||||
.setSmallIcon(R.drawable.ic_baseline_miscellaneous_services_24)
|
||||
.build()
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (wakeLock.isHeld) {
|
||||
wakeLock.release()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
129
app/src/main/java/com/legacy/android/ServerPreference.kt
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android
|
||||
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.datastore.preferences.core.*
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.legacy.android.network.ProxyServer
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.lang.reflect.Type
|
||||
|
||||
|
||||
class ServerPreference private constructor(private val context: Context) {
|
||||
|
||||
private val Context.dataStore by preferencesDataStore(name = "settings")
|
||||
|
||||
|
||||
val SERVER: Flow<ProxyServer> = context.dataStore.data.map(::retrieveServer)
|
||||
val serverList: Flow<List<ProxyServer>> = context.dataStore.data.map(::retrieveServerList)
|
||||
val interval: Flow<Int?> = context.dataStore.data.map(::retrieveInterval)
|
||||
val pass: Flow<String> = context.dataStore.data.map(::retrievePass)
|
||||
|
||||
|
||||
private fun retrieveServer(pref: Preferences): ProxyServer {
|
||||
if (pref[stringPreferencesKey("host")].isNullOrBlank()) return ProxyServer.default()
|
||||
return ProxyServer(
|
||||
pref[stringPreferencesKey("host")] ?: "",
|
||||
pref[stringPreferencesKey("region")] ?: "",
|
||||
pref[intPreferencesKey("port")] ?: 2000,
|
||||
pref[stringPreferencesKey("token")],
|
||||
pref[doublePreferencesKey("latitude")] ?: 0.0,
|
||||
pref[doublePreferencesKey("longitude")] ?: 0.0
|
||||
)
|
||||
}
|
||||
|
||||
private fun retrieveServerList(pref: Preferences): List<ProxyServer> {
|
||||
val serverString = pref[stringPreferencesKey("serverList")]
|
||||
Log.d("serverString", "retrieveServerList: ")
|
||||
val type: Type = object : TypeToken<List<ProxyServer?>?>() {}.type
|
||||
return Gson().fromJson(serverString, type) ?: listOf()
|
||||
}
|
||||
|
||||
|
||||
suspend fun clear() {
|
||||
context.dataStore.edit { it.clear() }
|
||||
}
|
||||
|
||||
suspend fun saveTimeInterval(interval: Int) {
|
||||
context.dataStore.edit { pref ->
|
||||
pref[intPreferencesKey("interval")] = interval
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveUserAndPassword(userPass: String) {
|
||||
context.dataStore.edit { pref ->
|
||||
pref[stringPreferencesKey("userPass")] = userPass
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun savePorts(port1: Int, port2: Int, port3: Int) {
|
||||
context.dataStore.edit { pref ->
|
||||
pref[intPreferencesKey("port1")] = port1
|
||||
pref[intPreferencesKey("port2")] = port2
|
||||
pref[intPreferencesKey("port3")] = port3
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun retrievePorts(): Triple<Int, Int, Int> {
|
||||
return Triple(
|
||||
context.dataStore.data.first()[intPreferencesKey("port1")] ?: 0,
|
||||
context.dataStore.data.first()[intPreferencesKey("port2")] ?: 0,
|
||||
context.dataStore.data.first()[intPreferencesKey("port3")] ?: 0
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun retrieveInterval(pref: Preferences): Int? {
|
||||
return pref[intPreferencesKey("interval")]
|
||||
}
|
||||
|
||||
private fun retrievePass(pref: Preferences): String {
|
||||
return pref[stringPreferencesKey("userPass")] ?: ""
|
||||
}
|
||||
|
||||
suspend fun saveServerConfig(config: String) {
|
||||
context.dataStore.edit { pref ->
|
||||
pref[stringPreferencesKey("selected")] = config
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun serverConfig(): String {
|
||||
return context.dataStore.data.first()[stringPreferencesKey("selected")] ?: "Auto"
|
||||
}
|
||||
|
||||
suspend fun saveProxyServer(device: ProxyServer) {
|
||||
context.dataStore.edit { pref ->
|
||||
pref[stringPreferencesKey("host")] = device.IP
|
||||
pref[intPreferencesKey("port")] = device.port
|
||||
pref[doublePreferencesKey("latitude")] = device.latitude
|
||||
pref[doublePreferencesKey("longitude")] = device.longitude
|
||||
pref[stringPreferencesKey("token")] = device.token ?: ""
|
||||
pref[stringPreferencesKey("region")] = device.region
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveServers(list: List<ProxyServer>) {
|
||||
context.dataStore.edit { pref ->
|
||||
pref[stringPreferencesKey("serverList")] = Gson().toJson(list)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
var INSTANCE: ServerPreference? = null
|
||||
fun getInstance(context: Context): ServerPreference {
|
||||
return INSTANCE ?: ServerPreference(context).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
app/src/main/java/com/legacy/android/UpdateReceiver.kt
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class UpdateReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
Log.d("TEST", "onReceive: ")
|
||||
context.startService(
|
||||
Intent(
|
||||
context,
|
||||
NotificationService::class.java
|
||||
).apply { action = ACTION_ROTATE })
|
||||
|
||||
val pref = ServerPreference.getInstance(context)
|
||||
val interval = runBlocking { pref.interval.first() }
|
||||
interval?.let {
|
||||
if (it > 0) {
|
||||
context.setAlarm(SystemClock.elapsedRealtime() + (it * 60_000))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
31
app/src/main/java/com/legacy/android/Utils.kt
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
|
||||
|
||||
fun getRandomPort(from: Int = 10000, to: Int = 60000): Int {
|
||||
return ((Math.random() * (to - from)).toInt()) + from
|
||||
}
|
||||
|
||||
fun getAlphaNumericString(length: Int = 4): String {
|
||||
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
||||
return (1..length)
|
||||
.map { allowedChars.random() }
|
||||
.joinToString("")
|
||||
}
|
||||
|
||||
fun Context.setAlarm(triggerTime: Long) {
|
||||
Log.d("TEST", "setAlarm: ")
|
||||
val startIntent = Intent(this, UpdateReceiver::class.java)
|
||||
val pendingIntent =
|
||||
PendingIntent.getBroadcast(this, 1, startIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, pendingIntent)
|
||||
}
|
||||
89
app/src/main/java/com/legacy/android/di/NetworkModule.kt
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.di
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.Gson
|
||||
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
|
||||
import com.legacy.android.BuildConfig
|
||||
import com.legacy.android.ServerPreference
|
||||
import com.legacy.android.network.NetworkService
|
||||
import com.legacy.android.network.NetworkService.Companion.HOST_NAME_
|
||||
import com.legacy.android.network.ServerService
|
||||
import com.legacy.android.network.ServerService.Companion.HOST_NAME
|
||||
import com.legacy.android.util.ApiResponseCallAdapterFactory
|
||||
import com.legacy.android.util.LiveDataCallAdapterFactory
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.create
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class NetworkModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideInterceptor(): HttpLoggingInterceptor =
|
||||
HttpLoggingInterceptor()
|
||||
.setLevel(
|
||||
when {
|
||||
BuildConfig.DEBUG -> HttpLoggingInterceptor.Level.BODY
|
||||
else -> HttpLoggingInterceptor.Level.NONE
|
||||
}
|
||||
)
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideHttpClient(
|
||||
loggingInterceptor: HttpLoggingInterceptor,
|
||||
): OkHttpClient =
|
||||
OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.build()
|
||||
|
||||
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNetworkServiceApi(
|
||||
okHttpClient: OkHttpClient
|
||||
|
||||
): NetworkService =
|
||||
Retrofit.Builder()
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create(Gson()))
|
||||
.addCallAdapterFactory(NetworkResponseAdapterFactory())
|
||||
.addCallAdapterFactory(LiveDataCallAdapterFactory())
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory())
|
||||
.baseUrl(HOST_NAME_)
|
||||
.build()
|
||||
.create()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideServerServiceApi(
|
||||
okHttpClient: OkHttpClient
|
||||
|
||||
): ServerService =
|
||||
Retrofit.Builder()
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create(Gson()))
|
||||
.addCallAdapterFactory(NetworkResponseAdapterFactory())
|
||||
.addCallAdapterFactory(LiveDataCallAdapterFactory())
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory())
|
||||
.baseUrl(HOST_NAME)
|
||||
.build()
|
||||
.create()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAuthPreferences(@ApplicationContext context: Context): ServerPreference =
|
||||
ServerPreference.getInstance(context)
|
||||
}
|
||||
16
app/src/main/java/com/legacy/android/network/InfoModel.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.network
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class InfoModel(
|
||||
@SerializedName("ip") val IP: String,
|
||||
@SerializedName("continent_code") val continentCode: String?,
|
||||
@SerializedName("latitude") val latitude: Double,
|
||||
@SerializedName("longitude") val longitude: Double,
|
||||
var isDummy: Boolean = false
|
||||
)
|
||||
|
||||
fun dummy() = InfoModel("", "", 0.0, 0.0, true)
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.network
|
||||
|
||||
import androidx.lifecycle.asLiveData
|
||||
import com.legacy.android.ServerPreference
|
||||
import com.legacy.android.util.networkBoundResource
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class NetworkRepository @Inject constructor(
|
||||
private val service: ServerService,
|
||||
private val networkService: NetworkService, private val pref: ServerPreference
|
||||
) {
|
||||
|
||||
fun getServerList() = networkBoundResource(
|
||||
saveCallResult = {
|
||||
pref.saveServers(it.list)
|
||||
},
|
||||
shouldFetch = { true },
|
||||
fetch = { service.getServers() },
|
||||
loadFromDb = { pref.serverList.asLiveData() }
|
||||
)
|
||||
|
||||
suspend fun getIpInfo() = networkService.getIpInfo()
|
||||
|
||||
|
||||
suspend fun saveServer(server: ProxyServer) {
|
||||
pref.saveProxyServer(server)
|
||||
}
|
||||
suspend fun saveServerConfig(config: String) {
|
||||
pref.saveServerConfig(config)
|
||||
}
|
||||
suspend fun getCurrentConfig() = pref.serverConfig()
|
||||
|
||||
val currentServer = pref.SERVER
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.network
|
||||
|
||||
|
||||
import com.haroldadmin.cnradapter.NetworkResponse
|
||||
import retrofit2.http.GET
|
||||
|
||||
|
||||
interface NetworkService {
|
||||
|
||||
@GET("json")
|
||||
suspend fun getIpInfo(): NResponse<InfoModel>
|
||||
|
||||
companion object {
|
||||
const val HOST_NAME_ = "https://ipapi.co/"
|
||||
}
|
||||
}
|
||||
typealias NResponse<T> = NetworkResponse<T, Any>
|
||||
28
app/src/main/java/com/legacy/android/network/ProxyServer.kt
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.network
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
data class ProxyServerResponse(@SerializedName("servers") val list:List<ProxyServer>)
|
||||
data class ProxyServer(
|
||||
@SerializedName("ip") val IP: String,
|
||||
@SerializedName("region") val region: String,
|
||||
@SerializedName("port") val port: Int,
|
||||
@SerializedName("token") val token: String?,
|
||||
@SerializedName("latitude") val latitude: Double,
|
||||
@SerializedName("longitude") val longitude: Double
|
||||
) {
|
||||
companion object {
|
||||
fun default() = ProxyServer(
|
||||
"0.0.0.0",
|
||||
"Create Your Own",
|
||||
1000,
|
||||
"00000000",
|
||||
0.0,
|
||||
0.0
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.network
|
||||
|
||||
import com.legacy.android.util.ApiResponse
|
||||
import retrofit2.http.GET
|
||||
|
||||
interface ServerService {
|
||||
|
||||
@GET("json")
|
||||
suspend fun getServers(): ApiResponse<ProxyServerResponse>
|
||||
|
||||
companion object {
|
||||
const val HOST_NAME = "https://ipapi.co/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.service
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* */
|
||||
class ProxidizeServer(private val context: Context) : NanoHTTPD(8080) {
|
||||
override fun serve(session: IHTTPSession): Response {
|
||||
val method = session.method
|
||||
val uri = session.uri
|
||||
LOG.info("$method '$uri' ")
|
||||
return when (method) {
|
||||
Method.GET -> handleGetRequest(session)
|
||||
else -> newFixedLengthResponse(
|
||||
Response.Status.NOT_FOUND,
|
||||
"application/json",
|
||||
response404()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param session
|
||||
* @return
|
||||
* */
|
||||
private fun handleGetRequest(session: IHTTPSession): Response {
|
||||
return when (session.uri) {
|
||||
"/change_ip" -> changeIp(context, session.parameters["t"]?.get(0).toString())
|
||||
else -> newFixedLengthResponse(
|
||||
Response.Status.NOT_FOUND,
|
||||
"application/json",
|
||||
response404()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOG: Logger = Logger.getLogger(ProxidizeServer::class.java.name)
|
||||
}
|
||||
}
|
||||
62
app/src/main/java/com/legacy/android/service/Utills.kt
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.service
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import com.legacy.android.ServerPreference
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.json.JSONObject
|
||||
|
||||
fun response404() = JSONObject().put("Response", "Page not found").toString()
|
||||
fun responseError(status: NanoHTTPD.Response.IStatus, msg: String): NanoHTTPD.Response =
|
||||
NanoHTTPD.newFixedLengthResponse(
|
||||
status,
|
||||
"application/json",
|
||||
JSONObject().put("response", msg).toString()
|
||||
)
|
||||
|
||||
|
||||
fun changeIp(context: Context, t: String?): NanoHTTPD.Response {
|
||||
val json = JSONObject()
|
||||
if (!isDefaultCurrentAssist(context)) {
|
||||
json.put("response", "Default assistant is required")
|
||||
} else if (t != runBlocking { ServerPreference.getInstance(context).pass.first() }) {
|
||||
json.put("response", "invalid password")
|
||||
} else {
|
||||
toggleAirPlaneMode(context)
|
||||
json.put("response", "success")
|
||||
}
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"application/json",
|
||||
json.toString()
|
||||
)
|
||||
}
|
||||
|
||||
fun isDefaultCurrentAssist(context: Context): Boolean {
|
||||
val setting = Settings.Secure.getString(context.contentResolver, "assistant")
|
||||
return if (setting != null) {
|
||||
ComponentName.unflattenFromString(setting)?.packageName == context.packageName
|
||||
} else false
|
||||
}
|
||||
|
||||
fun toggleAirPlaneMode(context: Context) {
|
||||
if (!isDefaultCurrentAssist(context)) return
|
||||
context.startService(Intent(context, VoiceInteractionServiceImp::class.java).setAction("start"))
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
context.startService(
|
||||
Intent(
|
||||
context,
|
||||
VoiceInteractionServiceImp::class.java
|
||||
).setAction("stop")
|
||||
)
|
||||
}, 5000)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.service
|
||||
|
||||
import android.app.Service
|
||||
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.service.voice.VoiceInteractionService
|
||||
import android.service.voice.VoiceInteractionSession
|
||||
|
||||
class VoiceInteractionServiceImp : VoiceInteractionService() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
try {
|
||||
if (intent?.action == "start")
|
||||
showSession(Bundle().apply {
|
||||
putBoolean(
|
||||
Settings.EXTRA_AIRPLANE_MODE_ENABLED,
|
||||
true
|
||||
)
|
||||
}, VoiceInteractionSession.SHOW_WITH_SCREENSHOT)
|
||||
else if (intent?.action == "stop")
|
||||
showSession(Bundle().apply {
|
||||
putBoolean(
|
||||
Settings.EXTRA_AIRPLANE_MODE_ENABLED,
|
||||
false
|
||||
)
|
||||
}, VoiceInteractionSession.SHOW_WITH_SCREENSHOT)
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
stopSelf()
|
||||
|
||||
return START_NOT_STICKY
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.service
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.service.voice.VoiceInteractionSession
|
||||
import android.view.View
|
||||
import com.legacy.android.R
|
||||
|
||||
|
||||
class VoiceInteractionSessionImp(context: Context) : VoiceInteractionSession(context) {
|
||||
|
||||
override fun onCreateContentView(): View {
|
||||
return layoutInflater.inflate(R.layout.activity_start_service, null, false)
|
||||
}
|
||||
|
||||
override fun onShow(args: Bundle?, showFlags: Int) {
|
||||
super.onShow(args, showFlags)
|
||||
startVoiceActivity(Intent(Settings.ACTION_VOICE_CONTROL_AIRPLANE_MODE).also {
|
||||
it.putExtra(
|
||||
Settings.EXTRA_AIRPLANE_MODE_ENABLED,
|
||||
args?.getBoolean(Settings.EXTRA_AIRPLANE_MODE_ENABLED)
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
override fun onTaskStarted(intent: Intent?, taskId: Int) {
|
||||
super.onTaskStarted(intent, taskId)
|
||||
}
|
||||
|
||||
override fun onTaskFinished(intent: Intent?, taskId: Int) {
|
||||
super.onTaskFinished(intent, taskId)
|
||||
hide()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.service
|
||||
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.service.voice.VoiceInteractionSession
|
||||
import android.service.voice.VoiceInteractionSessionService
|
||||
|
||||
class VoiceInteractionSessionService : VoiceInteractionSessionService() {
|
||||
override fun onNewSession(args: Bundle): VoiceInteractionSession {
|
||||
return VoiceInteractionSessionImp(this)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
}
|
||||
95
app/src/main/java/com/legacy/android/ui/NavDrawerActivity.kt
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.ButtonBarLayout
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import androidx.core.content.ContentProviderCompat.requireContext
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.legacy.android.R
|
||||
import com.legacy.android.ServerPreference
|
||||
import com.legacy.android.databinding.ActivityNavDrawerBinding
|
||||
import com.legacy.android.network.ProxyServer
|
||||
import com.legacy.android.ui.ui.home.HomeFragment
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dagger.hilt.android.components.ViewWithFragmentComponent
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class NavDrawerActivity : AppCompatActivity() {
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
private lateinit var binding: ActivityNavDrawerBinding
|
||||
private val preference by lazy { ServerPreference.getInstance(this) }
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityNavDrawerBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.appBarNavDrawer.toolbar)
|
||||
|
||||
|
||||
val drawerLayout: DrawerLayout = binding.drawerLayout
|
||||
val navView: NavigationView = binding.navView
|
||||
val navController = findNavController(R.id.nav_host_fragment_content_nav_drawer)
|
||||
appBarConfiguration = AppBarConfiguration(
|
||||
setOf(
|
||||
R.id.nav_home, R.id.nav_gallery
|
||||
), drawerLayout
|
||||
)
|
||||
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
navView.setupWithNavController(navController)
|
||||
|
||||
val resetButton = navView.menu.findItem(R.id.resetDefaultsButton).actionView as Button?
|
||||
resetButton?.setOnClickListener {
|
||||
Toast.makeText(this, "Proxy server set to default", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
lifecycleScope.launch {
|
||||
delay(100)
|
||||
preference.clear()
|
||||
showMessage()
|
||||
closeOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun showMessage() {
|
||||
Toast.makeText(
|
||||
this,
|
||||
"Changes saved, please restart the app",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
val navController = findNavController(R.id.nav_host_fragment_content_nav_drawer)
|
||||
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.ui.ui.gallery
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.os.Bundle
|
||||
import android.provider.SyncStateContract.Helpers.update
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.legacy.android.*
|
||||
import com.legacy.android.databinding.ActivityCustomServerBinding
|
||||
import com.legacy.android.network.ProxyServer
|
||||
import com.legacy.android.ui.ui.home.HomeFragment
|
||||
import com.legacy.android.ui.ui.home.HomeViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ChangeServerFragment : Fragment() {
|
||||
private lateinit var dbHelper: CredentialsDatabase
|
||||
private var _binding: ActivityCustomServerBinding? = null
|
||||
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val viewModel by activityViewModels<HomeViewModel>()
|
||||
private val preference by lazy { ServerPreference.getInstance(requireContext()) }
|
||||
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
|
||||
_binding = ActivityCustomServerBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
Log.d("Sever", "onViewCreated: shit is happening")
|
||||
val application = requireActivity().application as MyApplication
|
||||
binding.textField.onItemClickListener =
|
||||
AdapterView.OnItemClickListener { _, _, p2, _ ->
|
||||
setSelectedServer()
|
||||
}
|
||||
viewModel.servers.observe(viewLifecycleOwner) { resource ->
|
||||
|
||||
resource.data?.let { servers ->
|
||||
val list = mutableListOf("Auto", "Custom")
|
||||
list.addAll(servers.map { it.region })
|
||||
binding.textField.setAdapter(
|
||||
ArrayAdapter(
|
||||
requireContext(),
|
||||
R.layout.list_item,
|
||||
list
|
||||
)
|
||||
)
|
||||
val config = runBlocking { viewModel.getCurrentConfig() }
|
||||
binding.textField.setText(
|
||||
binding.textField.adapter.getItem(list.indexOf(config)).toString(),
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
binding.saveButton.setOnClickListener {
|
||||
if (isClear()) {
|
||||
Toast.makeText(requireContext(), "Saving server info", Toast.LENGTH_SHORT).show()
|
||||
lifecycleScope.launch {
|
||||
viewModel.serverConfig("Custom")
|
||||
viewModel.selectedServer(
|
||||
ProxyServer(
|
||||
binding.etHost.text.toString(),
|
||||
"Custom",
|
||||
binding.etPort.text.toString().toInt(),
|
||||
binding.etToken.text.toString(),
|
||||
0.0, 0.0
|
||||
)
|
||||
)
|
||||
preference.saveProxyServer(
|
||||
ProxyServer(
|
||||
binding.etHost.text.toString(),
|
||||
"Custom",
|
||||
binding.etPort.text.toString().toInt(),
|
||||
binding.etToken.text.toString(),
|
||||
0.0, 0.0
|
||||
)
|
||||
)
|
||||
|
||||
val values = ContentValues().apply {
|
||||
put("hostIp",binding.etHost.text.toString())
|
||||
put("region","Custom")
|
||||
put("port",binding.etPort.text.toString().toInt())
|
||||
put("token",binding.etToken.text.toString())
|
||||
put("latitude",0.0)
|
||||
put("longitude",0.0)
|
||||
}
|
||||
val newRowId = CredentialsDatabase(requireContext()).writableDatabase.update("mycredentials",values,"hostIp = ?", arrayOf("0.0.0.0"))
|
||||
showMessage()
|
||||
requireActivity().onBackPressed()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun showMessage() {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"Changes saved, please restart the app",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun setSelectedServer() {
|
||||
val item = binding.textField.text.toString()
|
||||
Log.d("FUCK", "setSelectedServer: $item")
|
||||
binding.group.isVisible = item == "Custom"
|
||||
if (item == "Custom")
|
||||
return
|
||||
if (item == "Auto") {
|
||||
showMessage()
|
||||
viewModel.servers.value?.data?.let { viewModel.calculateServer(it) }
|
||||
} else {
|
||||
val selected = viewModel.servers.value?.data?.findLast { it.region == item }
|
||||
selected?.let {
|
||||
showMessage()
|
||||
viewModel.serverConfig(selected.region)
|
||||
viewModel.selectedServer(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun isClear(): Boolean {
|
||||
var clear = true
|
||||
if (binding.etHost.text.isNullOrEmpty()) {
|
||||
binding.etHost.error = "Enter a valid Host"
|
||||
clear = false
|
||||
} else binding.etHost.error = null
|
||||
if (binding.etPort.text.isNullOrEmpty()) {
|
||||
binding.etPort.error = "Enter a valid Port"
|
||||
clear = false
|
||||
} else binding.etPort.error = null
|
||||
if (binding.etToken.text.isNullOrEmpty()) {
|
||||
binding.etToken.error = "Enter a valid token"
|
||||
clear = false
|
||||
} else binding.etToken.error = null
|
||||
return clear
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
533
app/src/main/java/com/legacy/android/ui/ui/home/HomeFragment.kt
Normal file
@@ -0,0 +1,533 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.ui.ui.home
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.*
|
||||
import android.provider.Settings
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.*
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.legacy.android.*
|
||||
import com.legacy.android.databinding.FragmentHomeBinding
|
||||
import com.legacy.android.network.ProxyServer
|
||||
import com.legacy.android.service.isDefaultCurrentAssist
|
||||
import com.legacy.android.ui.NavDrawerActivity
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import frpclib.Frpclib
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HomeFragment : Fragment() {
|
||||
private var user: String? = null
|
||||
private var pwd: String? = null
|
||||
private var notificationManager: NotificationManagerCompat? = null
|
||||
private var mRandomPortWeb = 0
|
||||
private var mRandomPortHttp = 0
|
||||
private var mRandomPortSocks = 0
|
||||
|
||||
private var server: ProxyServer = ProxyServer.default()
|
||||
|
||||
private val preference by lazy { ServerPreference.getInstance(requireContext()) }
|
||||
|
||||
private var _binding: FragmentHomeBinding? = null
|
||||
|
||||
private val viewModel by activityViewModels<HomeViewModel>()
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
activity?.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
activity?.window?.statusBarColor =
|
||||
ContextCompat.getColor(requireContext(), R.color.colorPrimaryDark)
|
||||
}
|
||||
|
||||
viewModel.currentServer.observe(viewLifecycleOwner) { proxyServer ->
|
||||
proxyServer?.let { server = it }
|
||||
}
|
||||
viewModel.servers.observe(viewLifecycleOwner) {
|
||||
if (getActiveNotification() == null && runBlocking { viewModel.getCurrentConfig() == "Auto" }) {
|
||||
it.data?.let { it1 -> viewModel.calculateServer(it1) }
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
preference.SERVER.collect {
|
||||
server = it
|
||||
}
|
||||
}
|
||||
}
|
||||
notificationManager = NotificationManagerCompat.from(requireContext())
|
||||
|
||||
val pm = requireContext().getSystemService(PowerManager::class.java)
|
||||
if (!pm.isIgnoringBatteryOptimizations(requireContext().packageName)) {
|
||||
val i = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
|
||||
.setData(Uri.parse("package:${requireContext().packageName}"))
|
||||
startActivity(i)
|
||||
}
|
||||
binding.copyHttp.setOnClickListener {
|
||||
val clipboard =
|
||||
requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Htt Proxy", binding.connectionHttp.text)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
Toast.makeText(requireContext(), "Copied", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
binding.copySocks.setOnClickListener {
|
||||
val clipboard =
|
||||
requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Socks Proxy", binding.connectionSocks.text)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
Toast.makeText(requireContext(), "Copied", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
binding.copyIpChange.setOnClickListener {
|
||||
val clipboard =
|
||||
requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("IP Change", binding.connectionIp.text)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
Toast.makeText(requireContext(), "Copied", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
|
||||
binding.stop.setOnClickListener {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setCancelable(false)
|
||||
.setTitle("Confirm Exit")
|
||||
.setMessage("Are you sure, You want to exit")
|
||||
.setPositiveButton("sure") { d, _ ->
|
||||
requireContext().stopService(
|
||||
Intent(
|
||||
requireContext().applicationContext,
|
||||
NotificationService::class.java
|
||||
)
|
||||
)
|
||||
d.dismiss()
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
exitProcess(0)
|
||||
}, 100)
|
||||
}.setNegativeButton("NO", null)
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
binding.connect.setOnClickListener {
|
||||
writeToConfigFile(server, false)
|
||||
Toast.makeText(requireContext(), "Connecting to proxy server", Toast.LENGTH_LONG)
|
||||
.show()
|
||||
startConnection()
|
||||
Handler(Looper.getMainLooper()).postDelayed({ checkIfPortUsed() }, 3000)
|
||||
// context?.let { it1 -> createCredentialsFile(server, it1) }
|
||||
}
|
||||
binding.rotate.setOnClickListener {
|
||||
requireContext().startService(
|
||||
Intent(
|
||||
requireContext().applicationContext,
|
||||
NotificationService::class.java
|
||||
).apply { action = ACTION_ROTATE })
|
||||
}
|
||||
binding.autoRotate.setOnClickListener {
|
||||
showAutoRotate()
|
||||
}
|
||||
binding.githubLink.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
|
||||
private fun showAutoRotate() {
|
||||
val viewInflated: View = LayoutInflater.from(requireContext())
|
||||
.inflate(R.layout.input, binding.root as ViewGroup?, false)
|
||||
val input = viewInflated.findViewById<View>(R.id.input) as EditText
|
||||
|
||||
val dialog = AlertDialog.Builder(requireContext())
|
||||
.setTitle("Custom IP Rotation")
|
||||
.setMessage("Please input time in minutes for the auto IP rotation.")
|
||||
.setView(viewInflated)
|
||||
.setPositiveButton(
|
||||
"Set", null
|
||||
)
|
||||
.setNegativeButton(
|
||||
"Cancel"
|
||||
) { _, _ -> }
|
||||
.create()
|
||||
dialog.setOnShowListener {
|
||||
val button: Button =
|
||||
(dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
button.setOnClickListener {
|
||||
val intervalString = input.text.toString()
|
||||
try {
|
||||
val interval = intervalString.toInt()
|
||||
if (interval > 0) {
|
||||
runBlocking { preference.saveTimeInterval(interval) }
|
||||
requireContext().setAlarm(SystemClock.elapsedRealtime() + interval * 60_000)
|
||||
dialog.dismiss()
|
||||
} else {
|
||||
input.error = "Please input valid time interval"
|
||||
}
|
||||
} catch (e: NumberFormatException) {
|
||||
input.error = "Please input valid time interval"
|
||||
}
|
||||
}
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun checkIfPortUsed(): Boolean {
|
||||
var connectionStatus = true
|
||||
val logfile = File(requireContext().filesDir, MyApplication.LOGFILE)
|
||||
if (logfile.exists()) {
|
||||
try {
|
||||
val inputStream: InputStream = requireContext().assets.open(MyApplication.LOGFILE)
|
||||
var line: String? = null
|
||||
val br = BufferedReader(FileReader(logfile))
|
||||
var isFileEmpty = true
|
||||
while ((br.readLine()?.also { line = it }) != null) {
|
||||
isFileEmpty = false
|
||||
if (line?.contains("port already used") == true) {
|
||||
connectionStatus = false
|
||||
writeToConfigFile(server, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!connectionStatus) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"Port is already used. Try again",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
} else if (isFileEmpty) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"Not connected. Restart app and connect again",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
} else {
|
||||
showConnectionDetails()
|
||||
val bundle = Bundle().also {
|
||||
it.putInt(PORT_HTTP, mRandomPortHttp)
|
||||
it.putInt(PORT_IP, mRandomPortWeb)
|
||||
it.putInt(PORT_SOCKS, mRandomPortSocks)
|
||||
it.putString(USER, user)
|
||||
it.putString(PASS, pwd)
|
||||
}
|
||||
Toast.makeText(requireContext(), "connection success", Toast.LENGTH_LONG)
|
||||
.show()
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
val sdf = SimpleDateFormat("HH:mm:ss z")
|
||||
val currentTime = sdf.format(Date())
|
||||
val largeIcon = BitmapFactory.decodeResource(resources, R.drawable.logo)
|
||||
val intent1 = Intent(requireContext(), NotificationService::class.java)
|
||||
intent1.action = ACTION_CLOSE
|
||||
var pIntent: PendingIntent? = null
|
||||
pIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.getService(
|
||||
requireContext(),
|
||||
0,
|
||||
intent1,
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
} else PendingIntent.getService(requireContext(), 0, intent1, 0)
|
||||
|
||||
val p2Inten = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.getActivity(
|
||||
requireContext(),
|
||||
2,
|
||||
Intent(requireContext(), NavDrawerActivity::class.java),
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
} else {
|
||||
PendingIntent.getActivity(
|
||||
requireContext(),
|
||||
2,
|
||||
Intent(requireContext(), NavDrawerActivity::class.java),
|
||||
0
|
||||
)
|
||||
}
|
||||
val notification = NotificationCompat.Builder(
|
||||
requireContext().applicationContext,
|
||||
MyApplication.channel_1_id
|
||||
)
|
||||
.setSmallIcon(R.drawable.bill)
|
||||
.setLargeIcon(largeIcon)
|
||||
.setStyle(NotificationCompat.BigPictureStyle())
|
||||
.setStyle(NotificationCompat.BigTextStyle())
|
||||
.setContentIntent(p2Inten)
|
||||
.setContentTitle("Connected")
|
||||
.setExtras(bundle)
|
||||
.addAction(
|
||||
NotificationCompat.Action(
|
||||
R.drawable.ic_baseline_close_24,
|
||||
"Close",
|
||||
pIntent
|
||||
)
|
||||
)
|
||||
.setContentText(
|
||||
"You are Connected to (${server.IP}:$mRandomPortHttp:$user:$pwd)On time$currentTime"
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||
.setColor(Color.YELLOW).build()
|
||||
notificationManager?.notify(NOTIFICATION_ID, notification)
|
||||
}, 2000)
|
||||
val servicesIntent =
|
||||
Intent(requireContext().applicationContext, NotificationService::class.java)
|
||||
servicesIntent.putExtra("stat", "Proxidize Android is running!!")
|
||||
requireContext().startService(servicesIntent)
|
||||
}
|
||||
br.close()
|
||||
inputStream.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return connectionStatus
|
||||
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
showDefaultAssistant()
|
||||
val notification = getActiveNotification() ?: return
|
||||
val extras = notification.extras
|
||||
pwd = extras.getString(PASS)
|
||||
user = extras.getString(USER)
|
||||
mRandomPortHttp = extras.getInt(PORT_HTTP)
|
||||
mRandomPortSocks = extras.getInt(PORT_SOCKS)
|
||||
mRandomPortWeb = extras.getInt(PORT_IP)
|
||||
showConnectionDetails()
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun showDefaultAssistant() {
|
||||
if (!isDefaultCurrentAssist(requireContext()))
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle("Default Assistant")
|
||||
.setMessage("Proxidize requires the default assistant permission to change the IP of the device.")
|
||||
.setPositiveButton("Set") { _, _ ->
|
||||
startActivityForResult(
|
||||
Intent(Settings.ACTION_VOICE_INPUT_SETTINGS),
|
||||
0
|
||||
)
|
||||
}
|
||||
.setNegativeButton("Cancel") { d, _ -> d.dismiss() }
|
||||
.show()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun showConnectionDetails() {
|
||||
binding.connectionGrp.isVisible = true
|
||||
binding.connectionHttp.text = "${server.IP}:$mRandomPortHttp:$user:$pwd"
|
||||
binding.connectionSocks.text = "${server.IP}:$mRandomPortSocks:$user:$pwd"
|
||||
binding.connectionIp.text = "${server.IP}:$mRandomPortWeb/change_ip?t=$user$pwd"
|
||||
binding.connect.text = getString(R.string.connected)
|
||||
}
|
||||
|
||||
private fun startConnection() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Frpclib.touch()
|
||||
Frpclib.run("${requireContext().filesDir}/config.ini")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getActiveNotification(): Notification? {
|
||||
val notificationManager =
|
||||
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val barNotifications = notificationManager.activeNotifications
|
||||
for (notification in barNotifications) {
|
||||
if (notification.id == NOTIFICATION_ID) {
|
||||
return notification.notification
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
//this method if we need these information for us
|
||||
private fun createCredentialsFile(server: ProxyServer,context: Context){
|
||||
val file = File(context.getExternalFilesDir(null),"CredentialsAndPortsDetails.txt")
|
||||
var out: FileOutputStream? = null
|
||||
|
||||
try {
|
||||
out = FileOutputStream(file, true)
|
||||
out.write("Connection Details : \r\n".toByteArray())
|
||||
out.write("server_addr = ${server.IP}\r\n".toByteArray())
|
||||
out.write("server_port = ${server.port}\r\n".toByteArray())
|
||||
out.write("token = ${server.token}\r\n".toByteArray())
|
||||
out.write("log_level = info\r\n".toByteArray())
|
||||
out.write("log_max_days = 3\r\n".toByteArray())
|
||||
out.write("pool_count = 5\r\n".toByteArray())
|
||||
out.write("tcp_mux = true\r\n".toByteArray())
|
||||
out.write("login_fail_exit = true\r\n".toByteArray())
|
||||
out.write("protocol = tcp\r\n".toByteArray())
|
||||
out.write("[api_$mRandomPortWeb]\r\n".toByteArray())
|
||||
out.write("type=tcp\r\n".toByteArray())
|
||||
out.write("local_ip = 0.0.0.0\r\n".toByteArray())
|
||||
out.write("local_port = 8080\r\n".toByteArray())
|
||||
out.write("remote_port = $mRandomPortWeb\r\n".toByteArray())
|
||||
out.write("[android_http_proxy_$mRandomPortHttp]\r\n".toByteArray())
|
||||
out.write("type=tcp\r\n".toByteArray())
|
||||
out.write("remote_port=$mRandomPortHttp\r\n".toByteArray())
|
||||
out.write("plugin=http_proxy\r\n".toByteArray())
|
||||
out.write("plugin_http_user=$user\r\n".toByteArray())
|
||||
out.write("plugin_http_passwd=$pwd\r\n".toByteArray())
|
||||
out.write("[android_socks_proxy_$mRandomPortSocks]\r\n".toByteArray())
|
||||
out.write("type=tcp\r\n".toByteArray())
|
||||
out.write("remote_port=$mRandomPortSocks\r\n".toByteArray())
|
||||
out.write("plugin=socks5\r\n".toByteArray())
|
||||
out.write("plugin_http_user=$user\r\n".toByteArray())
|
||||
out.write("plugin_http_passwd=$pwd\r\n".toByteArray())
|
||||
out.write("------------------------------------------------------------------------------\n".toByteArray())
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
try {
|
||||
out?.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
private fun writeToConfigFile(server: ProxyServer, resetPorts: Boolean) {
|
||||
// re-create connection.log file
|
||||
val logFile = File(requireContext().filesDir, MyApplication.LOGFILE)
|
||||
if (logFile.exists()) {
|
||||
logFile.delete()
|
||||
}
|
||||
if (!logFile.exists()) {
|
||||
try {
|
||||
logFile.createNewFile()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
var out: FileOutputStream? = null
|
||||
val file = File(requireContext().filesDir, MyApplication.FILENAME)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
if (!file.exists()) {
|
||||
try {
|
||||
file.createNewFile()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
try {
|
||||
out = FileOutputStream(file, true)
|
||||
initializePorts(resetPorts)
|
||||
out.write("[common]\r\n".toByteArray())
|
||||
out.write("server_addr = ${server.IP}\r\n".toByteArray())
|
||||
out.write("server_port = ${server.port}\r\n".toByteArray())
|
||||
out.write("token = ${server.token}\r\n".toByteArray())
|
||||
out.write("log_file = ${logFile.absolutePath}\r\n".toByteArray())
|
||||
out.write("log_level = info\r\n".toByteArray())
|
||||
out.write("log_max_days = 3\r\n".toByteArray())
|
||||
out.write("pool_count = 5\r\n".toByteArray())
|
||||
out.write("tcp_mux = true\r\n".toByteArray())
|
||||
out.write("login_fail_exit = true\r\n".toByteArray())
|
||||
out.write("protocol = tcp\r\n".toByteArray())
|
||||
out.write("[api_$mRandomPortWeb]\r\n".toByteArray())
|
||||
out.write("type=tcp\r\n".toByteArray())
|
||||
out.write("local_ip = 0.0.0.0\r\n".toByteArray())
|
||||
out.write("local_port = 8080\r\n".toByteArray())
|
||||
out.write("remote_port = $mRandomPortWeb\r\n".toByteArray())
|
||||
out.write("[android_http_proxy_$mRandomPortHttp]\r\n".toByteArray())
|
||||
out.write("type=tcp\r\n".toByteArray())
|
||||
out.write("remote_port=$mRandomPortHttp\r\n".toByteArray())
|
||||
out.write("plugin=http_proxy\r\n".toByteArray())
|
||||
out.write("plugin_http_user=$user\r\n".toByteArray())
|
||||
out.write("plugin_http_passwd=$pwd\r\n".toByteArray())
|
||||
out.write("[android_socks_proxy_$mRandomPortSocks]\r\n".toByteArray())
|
||||
out.write("type=tcp\r\n".toByteArray())
|
||||
out.write("remote_port=$mRandomPortSocks\r\n".toByteArray())
|
||||
out.write("plugin=socks5\r\n".toByteArray())
|
||||
out.write("plugin_http_user=$user\r\n".toByteArray())
|
||||
out.write("plugin_http_passwd=$pwd\r\n".toByteArray())
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
try {
|
||||
out?.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializePorts(resetPorts: Boolean) {
|
||||
val (port1, port2, port3) = runBlocking { preference.retrievePorts() }
|
||||
val portEmpty = port1 == 0 || port2 == 0 || port3 == 0
|
||||
if (resetPorts || portEmpty) {
|
||||
mRandomPortHttp = getRandomPort()
|
||||
mRandomPortWeb = getRandomPort()
|
||||
mRandomPortSocks = getRandomPort()
|
||||
lifecycleScope.launch {
|
||||
preference.savePorts(
|
||||
mRandomPortHttp,
|
||||
mRandomPortWeb,
|
||||
mRandomPortSocks
|
||||
)
|
||||
}
|
||||
} else {
|
||||
mRandomPortHttp = port1
|
||||
mRandomPortWeb = port2
|
||||
mRandomPortSocks = port3
|
||||
}
|
||||
|
||||
user = getAlphaNumericString()
|
||||
pwd = getAlphaNumericString()
|
||||
lifecycleScope.launch { preference.saveUserAndPassword("$user$pwd") }
|
||||
println(user)
|
||||
println(pwd)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NOTIFICATION_ID = 20340
|
||||
const val IP = "IP"
|
||||
const val PORT_HTTP = "PORT_HTTP"
|
||||
const val PORT_IP = "PORT_IP"
|
||||
const val PORT_SOCKS = "PORT_SOCKS"
|
||||
var USER = "USER"
|
||||
var PASS = "PASS"
|
||||
}
|
||||
|
||||
}
|
||||
107
app/src/main/java/com/legacy/android/ui/ui/home/HomeViewModel.kt
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.ui.ui.home
|
||||
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.*
|
||||
import com.google.gson.Gson
|
||||
import com.haroldadmin.cnradapter.NetworkResponse
|
||||
import com.legacy.android.network.InfoModel
|
||||
import com.legacy.android.network.NetworkRepository
|
||||
import com.legacy.android.network.ProxyServer
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.acos
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
@HiltViewModel
|
||||
class HomeViewModel @Inject constructor(
|
||||
private val repository: NetworkRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val triger = MutableLiveData(true)
|
||||
val servers = triger.switchMap { repository.getServerList() }
|
||||
|
||||
val currentServer = repository.currentServer.asLiveData()
|
||||
|
||||
suspend fun getCurrentConfig() = repository.getCurrentConfig()
|
||||
|
||||
|
||||
fun calculateServer(serverList: List<ProxyServer>) = viewModelScope.launch(Dispatchers.IO) {
|
||||
if (serverList.isEmpty())
|
||||
return@launch
|
||||
when (val ipInfo = repository.getIpInfo()) {
|
||||
is NetworkResponse.Success -> calculateDistance(ipInfo.body, serverList)
|
||||
else -> selectRandom(serverList)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun calculateDistance(infoModel: InfoModel, serverList: List<ProxyServer>) {
|
||||
Log.d("Server", "calculateDistance: ")
|
||||
if (serverList.isEmpty()) return
|
||||
val serverIndex = serverList.withIndex().minByOrNull { (_, s) ->
|
||||
distance(
|
||||
infoModel.latitude,
|
||||
infoModel.longitude,
|
||||
s.latitude,
|
||||
s.longitude
|
||||
)
|
||||
}?.index
|
||||
Log.d("Server", "calculateDistance: index $serverIndex")
|
||||
val server = serverList[serverIndex ?: 0]
|
||||
repository.saveServerConfig("Auto")
|
||||
repository.saveServer(server)
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun selectRandom(serverList: List<ProxyServer>) {
|
||||
Log.d("Server", "selectRandom: ")
|
||||
if (serverList.isEmpty()) return
|
||||
|
||||
viewModelScope.launch {
|
||||
repository.saveServerConfig("Auto")
|
||||
repository.saveServer(serverList.random())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun selectedServer(proxyServer: ProxyServer) {
|
||||
viewModelScope.launch {
|
||||
repository.saveServer(proxyServer)
|
||||
}
|
||||
}
|
||||
|
||||
fun serverConfig(config: String) {
|
||||
viewModelScope.launch {
|
||||
repository.saveServerConfig(config)
|
||||
}
|
||||
}
|
||||
|
||||
private fun distance(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
|
||||
val theta = lon1 - lon2
|
||||
var dist = (sin(deg2rad(lat1))
|
||||
* sin(deg2rad(lat2))
|
||||
+ (cos(deg2rad(lat1))
|
||||
* cos(deg2rad(lat2))
|
||||
* cos(deg2rad(theta))))
|
||||
dist = acos(dist)
|
||||
dist = rad2deg(dist)
|
||||
dist *= 60 * 1.1515
|
||||
return dist
|
||||
}
|
||||
|
||||
private fun deg2rad(deg: Double): Double {
|
||||
return deg * Math.PI / 180.0
|
||||
}
|
||||
|
||||
private fun rad2deg(rad: Double): Double {
|
||||
return rad * 180.0 / Math.PI
|
||||
}
|
||||
|
||||
}
|
||||
54
app/src/main/java/com/legacy/android/util/ApiResponse.kt
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.util
|
||||
|
||||
import android.util.Log
|
||||
import org.json.JSONObject
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* @param
|
||||
</T> */
|
||||
@Suppress("unused") // T is used in extending classes
|
||||
sealed class ApiResponse<T> {
|
||||
companion object {
|
||||
fun <T> create(error: Throwable): ApiErrorResponse<T> {
|
||||
Log.d("Server", "create: $error")
|
||||
val errorMessage = if (error is IOException) "Network error" else "Something went wrong"
|
||||
return ApiErrorResponse(errorMessage )
|
||||
}
|
||||
|
||||
fun <T> create(response: Response<T>): ApiResponse<T> {
|
||||
return if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
if (body == null || response.code() == 204) {
|
||||
ApiEmptyResponse()
|
||||
} else {
|
||||
ApiSuccessResponse(
|
||||
body = body
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val msg = response.errorBody()?.string()
|
||||
|
||||
val errorMsg = if (msg.isNullOrEmpty()) {
|
||||
response.message()
|
||||
} else {
|
||||
val json = JSONObject(msg)
|
||||
json.getString("message")
|
||||
}
|
||||
ApiErrorResponse(errorMsg ?: "unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ApiEmptyResponse<T> : ApiResponse<T>()
|
||||
|
||||
public data class ApiSuccessResponse<T>(
|
||||
val body: T
|
||||
) : ApiResponse<T>()
|
||||
|
||||
public data class ApiErrorResponse<T>(val errorMessage: String) : ApiResponse<T>()
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.util
|
||||
|
||||
|
||||
import okhttp3.Request
|
||||
import okio.Timeout
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
internal class ApiResponseCall<T>(
|
||||
private val delegate: Call<T>
|
||||
) : Call<ApiResponse<T>> {
|
||||
override fun enqueue(realCallback: Callback<ApiResponse<T>>) {
|
||||
delegate.enqueue(object : Callback<T> {
|
||||
override fun onFailure(call: Call<T>, error: Throwable) {
|
||||
realCallback.onResponse(
|
||||
this@ApiResponseCall,
|
||||
Response.success(ApiResponse.create(error))
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<T>, response: Response<T>) {
|
||||
realCallback.onResponse(
|
||||
this@ApiResponseCall, Response.success(
|
||||
ApiResponse.create(response)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
override fun isExecuted() = delegate.isExecuted
|
||||
|
||||
override fun clone(): Call<ApiResponse<T>> = ApiResponseCall(delegate)
|
||||
|
||||
override fun isCanceled() = delegate.isCanceled
|
||||
|
||||
override fun cancel() = delegate.cancel()
|
||||
|
||||
override fun execute(): Response<ApiResponse<T>> {
|
||||
return Response.success(ApiResponse.create(delegate.execute()))
|
||||
}
|
||||
|
||||
override fun request(): Request = delegate.request()
|
||||
override fun timeout(): Timeout {
|
||||
return Timeout()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ApiResponseCallAdapter<R>(
|
||||
private val bodyType: Type,
|
||||
private val delegate: CallAdapter<R, Call<R>>
|
||||
) : CallAdapter<R, Call<ApiResponse<R>>> {
|
||||
override fun adapt(original: Call<R>): Call<ApiResponse<R>> {
|
||||
return ApiResponseCall(delegate.adapt(original))
|
||||
}
|
||||
|
||||
override fun responseType(): Type = bodyType
|
||||
}
|
||||
|
||||
class ApiResponseCallAdapterFactory : CallAdapter.Factory() {
|
||||
override fun get(
|
||||
returnType: Type,
|
||||
annotations: Array<Annotation>,
|
||||
retrofit: Retrofit
|
||||
): CallAdapter<*, *>? {
|
||||
val parameterizedReturn = returnType as? ParameterizedType ?: return null
|
||||
if (parameterizedReturn.rawType != Call::class.java) {
|
||||
return null
|
||||
}
|
||||
val parameterizedApiResponse =
|
||||
parameterizedReturn.actualTypeArguments.firstOrNull() as? ParameterizedType
|
||||
?: return null
|
||||
val bodyType = parameterizedApiResponse.actualTypeArguments.firstOrNull() ?: return null
|
||||
val callBody = OneArgParameterizedType(Call::class.java, arrayOf(bodyType))
|
||||
val delegate = retrofit.callAdapter(callBody, annotations) ?: return null
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return ApiResponseCallAdapter(bodyType, delegate as CallAdapter<Any, Call<Any>>)
|
||||
}
|
||||
}
|
||||
|
||||
open class OneArgParameterizedType(
|
||||
private val myRawType: Type,
|
||||
private val myTypeArgs: Array<Type>
|
||||
) : ParameterizedType {
|
||||
override fun getRawType() = myRawType
|
||||
|
||||
override fun getOwnerType() = null
|
||||
|
||||
override fun getActualTypeArguments() = myTypeArgs
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.util
|
||||
|
||||
|
||||
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.lang.reflect.Type
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* @param <R>
|
||||
</R> */
|
||||
class LiveDataCallAdapter<R>(private val responseType: Type) :
|
||||
CallAdapter<R, LiveData<ApiResponse<R>>> {
|
||||
|
||||
override fun responseType() = responseType
|
||||
|
||||
override fun adapt(call: Call<R>): LiveData<ApiResponse<R>> {
|
||||
return object : LiveData<ApiResponse<R>>() {
|
||||
private var started = AtomicBoolean(false)
|
||||
override fun onActive() {
|
||||
super.onActive()
|
||||
if (started.compareAndSet(false, true)) {
|
||||
call.enqueue(object : Callback<R> {
|
||||
override fun onResponse(call: Call<R>, response: Response<R>) {
|
||||
postValue(ApiResponse.create(response))
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<R>, throwable: Throwable) {
|
||||
postValue(ApiResponse.create(throwable))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.util
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import retrofit2.CallAdapter
|
||||
import retrofit2.CallAdapter.Factory
|
||||
import retrofit2.Retrofit
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class LiveDataCallAdapterFactory : Factory() {
|
||||
override fun get(
|
||||
returnType: Type,
|
||||
annotations: Array<Annotation>,
|
||||
retrofit: Retrofit
|
||||
): CallAdapter<*, *>? {
|
||||
if (getRawType(returnType) != LiveData::class.java) {
|
||||
return null
|
||||
}
|
||||
val observableType = getParameterUpperBound(0, returnType as ParameterizedType)
|
||||
val rawObservableType = getRawType(observableType)
|
||||
if (rawObservableType != ApiResponse::class.java) {
|
||||
throw IllegalArgumentException("type must be a resource")
|
||||
}
|
||||
if (observableType !is ParameterizedType) {
|
||||
throw IllegalArgumentException("resource must be parameterized")
|
||||
}
|
||||
val bodyType = getParameterUpperBound(0, observableType)
|
||||
return LiveDataCallAdapter<Any>(bodyType)
|
||||
}
|
||||
}
|
||||
29
app/src/main/java/com/legacy/android/util/Resource.kt
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.util
|
||||
|
||||
/**
|
||||
* @param <T>
|
||||
</T> */
|
||||
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
|
||||
companion object {
|
||||
fun <T> success(data: T?): Resource<T> {
|
||||
return Resource(Status.SUCCESS, data, null)
|
||||
}
|
||||
|
||||
fun <T> error(msg: String, data: T?): Resource<T> {
|
||||
return Resource(Status.ERROR, data, msg)
|
||||
}
|
||||
|
||||
fun <T> loading(data: T?): Resource<T> {
|
||||
return Resource(Status.LOADING, data, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Status {
|
||||
SUCCESS,
|
||||
ERROR,
|
||||
LOADING
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright © 2020-2023 Proxidize. All Rights Reserved.
|
||||
*/
|
||||
package com.legacy.android.util
|
||||
|
||||
|
||||
/**
|
||||
* @param <ResultType>
|
||||
* @param <RequestType>
|
||||
</RequestType></ResultType> */
|
||||
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.lifecycle.*
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.CancellationException
|
||||
|
||||
|
||||
fun <ResultType, RequestType> networkBoundResource(
|
||||
saveCallResult: suspend (RequestType) -> Unit,
|
||||
shouldFetch: (ResultType) -> Boolean = { true },
|
||||
loadFromDb: () -> LiveData<ResultType>,
|
||||
fetch: suspend () -> ApiResponse<RequestType>,
|
||||
processResponse: (suspend (ApiSuccessResponse<RequestType>) -> RequestType) = { it.body },
|
||||
onFetchFailed: ((ApiErrorResponse<RequestType>) -> Unit)? = null
|
||||
): LiveData<Resource<ResultType>> {
|
||||
return CoroutineNetworkBoundResource(
|
||||
saveCallResult = saveCallResult,
|
||||
shouldFetch = shouldFetch,
|
||||
loadFromDb = loadFromDb,
|
||||
fetch = fetch,
|
||||
processResponse = processResponse,
|
||||
onFetchFailed = onFetchFailed
|
||||
).asLiveData().distinctUntilChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* A [NetworkBoundResource] implementation in corotuines
|
||||
*/
|
||||
private class CoroutineNetworkBoundResource<ResultType, RequestType>
|
||||
@MainThread constructor(
|
||||
private val saveCallResult: suspend (RequestType) -> Unit,
|
||||
private val shouldFetch: (ResultType) -> Boolean = { true },
|
||||
private val loadFromDb: () -> LiveData<ResultType>,
|
||||
private val fetch: suspend () -> ApiResponse<RequestType>,
|
||||
private val processResponse: (suspend (ApiSuccessResponse<RequestType>) -> RequestType),
|
||||
private val onFetchFailed: ((ApiErrorResponse<RequestType>) -> Unit)?
|
||||
) {
|
||||
@ExperimentalCoroutinesApi
|
||||
private val result = liveData<Resource<ResultType>> {
|
||||
if (latestValue?.status != Status.SUCCESS) {
|
||||
emit(Resource.loading(latestValue?.data))
|
||||
}
|
||||
val dbSource = loadFromDb()
|
||||
val initialValue = dbSource.await()
|
||||
val willFetch = initialValue == null || shouldFetch(initialValue)
|
||||
if (!willFetch) {
|
||||
emitSource(dbSource.map {
|
||||
Resource.success(it)
|
||||
})
|
||||
} else {
|
||||
doFetch(dbSource, this)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doFetch(
|
||||
dbSource: LiveData<ResultType>,
|
||||
liveDataScope: LiveDataScope<Resource<ResultType>>
|
||||
) {
|
||||
val initialSource = liveDataScope.emitSource(dbSource.map {
|
||||
Resource.loading(it)
|
||||
})
|
||||
when (val response = fetchCatching()) {
|
||||
is ApiSuccessResponse, is ApiEmptyResponse -> {
|
||||
if (response is ApiSuccessResponse) {
|
||||
val processed = processResponse(response)
|
||||
initialSource.dispose()
|
||||
saveCallResult(processed)
|
||||
}
|
||||
liveDataScope.emitSource(loadFromDb().map {
|
||||
Resource.success(it)
|
||||
})
|
||||
}
|
||||
is ApiErrorResponse -> {
|
||||
onFetchFailed?.invoke(response)
|
||||
liveDataScope.emitSource(dbSource.map {
|
||||
Resource.error(response.errorMessage, it)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun asLiveData() = result
|
||||
|
||||
private suspend fun fetchCatching(): ApiResponse<RequestType> {
|
||||
return try {
|
||||
fetch()
|
||||
} catch (ex: CancellationException) {
|
||||
throw ex
|
||||
} catch (ex: Throwable) {
|
||||
ApiResponse.create(ex)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> LiveData<T>.await() = withContext(Dispatchers.Main) {
|
||||
val receivedValue = CompletableDeferred<T?>()
|
||||
val observer = Observer<T> {
|
||||
if (receivedValue.isActive) {
|
||||
receivedValue.complete(it)
|
||||
}
|
||||
}
|
||||
try {
|
||||
observeForever(observer)
|
||||
return@withContext receivedValue.await()
|
||||
} finally {
|
||||
removeObserver(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
16
app/src/main/res/anim/bottom_anim.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate
|
||||
android:fromXDelta="0%"
|
||||
android:fromYDelta="100%"
|
||||
android:duration = "1500"
|
||||
/>
|
||||
|
||||
<alpha
|
||||
android:fromAlpha="0.1"
|
||||
android:toAlpha="1.0"
|
||||
android:duration = "1500"
|
||||
/>
|
||||
|
||||
</set>
|
||||
16
app/src/main/res/anim/tob_anim.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate
|
||||
android:fromXDelta="0%"
|
||||
android:fromYDelta="-50%"
|
||||
android:duration = "1500"
|
||||
/>
|
||||
|
||||
<alpha
|
||||
android:fromAlpha="0.1"
|
||||
android:toAlpha="1.0"
|
||||
android:duration = "1500"
|
||||
/>
|
||||
|
||||
</set>
|
||||
BIN
app/src/main/res/drawable/bill.png
Normal file
|
After Width: | Height: | Size: 772 B |
27
app/src/main/res/drawable/button_rectangle_pink.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#F3E40D"/>
|
||||
<corners
|
||||
android:topRightRadius="20dp"
|
||||
android:topLeftRadius="20dp"
|
||||
android:bottomRightRadius="20dp"
|
||||
android:bottomLeftRadius="20dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#Fff"/>
|
||||
<corners
|
||||
android:topRightRadius="20dp"
|
||||
android:topLeftRadius="20dp"
|
||||
android:bottomRightRadius="20dp"
|
||||
android:bottomLeftRadius="20dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</selector>
|
||||
BIN
app/src/main/res/drawable/ic_action_name.png
Normal file
|
After Width: | Height: | Size: 283 B |
5
app/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_baseline_home_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<vector android:height="40dp" android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="40dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M14.17,13.71l1.4,-2.42c0.09,-0.15 0.05,-0.34 -0.08,-0.45l-1.48,-1.16c0.03,-0.22 0.05,-0.45 0.05,-0.68s-0.02,-0.46 -0.05,-0.69l1.48,-1.16c0.13,-0.11 0.17,-0.3 0.08,-0.45l-1.4,-2.42c-0.09,-0.15 -0.27,-0.21 -0.43,-0.15L12,4.83c-0.36,-0.28 -0.75,-0.51 -1.18,-0.69l-0.26,-1.85C10.53,2.13 10.38,2 10.21,2h-2.8C7.24,2 7.09,2.13 7.06,2.3L6.8,4.15C6.38,4.33 5.98,4.56 5.62,4.84l-1.74,-0.7c-0.16,-0.06 -0.34,0 -0.43,0.15l-1.4,2.42C1.96,6.86 2,7.05 2.13,7.16l1.48,1.16C3.58,8.54 3.56,8.77 3.56,9s0.02,0.46 0.05,0.69l-1.48,1.16C2,10.96 1.96,11.15 2.05,11.3l1.4,2.42c0.09,0.15 0.27,0.21 0.43,0.15l1.74,-0.7c0.36,0.28 0.75,0.51 1.18,0.69l0.26,1.85C7.09,15.87 7.24,16 7.41,16h2.8c0.17,0 0.32,-0.13 0.35,-0.3l0.26,-1.85c0.42,-0.18 0.82,-0.41 1.18,-0.69l1.74,0.7C13.9,13.92 14.08,13.86 14.17,13.71zM8.81,11c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C10.81,10.1 9.91,11 8.81,11z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M21.92,18.67l-0.96,-0.74c0.02,-0.14 0.04,-0.29 0.04,-0.44c0,-0.15 -0.01,-0.3 -0.04,-0.44l0.95,-0.74c0.08,-0.07 0.11,-0.19 0.05,-0.29l-0.9,-1.55c-0.05,-0.1 -0.17,-0.13 -0.28,-0.1l-1.11,0.45c-0.23,-0.18 -0.48,-0.33 -0.76,-0.44l-0.17,-1.18C18.73,13.08 18.63,13 18.53,13h-1.79c-0.11,0 -0.21,0.08 -0.22,0.19l-0.17,1.18c-0.27,0.12 -0.53,0.26 -0.76,0.44l-1.11,-0.45c-0.1,-0.04 -0.22,0 -0.28,0.1l-0.9,1.55c-0.05,0.1 -0.04,0.22 0.05,0.29l0.95,0.74c-0.02,0.14 -0.03,0.29 -0.03,0.44c0,0.15 0.01,0.3 0.03,0.44l-0.95,0.74c-0.08,0.07 -0.11,0.19 -0.05,0.29l0.9,1.55c0.05,0.1 0.17,0.13 0.28,0.1l1.11,-0.45c0.23,0.18 0.48,0.33 0.76,0.44l0.17,1.18c0.02,0.11 0.11,0.19 0.22,0.19h1.79c0.11,0 0.21,-0.08 0.22,-0.19l0.17,-1.18c0.27,-0.12 0.53,-0.26 0.75,-0.44l1.12,0.45c0.1,0.04 0.22,0 0.28,-0.1l0.9,-1.55C22.03,18.86 22,18.74 21.92,18.67zM17.63,18.83c-0.74,0 -1.35,-0.6 -1.35,-1.35s0.6,-1.35 1.35,-1.35s1.35,0.6 1.35,1.35S18.37,18.83 17.63,18.83z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_baseline_token_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19.97,6.43L12,2L4.03,6.43L9.1,9.24C9.83,8.48 10.86,8 12,8s2.17,0.48 2.9,1.24L19.97,6.43zM10,12c0,-1.1 0.9,-2 2,-2s2,0.9 2,2s-0.9,2 -2,2S10,13.1 10,12zM11,21.44L3,17V8.14l5.13,2.85C8.04,11.31 8,11.65 8,12c0,1.86 1.27,3.43 3,3.87V21.44zM13,21.44v-5.57c1.73,-0.44 3,-2.01 3,-3.87c0,-0.35 -0.04,-0.69 -0.13,-1.01L21,8.14L21,17L13,21.44z"/>
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_menu_camera.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_menu_gallery.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_menu_slideshow.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z" />
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/icon_password.png
Normal file
|
After Width: | Height: | Size: 331 B |
BIN
app/src/main/res/drawable/logo.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
app/src/main/res/drawable/port.png
Normal file
|
After Width: | Height: | Size: 566 B |
BIN
app/src/main/res/drawable/server.png
Normal file
|
After Width: | Height: | Size: 243 B |
9
app/src/main/res/drawable/side_nav_bar.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="135"
|
||||
android:centerColor="#FFE500"
|
||||
android:endColor="#FFE500"
|
||||
android:startColor="#FFE500"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
147
app/src/main/res/layout/activity_custom_server.xml
Normal file
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#F8F8D1"
|
||||
tools:context=".CustomServerActivity">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/server"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
>
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/textField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/host"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:hint="@string/host"
|
||||
|
||||
app:startIconDrawable="@drawable/server"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/server">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/et_host"
|
||||
android:inputType="phone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/bind_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:hint="@string/binding_port"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:startIconDrawable="@drawable/port"
|
||||
app:layout_constraintTop_toBottomOf="@+id/host">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/et_port"
|
||||
android:layout_width="match_parent"
|
||||
android:inputType="number"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/token"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:hint="@string/token"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:startIconDrawable="@drawable/ic_baseline_token_24"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bind_port">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/et_token"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/saveButton"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/save"
|
||||
android:textColor="#241708"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/token" />
|
||||
|
||||
<!-- <com.google.android.material.button.MaterialButton-->
|
||||
<!-- android:id="@+id/resetButton"-->
|
||||
<!-- style="@style/Widget.MaterialComponents.Button.UnelevatedButton"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:layout_gravity="center"-->
|
||||
<!-- android:layout_marginStart="16dp"-->
|
||||
<!-- android:layout_marginTop="5dp"-->
|
||||
<!-- android:layout_marginEnd="16dp"-->
|
||||
<!-- android:elevation="8dp"-->
|
||||
<!-- android:padding="16dp"-->
|
||||
<!-- android:text="@string/reset"-->
|
||||
<!-- android:textColor="#241708"-->
|
||||
<!-- android:textStyle="bold"-->
|
||||
<!-- app:backgroundTint="@color/white"-->
|
||||
<!-- app:cornerRadius="16dp"-->
|
||||
<!-- app:elevation="8dp"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toBottomOf="@+id/saveButton" />-->
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="host,bind_port, token, saveButton"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
46
app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#F3E40D">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/logo"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="300dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="75dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/logo"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/slogan1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/_4g_proxy_solutions"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#000"
|
||||
android:textSize="18dp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<!--<TextView-->
|
||||
<!--android:layout_width="match_parent"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:layout_gravity="bottom|center"-->
|
||||
<!--android:gravity="center"-->
|
||||
<!--android:layout_marginBottom="5dp"-->
|
||||
<!--android:textColor="@android:color/holo_red_dark"-->
|
||||
<!--android:text="©2016~2018 fun"/>-->
|
||||
</RelativeLayout>
|
||||
25
app/src/main/res/layout/activity_nav_drawer.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:openDrawer="start">
|
||||
|
||||
<include
|
||||
android:id="@+id/app_bar_nav_drawer"
|
||||
layout="@layout/app_bar_nav_drawer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/nav_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
app:headerLayout="@layout/nav_header_nav_drawer"
|
||||
app:menu="@menu/activity_main_drawer" />
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
8
app/src/main/res/layout/activity_start_service.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
25
app/src/main/res/layout/app_bar_nav_drawer.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.NavDrawerActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.NoAction.AppBarOverlay">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="#FFE500"
|
||||
app:popupTheme="@style/AppTheme.NoAction.PopupOverlay" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<include layout="@layout/content_nav_drawer" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
20
app/src/main/res/layout/content_nav_drawer.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:showIn="@layout/app_bar_nav_drawer">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment_content_nav_drawer"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navGraph="@navigation/mobile_navigation" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
16
app/src/main/res/layout/dropdown.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/menu"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/textField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
366
app/src/main/res/layout/fragment_home.xml
Normal file
@@ -0,0 +1,366 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout_editor_absoluteX="1dp"
|
||||
tools:layout_editor_absoluteY="1dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:fillViewport="true"
|
||||
>
|
||||
|
||||
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
||||
android:id="@+id/linear"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#F8F8D1">
|
||||
|
||||
<!--<TextView
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/colorPrimaryDark"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/black"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:title="" />-->
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/connect"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp"
|
||||
android:stateListAnimator="@null"
|
||||
android:text="@string/connect"
|
||||
android:textSize="12.5sp"
|
||||
android:textColor="#241708"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/rotate"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/rotate"
|
||||
android:textColor="#241708"
|
||||
android:textSize="12.5sp"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/auto_rotate"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/connect" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/auto_rotate"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/custom_rotate"
|
||||
android:textSize="12.5sp"
|
||||
android:textColor="#241708"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/rotate"
|
||||
app:layout_constraintTop_toBottomOf="@+id/connect" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/connectionLabelTextView"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="31dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="15dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="@string/your_proxy_connection_details"
|
||||
android:textColor="#241708"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="20sp"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/rotate" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_http"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/http_proxy"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/connectionLabelTextView" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/http_proxy_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@color/cardview_light_background"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_http">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/connection_http"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/copy_http"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/copy_http"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/colorPrimaryDark"
|
||||
android:gravity="center"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Button"
|
||||
android:text="@string/copy"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_socks"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/socks_proxy"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/http_proxy_card" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/socks_proxy_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@color/cardview_light_background"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_socks">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/connection_socks"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/copy_socks"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/copy_socks"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/colorPrimaryDark"
|
||||
android:text="@string/copy"
|
||||
android:gravity="center"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_ip_change"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/ip_change_link_api"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/socks_proxy_card" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/ip_proxy_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@color/cardview_light_background"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_ip_change">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/connection_ip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/copy_ip_change"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/copy_ip_change"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/colorPrimaryDark"
|
||||
android:gravity="center"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:text="@string/copy"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/connection_grp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="tv_http,tv_ip_change,tv_socks,http_proxy_card,ip_proxy_card,socks_proxy_card" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/githubLink"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:text="@string/read_docs"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#0202C3"
|
||||
android:textColorLink="#0000FF"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/wbsiteLink"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ip_proxy_card" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wbsiteLink"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:autoLink="web"
|
||||
android:text="@string/learn_about_mobile_proxies_on_proxidize_com"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#0202C3"
|
||||
android:textColorLink="#0000FF"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/stop"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/githubLink" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/stop"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="54dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:elevation="8dp"
|
||||
android:text="@string/exit"
|
||||
android:textColor="#241708"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/wbsiteLink" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
21
app/src/main/res/layout/input.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/dp_16">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/time_interval"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="number" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</FrameLayout>
|
||||
10
app/src/main/res/layout/list_item.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?attr/textAppearanceSubtitle1"
|
||||
/>
|
||||
69
app/src/main/res/layout/log_layout.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_user"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
app:srcCompat="@drawable/logo" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_email"
|
||||
android:drawableLeft="@drawable/ic_action_name"
|
||||
android:textColor="#241708"
|
||||
android:background="#115A3131"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_below="@+id/iv_user"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="40dp"
|
||||
android:ems="10"
|
||||
android:inputType="textEmailAddress" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_password"
|
||||
android:textColor="#241708"
|
||||
android:drawableLeft="@drawable/icon_password"
|
||||
android:background="#11000000"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_alignLeft="@+id/et_email"
|
||||
android:layout_alignStart="@+id/et_email"
|
||||
android:layout_below="@+id/et_email"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:ems="10"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_login"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="45dp"
|
||||
android:background="@drawable/button_rectangle_pink"
|
||||
android:textColor="#241708"
|
||||
android:textSize="20sp"
|
||||
android:layout_below="@+id/et_password"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="40dp"
|
||||
android:text="Login" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_register"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/btn_login"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="20dp"
|
||||
|
||||
android:textSize="15sp"
|
||||
android:textColor="#241708"
|
||||
android:text="Register Me" />
|
||||
|
||||
</RelativeLayout>
|
||||
37
app/src/main/res/layout/nav_header_nav_drawer.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/nav_header_height"
|
||||
android:background="@drawable/side_nav_bar"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/nav_header_desc"
|
||||
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
||||
app:srcCompat="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
||||
android:text="@string/nav_header_title"
|
||||
android:textColor="#000"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#676767"
|
||||
android:text="@string/nav_header_subtitle" />
|
||||
</LinearLayout>
|
||||
18
app/src/main/res/layout/reset_custom_button.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.button.MaterialButton android:id="@+id/menu"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:text="Reset Defaults"
|
||||
android:textColor="@color/black"
|
||||
android:backgroundTint="@color/colorPrimaryDark"
|
||||
android:textSize="15sp"
|
||||
app:cornerRadius="20dp"
|
||||
app:elevation="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
|
||||
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
26
app/src/main/res/menu/activity_main_drawer.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:showIn="navigation_view">
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/nav_home"
|
||||
android:icon="@drawable/ic_baseline_home_24"
|
||||
android:title="@string/menu_home" />
|
||||
<item
|
||||
android:id="@+id/nav_gallery"
|
||||
android:icon="@drawable/server"
|
||||
android:title="@string/menu_gallery" />
|
||||
|
||||
</group>
|
||||
|
||||
|
||||
<item
|
||||
android:id="@+id/resetDefaultsButton"
|
||||
app:actionLayout="@layout/reset_custom_button"
|
||||
app:showAsAction="always|withText"
|
||||
tools:ignore="MenuTitle"
|
||||
/>
|
||||
|
||||
</menu>
|
||||
9
app/src/main/res/menu/nav_drawer.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/action_settings"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
4
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_adaptive_fore"/>
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_fore.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_fore.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |