added build script that detects subprojects in src containing a build.gradle and execute gradle
32
src/FastAdmin/app/build.gradle
Normal file
@@ -0,0 +1,32 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.example.FastAdmin' // Replace with your package name
|
||||
compileSdk 34 // Use the latest stable SDK
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.example.FastAdmin"
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
// Optional: for Kotlin usage
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Add necessary dependencies
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
}
|
||||
21
src/FastAdmin/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.FastAdmin">
|
||||
|
||||
<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>
|
||||
</application>
|
||||
</manifest>
|
||||
320
src/FastAdmin/app/src/main/java/MainActivity.kt
Normal file
@@ -0,0 +1,320 @@
|
||||
package com.example.FastAdmin // Corrected package name
|
||||
|
||||
import android.os.Bundle
|
||||
import android.webkit.HttpAuthHandler
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Button
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.graphics.Color
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
// --- COLOR CONSTANTS DERIVED FROM styles.css AND colors.xml ---
|
||||
private val COLOR_BG = 0xFF1A1B26.toInt()
|
||||
private val COLOR_CARD = 0xFF24283B.toInt()
|
||||
private val COLOR_PRIMARY_TEXT = 0xFFC0CAF5.toInt()
|
||||
private val COLOR_ACCENT = 0xFF7AA2F7.toInt()
|
||||
private val COLOR_ERROR = 0xFFF7768E.toInt()
|
||||
private val COLOR_BORDER_DARK = 0xFF3A3C4D.toInt()
|
||||
|
||||
private lateinit var myWebView: WebView
|
||||
private lateinit var sharedPrefs: SharedPreferences
|
||||
|
||||
// Key for SharedPreferences
|
||||
private val PREFS_NAME = "AdminPrefs"
|
||||
private val DOMAIN_KEY = "TargetDomain" // Currently selected domain
|
||||
private val DOMAIN_LIST_KEY = "SavedDomains" // Key for the list of all domains
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
// Initialize SharedPreferences
|
||||
sharedPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
// Check if the domain is already saved
|
||||
val savedDomain = sharedPrefs.getString(DOMAIN_KEY, null)
|
||||
|
||||
if (savedDomain.isNullOrEmpty()) {
|
||||
// Domain not set: show the full manager menu
|
||||
showDomainManagerDialog()
|
||||
} else {
|
||||
// Domain is set: proceed to load WebView
|
||||
setupWebView(savedDomain)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Core WebView Setup Function (NO CHANGE) ---
|
||||
private fun setupWebView(baseUrl: String) {
|
||||
myWebView = findViewById(R.id.webview)
|
||||
|
||||
val settings = myWebView.settings
|
||||
settings.javaScriptEnabled = true
|
||||
settings.domStorageEnabled = true
|
||||
|
||||
myWebView.webViewClient = object : WebViewClient() {
|
||||
override fun onReceivedHttpAuthRequest(
|
||||
view: WebView?,
|
||||
handler: HttpAuthHandler?,
|
||||
host: String?,
|
||||
realm: String?
|
||||
) {
|
||||
if (handler != null) {
|
||||
showHttpAuthDialog(handler, host, realm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val urlToLoad = if (baseUrl.startsWith("http")) baseUrl else "https://$baseUrl"
|
||||
myWebView.loadUrl(urlToLoad)
|
||||
}
|
||||
|
||||
// --- Domain Manager Menu (STYLED) ---
|
||||
private fun showDomainManagerDialog() {
|
||||
val savedDomains = sharedPrefs.getStringSet(DOMAIN_LIST_KEY, HashSet())?.toList() ?: emptyList()
|
||||
val currentDomain = sharedPrefs.getString(DOMAIN_KEY, null)
|
||||
|
||||
val managerLayout = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(dpToPx(20), dpToPx(10), dpToPx(20), dpToPx(10))
|
||||
}
|
||||
|
||||
// 1. Add List of Domains
|
||||
if (savedDomains.isEmpty()) {
|
||||
managerLayout.addView(TextView(this).apply {
|
||||
text = "No domains saved. Tap ADD NEW below."
|
||||
setTextColor(COLOR_PRIMARY_TEXT)
|
||||
setPadding(0, dpToPx(15), 0, dpToPx(15))
|
||||
})
|
||||
} else {
|
||||
for (domain in savedDomains) {
|
||||
val isSelected = domain == currentDomain
|
||||
val domainItem = createDomainListItem(domain, isSelected)
|
||||
managerLayout.addView(domainItem)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 2. Add Buttons Layout
|
||||
val buttonsLayout = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER_HORIZONTAL
|
||||
setPadding(0, dpToPx(20), 0, 0)
|
||||
}
|
||||
|
||||
// Add Button
|
||||
val addButton = createStyledButton("ADD NEW", COLOR_ACCENT, COLOR_BG).apply {
|
||||
setOnClickListener {
|
||||
showAddDomainDialog(savedDomains.toSet())
|
||||
}
|
||||
}
|
||||
buttonsLayout.addView(addButton, LinearLayout.LayoutParams(0, WRAP_CONTENT, 1f).apply { rightMargin = dpToPx(10) })
|
||||
|
||||
// Delete Button
|
||||
val deleteButton = createStyledButton("DELETE", COLOR_ERROR, COLOR_BG).apply {
|
||||
isEnabled = savedDomains.isNotEmpty()
|
||||
setOnClickListener {
|
||||
showDeleteDomainDialog(savedDomains)
|
||||
}
|
||||
}
|
||||
buttonsLayout.addView(deleteButton, LinearLayout.LayoutParams(0, WRAP_CONTENT, 1f))
|
||||
|
||||
managerLayout.addView(buttonsLayout)
|
||||
|
||||
// Show the styled AlertDialog
|
||||
AlertDialog.Builder(this, R.style.Theme_App_DarkDialog) // Use the custom dialog theme
|
||||
.setTitle("Manage Admin Boards")
|
||||
.setMessage("Tap to select a board:")
|
||||
.setView(managerLayout)
|
||||
.setNegativeButton("EXIT") { _, _ ->
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
// --- Helper function to create a styled button ---
|
||||
private fun createStyledButton(text: String, bgColor: Int, textColor: Int): Button {
|
||||
return Button(this).apply {
|
||||
this.text = text
|
||||
setBackgroundColor(bgColor)
|
||||
setTextColor(textColor)
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
|
||||
setPadding(dpToPx(15), dpToPx(10), dpToPx(15), dpToPx(10))
|
||||
elevation = dpToPx(4).toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helper function to create a styled list item ---
|
||||
private fun createDomainListItem(domain: String, isSelected: Boolean): View {
|
||||
val container = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
setPadding(dpToPx(15), dpToPx(15), dpToPx(15), dpToPx(15))
|
||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { bottomMargin = dpToPx(10) }
|
||||
|
||||
val drawable = GradientDrawable()
|
||||
drawable.setColor(if (isSelected) COLOR_ACCENT else COLOR_CARD)
|
||||
drawable.cornerRadius = dpToPx(8).toFloat()
|
||||
background = drawable
|
||||
|
||||
setOnClickListener {
|
||||
sharedPrefs.edit().putString(DOMAIN_KEY, domain).apply()
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
|
||||
val textView = TextView(this).apply {
|
||||
text = domain
|
||||
setTextColor(if (isSelected) COLOR_BG else COLOR_PRIMARY_TEXT)
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
}
|
||||
container.addView(textView, LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 1f))
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
// --- Add Domain Dialog (STYLED) ---
|
||||
private fun showAddDomainDialog(existingDomains: Set<String>) {
|
||||
val domainInput = EditText(this).apply {
|
||||
hint = "e.g., admin.board.com/panel"
|
||||
setTextColor(COLOR_PRIMARY_TEXT)
|
||||
setHintTextColor(Color.GRAY)
|
||||
setPadding(dpToPx(15), dpToPx(15), dpToPx(15), dpToPx(15))
|
||||
|
||||
val borderDrawable = GradientDrawable()
|
||||
borderDrawable.setColor(COLOR_BG)
|
||||
borderDrawable.setStroke(dpToPx(1), COLOR_BORDER_DARK)
|
||||
borderDrawable.cornerRadius = dpToPx(8).toFloat()
|
||||
background = borderDrawable
|
||||
}
|
||||
|
||||
val layout = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(dpToPx(20), dpToPx(20), dpToPx(20), dpToPx(20))
|
||||
addView(domainInput, LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
|
||||
}
|
||||
|
||||
AlertDialog.Builder(this, R.style.Theme_App_DarkDialog)
|
||||
.setTitle("Add New Domain")
|
||||
.setView(layout)
|
||||
.setPositiveButton("Save") { _, _ ->
|
||||
val newDomain = domainInput.text.toString().trim()
|
||||
if (newDomain.isNotEmpty() && !existingDomains.contains(newDomain)) {
|
||||
val updatedSet = existingDomains.toMutableSet()
|
||||
updatedSet.add(newDomain)
|
||||
sharedPrefs.edit().putStringSet(DOMAIN_LIST_KEY, updatedSet).apply()
|
||||
}
|
||||
showDomainManagerDialog()
|
||||
}
|
||||
.setNegativeButton("Cancel") { _, _ ->
|
||||
showDomainManagerDialog()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
// --- Delete Domain Dialog (Styled) ---
|
||||
private fun showDeleteDomainDialog(domainNames: List<String>) {
|
||||
if (domainNames.isEmpty()) {
|
||||
showDomainManagerDialog()
|
||||
return
|
||||
}
|
||||
|
||||
val items = domainNames.toTypedArray()
|
||||
|
||||
AlertDialog.Builder(this, R.style.Theme_App_DarkDialog)
|
||||
.setTitle("Select Domain to DELETE")
|
||||
.setItems(items) { dialog, which ->
|
||||
val domainToDelete = domainNames[which]
|
||||
val currentSet = sharedPrefs.getStringSet(DOMAIN_LIST_KEY, HashSet())?.toMutableSet()
|
||||
|
||||
if (currentSet != null) {
|
||||
currentSet.remove(domainToDelete)
|
||||
sharedPrefs.edit().putStringSet(DOMAIN_LIST_KEY, currentSet).apply()
|
||||
|
||||
if (sharedPrefs.getString(DOMAIN_KEY, null) == domainToDelete) {
|
||||
sharedPrefs.edit().remove(DOMAIN_KEY).apply()
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
showDomainManagerDialog()
|
||||
}
|
||||
.setNegativeButton("Cancel") { _, _ ->
|
||||
showDomainManagerDialog()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
// --- Utility function for converting DP to PX ---
|
||||
private fun dpToPx(dp: Int): Int {
|
||||
return TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dp.toFloat(),
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
}
|
||||
|
||||
// --- HTTP Auth Dialog Function (EXISTING, uses custom theme) ---
|
||||
private fun showHttpAuthDialog(handler: HttpAuthHandler, host: String?, realm: String?) {
|
||||
val usernameInput = EditText(this).apply {
|
||||
hint = "Username"
|
||||
setTextColor(COLOR_PRIMARY_TEXT)
|
||||
setHintTextColor(Color.GRAY)
|
||||
}
|
||||
val passwordInput = EditText(this).apply {
|
||||
hint = "Password"
|
||||
inputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
setTextColor(COLOR_PRIMARY_TEXT)
|
||||
setHintTextColor(Color.GRAY)
|
||||
}
|
||||
|
||||
val layout = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(dpToPx(20), dpToPx(10), dpToPx(20), dpToPx(10))
|
||||
addView(usernameInput, LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
|
||||
addView(passwordInput, LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
|
||||
}
|
||||
|
||||
AlertDialog.Builder(this, R.style.Theme_App_DarkDialog)
|
||||
.setTitle("Authentication Required")
|
||||
.setMessage("Please enter credentials for $realm on $host")
|
||||
.setView(layout)
|
||||
.setPositiveButton("Log In") { _, _ ->
|
||||
val username = usernameInput.text.toString()
|
||||
val password = passwordInput.text.toString()
|
||||
handler.proceed(username, password)
|
||||
}
|
||||
.setNegativeButton("Cancel") { _, _ ->
|
||||
handler.cancel()
|
||||
}
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
|
||||
// --- Back Button Logic (EXISTING) ---
|
||||
override fun onBackPressed() {
|
||||
if (::myWebView.isInitialized && myWebView.canGoBack()) {
|
||||
myWebView.goBack()
|
||||
} else {
|
||||
if (sharedPrefs.getString(DOMAIN_KEY, null).isNullOrEmpty()) {
|
||||
super.onBackPressed()
|
||||
} else {
|
||||
sharedPrefs.edit().remove(DOMAIN_KEY).apply()
|
||||
setContentView(R.layout.activity_main)
|
||||
showDomainManagerDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 156 KiB |
14
src/FastAdmin/app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
src/FastAdmin/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
src/FastAdmin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
src/FastAdmin/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
src/FastAdmin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
src/FastAdmin/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 156 KiB |
BIN
src/FastAdmin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 156 KiB |
BIN
src/FastAdmin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 156 KiB |
15
src/FastAdmin/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="color_bg">#1A1B26</color>
|
||||
<color name="color_card">#24283B</color>
|
||||
<color name="color_primary_text">#C0CAF5</color>
|
||||
<color name="color_accent">#7AA2F7</color>
|
||||
<color name="color_error">#F7768E</color>
|
||||
<color name="color_border_dark">#3A3C4D</color>
|
||||
|
||||
<color name="colorPrimary">@color/color_card</color>
|
||||
<color name="colorPrimaryDark">@color/color_bg</color>
|
||||
<color name="colorAccent">@color/color_accent</color>
|
||||
|
||||
<color name="ic_launcher_background">#1A237E</color>
|
||||
</resources>
|
||||
4
src/FastAdmin/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">FastAdmin</string>
|
||||
</resources>
|
||||
19
src/FastAdmin/app/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="Theme.FastAdmin" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="android:windowBackground">@color/color_bg</item>
|
||||
<item name="android:colorBackground">@color/color_bg</item>
|
||||
|
||||
<item name="alertDialogTheme">@style/Theme.App.DarkDialog</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.App.DarkDialog" parent="Theme.AppCompat.Dialog.Alert">
|
||||
<item name="android:background">@color/color_bg</item>
|
||||
<item name="colorAccent">@color/color_accent</item>
|
||||
<item name="android:textColorPrimary">@color/color_primary_text</item>
|
||||
<item name="android:textColorAlertDialogListItem">@color/color_primary_text</item>
|
||||
<item name="android:windowBackground">@color/color_bg</item>
|
||||
</style>
|
||||
</resources>
|
||||