first commit

This commit is contained in:
fengge 2026-03-15 12:46:02 +08:00
commit 2f5a3edbb5
64 changed files with 2090 additions and 0 deletions

15
StandAPP/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
StandAPP/.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-03-14T21:27:21.683378200Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\36549\.android\avd\Pixel_Tablet_2.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

18
StandAPP/.idea/gradle.xml Normal file
View File

@ -0,0 +1,18 @@
<?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="CHOOSE_PER_TEST" />
<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>

9
StandAPP/.idea/misc.xml Normal file
View File

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="temurin-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

1
StandAPP/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,64 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "com.stand.standapp"
compileSdk = 36
defaultConfig {
applicationId = "com.stand.standapp"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// 1. 开启代码混淆/压缩 (注意是 isMinifyEnabled)
isMinifyEnabled = true
// 2. 开启资源压缩 (注意是 isShrinkResources)
// 必须配合 isMinifyEnabled = true 使用
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
compose = true
}
}
dependencies {
implementation("io.github.justson:agentweb-core:v5.1.1-androidx")
implementation("com.github.Justson:Downloader:v5.0.4-androidx")
implementation("com.google.android.material:material:1.12.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation(libs.androidx.appcompat)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
}

31
StandAPP/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,31 @@
# AgentWeb 混淆规则
-keep class com.just.agentweb.** { *; }
-dontwarn com.just.agentweb.**
# 忽略不存在的可选依赖报错
-dontwarn com.alipay.sdk.**
-dontwarn com.download.library.**
-dontwarn com.google.android.material.snackbar.**
# 保护 JavaScript 交互接口 (非常重要!)
-keepattributes *Annotation*
-keepattributes *JavascriptInterface*
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
# 保护你自己的 JS 交互类
-keep class com.stand.standapp.AndroidInterface { *; }
# OkHttp 混淆规则
-keepattributes Signature
-keepattributes InnerClasses
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontwarn org.conscrypt.**
# A resource is loaded at runtime for config index:
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# 保护实体类 (如果你后续有 JSON 解析)
-keep class com.stand.standapp.** { *; }

View File

@ -0,0 +1,24 @@
package com.stand.standapp
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.stand.standapp", appContext.packageName)
}
}

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application
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:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/Theme.StandAPP">
<activity
android:name=".LoginActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.StandAPP">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="false"
android:label="@string/app_name"
android:theme="@style/Theme.StandAPP" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

View File

@ -0,0 +1,195 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能调度终端 - 打印演示</title>
<style>
body { font-family: -apple-system, sans-serif; background: #f4f7f9; margin: 0; padding: 20px; color: #333; }
.header { text-align: center; padding: 20px 0; background: #004A8F; color: white; border-radius: 8px 8px 0 0; margin: -20px -20px 20px -20px; }
.card { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); max-width: 500px; margin: 0 auto; }
.info-box { background: #eef2f6; padding: 15px; border-radius: 8px; margin-bottom: 20px; font-size: 14px; }
.btn {
display: block; width: 100%; padding: 15px; margin: 12px 0;
background: #3B82F6; color: white; border: none; border-radius: 8px;
font-size: 16px; font-weight: bold; cursor: pointer; transition: background 0.3s;
}
.btn:active { background: #2563EB; }
.btn-green { background: #10B981; }
.btn-orange { background: #F59E0B; }
.status-tag { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; margin-bottom: 10px; }
.status-online { background: #D1FAE5; color: #065F46; }
.status-offline { background: #FEE2E2; color: #991B1B; }
.bill-preview { border: 1px dashed #aaa; padding: 15px; background: #fff; margin-top: 20px; }
.footer { text-align: center; margin-top: 30px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="card">
<div class="header">
<h3>打印控制台</h3>
</div>
<div id="inject-status" class="status-tag status-offline">未检测到 App 接口</div>
<div class="info-box">
<strong>设备信息:</strong><br>
应用版本: <span id="app-version">Unknown</span><br>
环境检测: <span id="env-type">Browser</span>
</div>
<button class="btn" onclick="callSystemPrint()">1. 调用系统标准打印</button>
<p style="font-size: 12px; color: #666;">(支持 PDF 导出、网络打印机、A4 适配)</p>
<button class="btn btn-green" onclick="callRawPrint()">2. 发送热敏指令打印</button>
<p style="font-size: 12px; color: #666;">(适用于蓝牙/USB 小票机,传输原始 JSON)</p>
<button class="btn btn-orange" onclick="callUpdate()">3. 检查系统更新</button>
<button class="btn btn-red" onclick="callExit()">4. 退出应用</button>
<div class="bill-preview" id="printArea">
<div style="text-align: center; font-weight: bold; font-size: 18px;">测试签收单</div>
<hr>
<p>单号: SN20260315888</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>物料: 工业级调度终端 x 5</p>
<p>时间: <span id="current-time"></span></p>
<div style="height: 40px; border: 1px solid #eee; text-align: center; line-height: 40px; color: #ccc;">
[ 此处为二维码/条码预览 ]
</div>
</div>
</div>
<div class="footer">
Telewave SDK Connectivity v1.0
</div>
<script>
// 初始化界面
document.getElementById('current-time').innerText = new Date().toLocaleString();
function checkApp() {
const tag = document.getElementById('inject-status');
const versionSpan = document.getElementById('app-version');
const envSpan = document.getElementById('env-type');
if (window.android) {
tag.innerText = "已连接至 App 原生接口";
tag.className = "status-tag status-online";
envSpan.innerText = "Android App Container";
if (window.android.getVersionName) {
versionSpan.innerText = window.android.getVersionName();
}
}
}
// 调用系统打印
function callSystemPrint() {
if (window.android && window.android.printPage) {
window.android.printPage();
} else {
alert("当前环境不支持系统打印");
}
}
// 调用原始数据打印
function callRawPrint() {
if (window.android && window.android.printRawData) {
const data = {
type: "receipt",
title: "测试签收单",
sn: "SN20260315888",
items: ["调度终端 x 5"],
timestamp: Date.now()
};
window.android.printRawData(JSON.stringify(data));
} else {
alert("当前环境不支持原始指令打印");
}
}
// 检查更新
function callUpdate() {
if (window.android && window.android.startUpdate) {
window.android.startUpdate("https://example.com/latest.apk");
} else {
alert("更新接口未就绪");
}
}
function callExit() {
if (window.android && window.android.exitApp) {
window.android.exitApp();
} else {
alert("App 退出接口不可用");
}
}
// 延迟检测
setTimeout(checkApp, 300);
</script>
</body>
</html>

View File

@ -0,0 +1,92 @@
package com.stand.standapp
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.webkit.JavascriptInterface
import android.widget.Toast
import android.print.PrintAttributes
import android.print.PrintManager
import com.stand.standapp.R
class AndroidInterface(private val activity: MainActivity) {
/**
* JS 调用标准系统打印 (PDF/网络打印机/AirPrint)
* 适用于打印精美的 A4 单据或页面
*/
@JavascriptInterface
fun printPage() {
Handler(Looper.getMainLooper()).post {
try {
val printManager = activity.getSystemService(Context.PRINT_SERVICE) as PrintManager
// 获取 WebView 的打印适配器
val webView = activity.getAgentWeb()?.webCreator?.getWebView()
if (webView == null) {
Toast.makeText(activity, "WebView 未就绪", Toast.LENGTH_SHORT).show()
return@post
}
val printAdapter = webView.createPrintDocumentAdapter("Document")
val jobName = activity.getString(R.string.app_name) + " Print Job"
printManager.print(jobName, printAdapter, PrintAttributes.Builder().build())
} catch (e: Exception) {
Log.e("Print", "系统打印失败", e)
Toast.makeText(activity, "打印服务启动失败", Toast.LENGTH_SHORT).show()
}
}
}
/**
* JS 调用原始数据打印
* 适用于热敏小票打印机 (蓝牙USB网络)
* @param data 可以是 JSON 字符串包含打印的指令或文本
*/
@JavascriptInterface
fun printRawData(data: String) {
Log.d("Print", "收到 H5 原始打印数据: $data")
Handler(Looper.getMainLooper()).post {
// 这里可以弹出一个选择框让用户选择打印机,或者根据配置自动连接
// 常见的打印流程1. 解析数据 -> 2. 转换成 ESC/POS 指令 -> 3. 发送到通道
Toast.makeText(activity, "H5指令打印: $data", Toast.LENGTH_LONG).show()
// 预留不同通道的入口
// connectAndPrint(data)
}
}
/**
* JS 调用在线升级 (手动触发检查)
*/
@JavascriptInterface
fun startUpdate(apkUrl: String) {
Handler(Looper.getMainLooper()).post {
UpdateManager(activity).checkUpdate()
}
}
/**
* JS 调用退出应用
*/
@JavascriptInterface
fun exitApp() {
Handler(Looper.getMainLooper()).post {
activity.showExitDialog()
}
}
/**
* 获取版本号给 H5 判断是否需要升级
*/
@JavascriptInterface
fun getVersionName(): String {
return try {
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
packageInfo.versionName ?: "1.0.0"
} catch (e: Exception) {
"1.0.0"
}
}
}

View File

@ -0,0 +1,51 @@
package com.stand.standapp
import android.content.Context
import android.content.SharedPreferences
object AppConfig {
private const val PREF_NAME = "app_settings"
private const val KEY_SERVER_URL = "server_url"
private const val KEY_REMEMBER_LOGIN = "remember_login"
private const val KEY_USERNAME = "saved_username"
private const val KEY_PASSWORD = "saved_password"
// 默认服务器地址
const val DEFAULT_SERVER_URL = "http://152.32.186.199:38080"
private fun getPrefs(context: Context): SharedPreferences {
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
}
fun getServerUrl(context: Context): String {
return getPrefs(context).getString(KEY_SERVER_URL, DEFAULT_SERVER_URL) ?: DEFAULT_SERVER_URL
}
fun setServerUrl(context: Context, url: String) {
getPrefs(context).edit().putString(KEY_SERVER_URL, url).apply()
}
fun isRememberLogin(context: Context): Boolean {
return getPrefs(context).getBoolean(KEY_REMEMBER_LOGIN, false)
}
fun setRememberLogin(context: Context, remember: Boolean) {
getPrefs(context).edit().putBoolean(KEY_REMEMBER_LOGIN, remember).apply()
}
fun getSavedUsername(context: Context): String {
return getPrefs(context).getString(KEY_USERNAME, "") ?: ""
}
fun setSavedUsername(context: Context, username: String) {
getPrefs(context).edit().putString(KEY_USERNAME, username).apply()
}
fun getSavedPassword(context: Context): String {
return getPrefs(context).getString(KEY_PASSWORD, "") ?: ""
}
fun setSavedPassword(context: Context, password: String) {
getPrefs(context).edit().putString(KEY_PASSWORD, password).apply()
}
}

View File

@ -0,0 +1,103 @@
package com.stand.standapp
import android.content.Intent
import android.os.Bundle
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
val etUsername = findViewById<EditText>(R.id.et_username)
val etPassword = findViewById<EditText>(R.id.et_password)
val cbRemember = findViewById<CheckBox>(R.id.cb_remember)
val btnLogin = findViewById<Button>(R.id.btn_login)
val ivSettings = findViewById<ImageView>(R.id.iv_settings)
// 1. 加载保存的状态和凭据
val isRemember = AppConfig.isRememberLogin(this)
cbRemember.isChecked = isRemember
if (isRemember) {
etUsername.setText(AppConfig.getSavedUsername(this))
etPassword.setText(AppConfig.getSavedPassword(this))
}
// 2. 服务器配置图标点击事件
ivSettings.setOnClickListener {
showServerConfigDialog()
}
// 3. 登录按钮点击事件
btnLogin.setOnClickListener {
val username = etUsername.text.toString().trim()
val password = etPassword.text.toString().trim()
if (username.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "请输入账号和密码", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
// 模拟登录校验
if (username == "admin" && password == "admin") {
// 保存“记住登录”状态及凭据
AppConfig.setRememberLogin(this, cbRemember.isChecked)
if (cbRemember.isChecked) {
AppConfig.setSavedUsername(this, username)
AppConfig.setSavedPassword(this, password)
} else {
AppConfig.setSavedUsername(this, "")
AppConfig.setSavedPassword(this, "")
}
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show()
// 跳转到主界面
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
} else {
Toast.makeText(this, "账号或密码错误", Toast.LENGTH_SHORT).show()
}
}
}
/**
* 弹出服务器地址配置对话框
*/
private fun showServerConfigDialog() {
val container = LinearLayout(this)
container.orientation = LinearLayout.VERTICAL
container.setPadding(60, 20, 60, 0)
val editText = EditText(this)
editText.setText(AppConfig.getServerUrl(this))
editText.hint = "例如: http://192.168.1.100:8080"
container.addView(editText)
AlertDialog.Builder(this)
.setTitle("服务器地址配置")
.setView(container)
.setPositiveButton("保存") { _, _ ->
val newUrl = editText.text.toString().trim()
if (newUrl.isNotEmpty()) {
AppConfig.setServerUrl(this, newUrl)
Toast.makeText(this, "服务器配置已更新", Toast.LENGTH_SHORT).show()
}
}
.setNegativeButton("取消", null)
.show()
}
override fun onBackPressed() {
AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("确定要退出应用吗?")
.setPositiveButton("确定") { _, _ -> super.onBackPressed() }
.setNegativeButton("取消", null)
.show()
}
}

View File

@ -0,0 +1,84 @@
package com.stand.standapp
import android.os.Bundle
import android.view.KeyEvent
import android.view.ViewGroup
import android.widget.LinearLayout
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.just.agentweb.AgentWeb
import com.stand.standapp.R
class MainActivity : AppCompatActivity() {
private var mAgentWeb: AgentWeb? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
setContentView(R.layout.activity_main)
val container = findViewById<LinearLayout>(R.id.container)
if (container == null) return
// 从配置中获取服务器地址
val serverUrl = AppConfig.getServerUrl(this)
mAgentWeb = AgentWeb.with(this)
.setAgentWebParent(container, LinearLayout.LayoutParams(-1, -1))
.useDefaultIndicator()
.addJavascriptInterface("android", AndroidInterface(this))
.createAgentWeb()
.ready()
.go("http://152.32.186.199/local/print_demo.html")
// 启动时检查更新
UpdateManager(this).checkUpdate()
} catch (e: Exception) {
Log.e("MainActivity", "Fatal Crash: ${e.message}")
e.printStackTrace()
}
}
fun getAgentWeb(): AgentWeb? {
return mAgentWeb
}
fun showExitDialog() {
androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("确定要退出应用吗?")
.setPositiveButton("确定") { _, _ -> finish() }
.setNegativeButton("取消", null)
.show()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (mAgentWeb?.handleKeyEvent(keyCode, event) == true) {
return true
}
if (keyCode == KeyEvent.KEYCODE_BACK) {
showExitDialog()
return true
}
return super.onKeyDown(keyCode, event)
}
override fun onPause() {
mAgentWeb?.webLifeCycle?.onPause()
super.onPause()
}
override fun onResume() {
mAgentWeb?.webLifeCycle?.onResume()
super.onResume()
}
override fun onDestroy() {
mAgentWeb?.webLifeCycle?.onDestroy()
super.onDestroy()
}
}

View File

@ -0,0 +1,245 @@
package com.stand.standapp
import android.app.ProgressDialog
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import okhttp3.*
import org.json.JSONObject
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
class UpdateManager(private val context: Context) {
private var progressDialog: ProgressDialog? = null
private val handler = Handler(Looper.getMainLooper())
private var call: Call? = null
private val fileName = "StandApp_Update.apk"
/**
* 获取或创建公共下载目录下的文件 URI
*/
private fun getPublicDownloadUri(): Uri? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, "application/vnd.android.package-archive")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}
val contentResolver = context.contentResolver
return contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
} else {
val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName)
return Uri.fromFile(file)
}
}
fun checkUpdate() {
val serverUrl = AppConfig.getServerUrl(context)
val currentVersionCode = try {
context.packageManager.getPackageInfo(context.packageName, 0).let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) it.longVersionCode.toInt() else it.versionCode
}
} catch (e: Exception) { 0 }
val url = "$serverUrl/app-version/check-update?versionCode=$currentVersionCode"
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e("UpdateManager", "检查更新失败: ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string() ?: return
try {
val json = JSONObject(body)
if (json.getInt("code") == 0) {
val data = json.optJSONObject("data")
if (data != null) {
val newVersionCode = data.getInt("versionCode")
if (newVersionCode > currentVersionCode) {
val versionName = data.getString("versionName")
val content = data.getString("updateContent")
val id = data.getLong("id")
(context as? AppCompatActivity)?.runOnUiThread {
showUpdateDialog(versionName, content, id)
}
}
}
}
} catch (e: Exception) {
Log.e("UpdateManager", "解析结果失败", e)
}
}
})
}
private fun showUpdateDialog(versionName: String, content: String, id: Long) {
val view = (context as AppCompatActivity).layoutInflater.inflate(R.layout.dialog_update, null)
val tvTitle = view.findViewById<android.widget.TextView>(R.id.tv_update_title)
val tvContent = view.findViewById<android.widget.TextView>(R.id.tv_update_content)
tvTitle.text = "新版本 V$versionName"
tvContent.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
android.text.Html.fromHtml(content, android.text.Html.FROM_HTML_MODE_LEGACY)
} else {
@Suppress("DEPRECATION")
android.text.Html.fromHtml(content)
}
AlertDialog.Builder(context)
.setView(view)
.setCancelable(false)
.setPositiveButton("立即升级") { _, _ ->
val serverUrl = AppConfig.getServerUrl(context)
val downloadUrl = "$serverUrl/app-version/download/$id"
startManualDownload(downloadUrl)
}
.setNegativeButton("稍后再说", null)
.show()
}
private fun startManualDownload(url: String) {
showProgressDialog()
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.addHeader("User-Agent", "Mozilla/5.0 (Android)")
.build()
call = client.newCall(request)
call?.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
handler.post {
progressDialog?.dismiss()
Toast.makeText(context, "连接服务器失败: ${e.message}", Toast.LENGTH_LONG).show()
}
}
override fun onResponse(call: Call, response: Response) {
if (!response.isSuccessful) {
handler.post {
progressDialog?.dismiss()
Toast.makeText(context, "下载失败: 错误码 ${response.code}", Toast.LENGTH_LONG).show()
}
return
}
val body = response.body
if (body != null) saveFileToPublic(body)
}
})
}
private fun saveFileToPublic(body: ResponseBody) {
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
var targetUri: Uri? = null
try {
targetUri = getPublicDownloadUri()
if (targetUri == null) throw IOException("无法创建文件记录")
outputStream = context.contentResolver.openOutputStream(targetUri)
inputStream = body.byteStream()
val totalBytes = body.contentLength()
val buffer = ByteArray(8192)
var bytesRead: Int
var totalRead: Long = 0
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream?.write(buffer, 0, bytesRead)
totalRead += bytesRead
if (totalBytes > 0) {
val progress = (totalRead * 100 / totalBytes).toInt()
handler.post {
progressDialog?.progress = progress
progressDialog?.setMessage("已下载: $progress%")
}
}
}
outputStream?.flush()
handler.post {
progressDialog?.dismiss()
Toast.makeText(context, "下载完成,文件已保存到系统下载目录", Toast.LENGTH_SHORT).show()
installApk(targetUri)
}
} catch (e: Exception) {
handler.post {
progressDialog?.dismiss()
Toast.makeText(context, "保存失败: ${e.message}", Toast.LENGTH_LONG).show()
}
} finally {
inputStream?.close()
outputStream?.close()
}
}
private fun showProgressDialog() {
progressDialog = ProgressDialog(context).apply {
setTitle("正在下载")
setMessage("正在连接服务器...")
setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
max = 100
setCancelable(false)
setButton(ProgressDialog.BUTTON_NEGATIVE, "取消下载") { _, _ ->
call?.cancel()
}
show()
}
}
private fun installApk(uri: Uri) {
try {
// Android 8.0+ 检查安装权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!context.packageManager.canRequestPackageInstalls()) {
val intent = Intent(android.provider.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
intent.data = Uri.parse("package:" + context.packageName)
context.startActivity(intent)
Toast.makeText(context, "请先授予安装权限", Toast.LENGTH_SHORT).show()
return
}
}
val intent = Intent(Intent.ACTION_VIEW)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
// 如果是 content:// URI 直接使用,否则通过 FileProvider 转换
val installUri = if (uri.scheme == "content") {
uri
} else {
val file = File(uri.path!!)
FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
}
intent.setDataAndType(installUri, "application/vnd.android.package-archive")
context.startActivity(intent)
} catch (e: Exception) {
Log.e("UpdateManager", "安装失败", e)
Toast.makeText(context, "无法自动调起安装,请在系统下载目录中手动安装", Toast.LENGTH_LONG).show()
}
}
}

View File

@ -0,0 +1,11 @@
package com.stand.standapp.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -0,0 +1,58 @@
package com.stand.standapp.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun StandAPPTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package com.stand.standapp.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="45"
android:endColor="#004A8F"
android:startColor="#001F3F"
android:type="linear" />
</shape>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#3B82F6" />
<corners android:radius="8dp" />
</shape>

View File

@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<corners android:radius="8dp" />
</shape>

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#22FFFFFF" />
<corners android:radius="12dp" />
<stroke android:width="1dp" android:color="#44FFFFFF" />
</shape>

View File

@ -0,0 +1,127 @@
<?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:background="@drawable/bg_login"
android:padding="20dp">
<!-- 服务器配置入口 (右上角) -->
<ImageView
android:id="@+id/iv_settings"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_marginTop="10dp"
android:padding="8dp"
android:src="@android:drawable/ic_menu_manage"
android:contentDescription="配置服务器" />
<!-- 顶部标题 -->
<LinearLayout
android:id="@+id/ll_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="50dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="智能调度终端"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
</LinearLayout>
<!-- 中间登录框 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@drawable/shape_login_box"
android:elevation="8dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="30dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="25dp"
android:text="用户登录"
android:textColor="#FFFFFF"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:text="测试账号: admin 密码: admin"
android:textColor="#CCFFFFFF"
android:textSize="14sp" />
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginBottom="15dp"
android:background="@drawable/shape_edit_text"
android:hint="请输入用户名"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:singleLine="true"
android:textColor="#333333"
android:textColorHint="#999999" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginBottom="10dp"
android:background="@drawable/shape_edit_text"
android:hint="请输入密码"
android:inputType="textPassword"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:singleLine="true"
android:textColor="#333333"
android:textColorHint="#999999" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="25dp">
<CheckBox
android:id="@+id/cb_remember"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="记住登录"
android:textColor="#FFFFFF" />
</RelativeLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@drawable/shape_button_login"
android:text="登录"
android:textColor="#FFFFFF"
android:textSize="18sp" />
</LinearLayout>
<!-- 底部版权 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:text="Copyright © 2020-2026 Telewave. All Rights Reserved."
android:textColor="#88FFFFFF"
android:textSize="12sp" />
</RelativeLayout>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#3B82F6" />
<TextView
android:id="@+id/tv_update_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-50dp"
android:gravity="center"
android:padding="10dp"
android:text="发现新版本"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="15dp"
android:background="#F9FAFB"
android:padding="10dp">
<TextView
android:id="@+id/tv_update_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingExtra="4dp"
android:text="更新内容加载中..."
android:textColor="#4B5563"
android:textSize="14sp" />
</ScrollView>
</LinearLayout>

View File

@ -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="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -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="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">StandAPP</string>
</resources>

View File

@ -0,0 +1,9 @@
<resources>
<!-- Base application theme. -->
<style name="Theme.StandAPP" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">#6200EE</item>
<item name="colorPrimaryDark">#3700B3</item>
<item name="colorAccent">#03DAC5</item>
</style>
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older than API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="." />
<external-cache-path name="external_cache" path="." />
<external-files-path name="external_files_dir" path="." />
<files-path name="files" path="." />
<cache-path name="cache" path="." />
</paths>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">152.32.186.199</domain>
</domain-config>
</network-security-config>

View File

@ -0,0 +1,17 @@
package com.stand.standapp
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@ -0,0 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.compose) apply false
}

View File

@ -0,0 +1,17 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/9e87f9444e29ce8efb3f66e8435d94b4/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/9e87f9444e29ce8efb3f66e8435d94b4/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1050b2216f8beaaecc1289b17d30b586/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/3ac7a5361c25c0b23d933f44bdb0abd9/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73c462e34475aeb6509ab7ba3eda218f/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/9e87f9444e29ce8efb3f66e8435d94b4/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/dd5b582862cacd4b8e0d82037f92a53f/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/69a793dd932268c7d1ae9d8b855de8ed/redirect
toolchainVersion=17

View File

@ -0,0 +1,33 @@
[versions]
agp = "9.1.0"
coreKtx = "1.18.0"
appcompat = "1.7.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
lifecycleRuntimeKtx = "2.10.0"
activityCompose = "1.13.0"
kotlin = "2.2.10"
composeBom = "2024.09.00"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

Binary file not shown.

View File

@ -0,0 +1,9 @@
#Fri Mar 13 01:17:11 CST 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=b266d5ff6b90eada6dc3b20cb090e3731302e553a27c5d3e4df1f0d76beaff06
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
StandAPP/gradlew vendored Normal file
View File

@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
StandAPP/gradlew.bat vendored Normal file
View File

@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
StandAPP/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

BIN
StandAPP/key/StandAPP Normal file

Binary file not shown.

View File

@ -0,0 +1,27 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
rootProject.name = "StandAPP"
include(":app")

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB