把应用介绍类从AES简化到APPBase。

This commit is contained in:
2026-01-11 14:19:46 +08:00
parent df4ad5ac61
commit 32c869d043
17 changed files with 787 additions and 925 deletions

View File

@@ -18,22 +18,19 @@ def genVersionName(def versionName){
}
android {
// 关键:改为你已安装的 SDK 32≥ targetSdkVersion 30兼容已安装环境
compileSdkVersion 32
// 直接使用已安装的构建工具 33.0.3(无需修改)
buildToolsVersion "33.0.3"
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "cc.winboll.studio.appbase"
minSdkVersion 23
minSdkVersion 21
targetSdkVersion 30
versionCode 1
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.14"
versionName "15.15"
if(true) {
versionName = genVersionName("${versionName}")
}
@@ -48,12 +45,6 @@ android {
dependencies {
api project(':libappbase')
// 应用介绍页类库
api 'io.github.medyo:android-about-page:2.0.0'
// 网络连接类库
api 'com.squareup.okhttp3:okhttp:4.4.1'
// Html 解析
api 'org.jsoup:jsoup:1.13.1'
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Jan 10 12:31:14 GMT 2026
#Sun Jan 11 06:14:37 GMT 2026
stageCount=3
libraryProject=libappbase
baseVersion=15.14
baseVersion=15.15
publishVersion=15.14.2
buildCount=0
buildCount=14
baseBetaVersion=15.14.3

View File

@@ -38,6 +38,8 @@
<activity android:name="cc.winboll.studio.libappbase.activities.CrashCopyReceiverActivity"/>
<activity android:name=".AboutActivity"/>
</application>
</manifest>

View File

@@ -1,169 +0,0 @@
package cc.winboll.studio.appbase;
import cc.winboll.studio.appbase.R;
import java.io.Serializable;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/10 20:45
* @Describe 应用信息类
*/
public class APPInfo implements Serializable {
public static final String TAG = "APPInfo";
// 应用名称
String appName;
// 应用图标
int appIcon;
// 应用描述
String appDescription;
// 应用Git仓库地址
String appGitName;
// 应用Git仓库拥有者
String appGitOwner;
// 应用Git仓库分支
String appGitAPPBranch;
// 应用Git仓库子项目文件夹
String appGitAPPSubProjectFolder;
// 应用主页
String appHomePage;
// 应用包名称
String appAPKName;
// 应用包存储文件夹名称
String appAPKFolderName;
// 是否添加调试工具
boolean isAddDebugTools;
public APPInfo(String appName, int appIcon, String appDescription, String appGitName, String appGitOwner, String appGitAPPBranch, String appGitAPPSubProjectFolder, String appHomePage, String appAPKName, String appAPKFolderName) {
this.appName = appName;
this.appIcon = appIcon;
this.appDescription = appDescription;
this.appGitName = appGitName;
this.appGitOwner = appGitOwner;
this.appGitAPPBranch = appGitAPPBranch;
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
this.appHomePage = appHomePage;
this.appAPKName = appAPKName;
this.appAPKFolderName = appAPKFolderName;
this.isAddDebugTools = false;
}
public APPInfo(String appName, int appIcon, String appDescription, String appGitName, String appGitOwner, String appGitAPPBranch, String appGitAPPSubProjectFolder, String appHomePage, String appAPKName, String appAPKFolderName, boolean isAddDebugTools) {
this.appName = appName;
this.appIcon = appIcon;
this.appDescription = appDescription;
this.appGitName = appGitName;
this.appGitOwner = appGitOwner;
this.appGitAPPBranch = appGitAPPBranch;
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
this.appHomePage = appHomePage;
this.appAPKName = appAPKName;
this.appAPKFolderName = appAPKFolderName;
this.isAddDebugTools = isAddDebugTools;
}
public APPInfo() {
String szBranchName = "app";
this.appName = "APP";
this.appIcon = R.drawable.ic_winboll;
this.appDescription = "APP Description";
this.appGitName = "APP";
this.appGitOwner = "Studio";
this.appGitAPPBranch = szBranchName;
this.appGitAPPSubProjectFolder = szBranchName;
this.appHomePage = "https://www.winboll.cc/studio/details.php?app=APP";
this.appAPKName = "APP";
this.appAPKFolderName = "APP";
this.isAddDebugTools = false;
}
public void setIsAddDebugTools(boolean isAddDebugTools) {
this.isAddDebugTools = isAddDebugTools;
}
public boolean isAddDebugTools() {
return isAddDebugTools;
}
public void setAppGitOwner(String appGitOwner) {
this.appGitOwner = appGitOwner;
}
public String getAppGitOwner() {
return appGitOwner;
}
public void setAppGitAPPBranch(String appGitAPPBranch) {
this.appGitAPPBranch = appGitAPPBranch;
}
public String getAppGitAPPBranch() {
return appGitAPPBranch;
}
public void setAppGitAPPSubProjectFolder(String appGitAPPSubProjectFolder) {
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
}
public String getAppGitAPPSubProjectFolder() {
return appGitAPPSubProjectFolder;
}
public void setAppIcon(int appIcon) {
this.appIcon = appIcon;
}
public int getAppIcon() {
return appIcon;
}
public void setAppDescription(String appDescription) {
this.appDescription = appDescription;
}
public String getAppDescription() {
return appDescription;
}
public void setAppAPKFolderName(String appAPKFolderName) {
this.appAPKFolderName = appAPKFolderName;
}
public String getAppAPKFolderName() {
return appAPKFolderName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getAppName() {
return appName;
}
public void setAppGitName(String appGitName) {
this.appGitName = appGitName;
}
public String getAppGitName() {
return appGitName;
}
public void setAppHomePage(String appHomePage) {
this.appHomePage = appHomePage;
}
public String getAppHomePage() {
return appHomePage;
}
public void setAppAPKName(String appAPKName) {
this.appAPKName = appAPKName;
}
public String getAppAPKName() {
return appAPKName;
}
}

View File

@@ -1,36 +1,27 @@
package cc.winboll.studio.appbase;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toolbar;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.models.APPInfo;
import cc.winboll.studio.libappbase.views.AboutView;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/10 20:39
* @Date 2026/01/11 12:55
* @Describe AboutActivity
*/
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toolbar;
public class AboutActivity extends Activity {
public static final String TAG = "AboutActivity";
public static final String TAG = "AboutActivity";
public static final String EXTRA_APPINFO = "EXTRA_APPINFO";
Context mContext;
Toolbar mToolbar;
APPInfo mAPPInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
setContentView(R.layout.activity_about);
mAPPInfo = extractAPPInfoFromIntent();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
// 设置工具栏
Toolbar toolbar = findViewById(R.id.toolbar);
@@ -44,59 +35,25 @@ public class AboutActivity extends Activity {
}
});
mAPPInfo = extractAPPInfoFromIntent();
AboutView aboutView = new AboutView(mContext, mAPPInfo);
// 在 Activity 的 onCreate 或其他生命周期方法中调用
// LinearLayout layout = new LinearLayout(this);
// layout.setOrientation(LinearLayout.VERTICAL);
// // 创建布局参数(宽度和高度)
// ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
// ViewGroup.LayoutParams.MATCH_PARENT,
// ViewGroup.LayoutParams.MATCH_PARENT
// );
// addContentView(aboutView, params);
AboutView aboutView = findViewById(R.id.aboutview);
aboutView.setAPPInfo(genDefaultAppInfo());
}
LinearLayout layout = findViewById(R.id.aboutviewroot_ll);
// 创建布局参数(宽度和高度)
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
layout.addView(aboutView, params);
//WinBoLLActivityManager.getInstance().add(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//WinBoLLActivityManager.getInstance().registeRemove(this);
}
APPInfo extractAPPInfoFromIntent() {
Intent intent = getIntent();
APPInfo appInfo = null;
if (intent != null) {
appInfo = (APPInfo)intent.getSerializableExtra(EXTRA_APPINFO);
}
return appInfo == null ? genDefaultAPPInfo() : appInfo;
}
APPInfo genDefaultAPPInfo() {
String szBranchName = "aes";
APPInfo appInfo = new APPInfo();
appInfo.setAppName("AES");
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription("WinBoLL AndroidX 可视化元素类库。");
appInfo.setAppGitName("AES");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(szBranchName);
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=AES");
appInfo.setAppAPKName("AES");
appInfo.setAppAPKFolderName("AES");
//appInfo.setIsAddDebugTools(false);
//appInfo.setIsAddDebugTools(BuildConfig.DEBUG);
return appInfo;
}
private APPInfo genDefaultAppInfo() {
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
String branchName = "appbase";
APPInfo appInfo = new APPInfo();
appInfo.setAppName(getString(R.string.app_name));
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("APPBase");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=APPBase");
appInfo.setAppAPKName("APPBase");
appInfo.setAppAPKFolderName("APPBase");
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
return appInfo;
}
}

View File

@@ -1,373 +0,0 @@
package cc.winboll.studio.appbase;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.io.IOException;
import mehdi.sakout.aboutpage.AboutPage;
import mehdi.sakout.aboutpage.Element;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/24 15:08:52
* @Describe WinBoLL应用介绍视图
*/
public class AboutView extends LinearLayout {
public static final String TAG = "AboutView";
public static final int MSG_APPUPDATE_CHECKED = 0;
static Context _mContext;
APPInfo mAPPInfo;
//WinBoLLServiceStatusView mWinBoLLServiceStatusView;
OnRequestDevUserInfoAutofillListener mOnRequestDevUserInfoAutofillListener;
String mszAppName = "";
String mszAppAPKFolderName = "";
String mszAppAPKName = "";
String mszAppGitName = "";
String mszAppVersionName = "";
String mszCurrentAppPackageName = "";
boolean mIsAddDebugTools;
volatile String mszNewestAppPackageName = "";
String mszAppDescription = "";
String mszHomePage = "";
String mszGitea = "";
int mnAppIcon = 0;
String mszWinBoLLServerHost;
String mszReleaseAPKName;
EditText metDevUserName;
EditText metDevUserPassword;
public AboutView(Context context, APPInfo appInfo) {
super(context);
_mContext = context;
setAPPInfo(appInfo);
initView(context);
}
public AboutView(Context context, AttributeSet attrs) {
super(context, attrs);
_mContext = context;
initView(context, attrs);
}
public void setAPPInfo(APPInfo appInfo) {
mAPPInfo = appInfo;
}
APPInfo createAppInfo(Context context, AttributeSet attrs) {
APPInfo appInfo = new APPInfo();
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AboutView);
appInfo.setAppName(typedArray.getString(R.styleable.AboutView_app_name));
appInfo.setAppAPKFolderName(typedArray.getString(R.styleable.AboutView_app_apkfoldername));
appInfo.setAppAPKName(typedArray.getString(R.styleable.AboutView_app_apkname));
appInfo.setAppGitName(typedArray.getString(R.styleable.AboutView_app_gitname));
appInfo.setAppGitOwner(typedArray.getString(R.styleable.AboutView_app_gitowner));
appInfo.setAppGitAPPBranch(typedArray.getString(R.styleable.AboutView_app_gitappbranch));
appInfo.setAppGitAPPSubProjectFolder(typedArray.getString(R.styleable.AboutView_app_gitappsubprojectfolder));
appInfo.setAppDescription(typedArray.getString(R.styleable.AboutView_appdescription));
appInfo.setAppIcon(typedArray.getResourceId(R.styleable.AboutView_appicon, R.drawable.ic_winboll));
appInfo.setIsAddDebugTools(typedArray.getBoolean(R.styleable.AboutView_is_adddebugtools, false));
// 返回一个绑定资源结束的信号给资源
typedArray.recycle();
return appInfo;
}
void initView(Context context) {
mszAppName = mAPPInfo.getAppName();
mszAppAPKFolderName = mAPPInfo.getAppAPKFolderName();
mszAppAPKName = mAPPInfo.getAppAPKName();
mszAppGitName = mAPPInfo.getAppGitName();
mszAppDescription = mAPPInfo.getAppDescription();
mnAppIcon = mAPPInfo.getAppIcon();
mszWinBoLLServerHost = GlobalApplication.isDebugging() ? "https://yun-preivew.winboll.cc": "https://yun.winboll.cc";
try {
mszAppVersionName = _mContext.getPackageManager().getPackageInfo(_mContext.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
mszCurrentAppPackageName = mszAppAPKName + "_" + mszAppVersionName + ".apk";
mszHomePage = mAPPInfo.getAppHomePage();
//mszHomePage = mszWinBoLLServerHost + "/studio/details.php?app=" + mszAppAPKFolderName;
if (mAPPInfo.getAppGitAPPBranch().equals("")) {
mszGitea = "https://gitea.winboll.cc/" + mAPPInfo.getAppGitOwner() + "/" + mszAppGitName;
} else {
mszGitea = "https://gitea.winboll.cc/" + mAPPInfo.getAppGitOwner() + "/" + mszAppGitName + "/src/branch/" + mAPPInfo.getAppGitAPPBranch() + "/" + mAPPInfo.getAppGitAPPSubProjectFolder();
}
addView(createAboutPage());
// 初始化标题栏
//setSubtitle(getContext().getString(R.string.text_about));
// LinearLayout llMain = findViewById(R.id.viewaboutLinearLayout1);
// llMain.addView(createAboutPage());
// 就读取正式版应用包版本号,设置 Release 应用包文件名
String szReleaseAppVersionName = "";
try {
//LogUtils.d(TAG, String.format("mContext.getPackageName() %s", mContext.getPackageName()));
String szSubBetaSuffix = subBetaSuffix(_mContext.getPackageName());
//LogUtils.d(TAG, String.format("szSubBetaSuffix : %s", szSubBetaSuffix));
szReleaseAppVersionName = _mContext.getPackageManager().getPackageInfo(szSubBetaSuffix, 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
mszReleaseAPKName = mszAppAPKName + "_" + szReleaseAppVersionName + ".apk";
}
void initView(Context context, AttributeSet attrs) {
mAPPInfo = createAppInfo(context, attrs);
initView(context);
}
public static String subBetaSuffix(String input) {
if (input.endsWith(".beta")) {
return input.substring(0, input.length() - ".beta".length());
}
return input;
}
android.os.Handler mHandler = new android.os.Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_APPUPDATE_CHECKED : {
/*//检查当前应用包文件名是否是测试版,如果是就忽略检查
if(mszCurrentAppPackageName.matches(".*_\\d+\\.\\d+\\.\\d+-beta.*\\.apk")) {
ToastUtils.show("APP is the beta Version. Version check ignore.");
return;
}*/
// if (!AppVersionUtils.isHasNewStageReleaseVersion(mszReleaseAPKName, mszNewestAppPackageName)) {
// ToastUtils.delayedShow("Current app is the newest.", 5000);
// }
if (!AppVersionUtils.isHasNewVersion2(mszCurrentAppPackageName, mszNewestAppPackageName)) {
ToastUtils.show("Current app is the newest.");
} else {
String szMsg = "Current app is :\n[ " + mszCurrentAppPackageName
+ " ]\nThe last app is :\n[ " + mszNewestAppPackageName
+ " ]\nIs download the last app?";
YesNoAlertDialog.show(_mContext, "Application Update Prompt", szMsg, mIsDownlaodUpdateListener);
}
break;
}
}
}
};
protected View createAboutPage() {
// 定义 GitWeb 按钮
//
Element elementGitWeb = new Element(_mContext.getString(R.string.gitea_home), R.drawable.ic_winboll);
elementGitWeb.setOnClickListener(mGitWebOnClickListener);
// 定义检查更新按钮
//
/*Element elementAppUpdate = new Element(_mContext.getString(R.string.app_update), R.drawable.ic_winboll);
elementAppUpdate.setOnClickListener(mAppUpdateOnClickListener);
*/
String szAppInfo = "";
try {
szAppInfo = mszAppName + " "
+ _mContext.getPackageManager().getPackageInfo(_mContext.getPackageName(), 0).versionName
+ "\n" + mszAppDescription;
} catch (PackageManager.NameNotFoundException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
AboutPage aboutPage = new AboutPage(_mContext);
aboutPage.setDescription(szAppInfo)
//.isRTL(false)
//.setCustomFont(String) // or Typeface
.setImage(mnAppIcon)
//.addItem(versionElement)
//.addItem(adsElement)
//.addGroup("Connect with us")
.addEmail("WinBoLLStudio<studio@winboll.cc>")
.addWebsite(mszHomePage)
.addItem(elementGitWeb);
//.addItem(elementAppUpdate);
//.addFacebook("the.medy")
//.addTwitter("medyo80")
//.addYoutube("UCdPQtdWIsg7_pi4mrRu46vA")
//.addPlayStore("com.ideashower.readitlater.pro")
//.addGitHub("medyo")
//.addInstagram("medyo80")
//.create();
/*if (mAPPInfo.isAddDebugTools()) {
// 定义应用调试按钮
//
Element elementAppMode;
if (GlobalApplication.isDebugging()) {
elementAppMode = new Element(_mContext.getString(R.string.app_normal), R.drawable.ic_winboll);
elementAppMode.setOnClickListener(mAppNormalOnClickListener);
} else {
elementAppMode = new Element(_mContext.getString(R.string.app_debug), R.drawable.ic_winboll);
elementAppMode.setOnClickListener(mAppDebugOnClickListener);
}
aboutPage.addItem(elementAppMode);
}*/
return aboutPage.create();
}
View.OnClickListener mAppDebugOnClickListener = new View.OnClickListener(){
@Override
public void onClick(View view) {
//ToastUtils.show("mAppDebugOnClickListener");
setApp2DebugMode(_mContext);
}
};
View.OnClickListener mAppNormalOnClickListener = new View.OnClickListener(){
@Override
public void onClick(View view) {
//ToastUtils.show("mAppNormalOnClickListener");
setApp2NormalMode(_mContext);
}
};
public static void setApp2DebugMode(Context context) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
if (intent != null) {
//intent.setAction(cc.winboll.studio.libapputils.intent.action.DEBUGVIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
GlobalApplication.setIsDebugging(true);
GlobalApplication.saveDebugStatus((GlobalApplication)_mContext.getApplicationContext());
//WinBoLLActivityManager.getInstance().finishAll();
context.startActivity(intent);
}
}
public static void setApp2NormalMode(Context context) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
GlobalApplication.setIsDebugging(false);
GlobalApplication.saveDebugStatus((GlobalApplication)_mContext.getApplicationContext());
//WinBoLLActivityManager.getInstance().finishAll();
context.startActivity(intent);
}
}
View.OnClickListener mGitWebOnClickListener = new View.OnClickListener(){
@Override
public void onClick(View view) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(mszGitea));
_mContext.startActivity(browserIntent);
}
};
View.OnClickListener mAppUpdateOnClickListener = new View.OnClickListener(){
@Override
public void onClick(View view) {
ToastUtils.show("Start app update checking.");
new Thread(new Runnable() {
@Override
public void run() {
String szUrl = mszWinBoLLServerHost + "/studio/details.php?app=" + mszAppAPKFolderName;
// 构建包含认证信息的请求
String credential = "";
if (GlobalApplication.isDebugging()) {
credential = Credentials.basic(metDevUserName.getText().toString(), metDevUserPassword.getText().toString());
PrefUtils.saveString(_mContext, "metDevUserName", metDevUserName.getText().toString());
PrefUtils.saveString(_mContext, "metDevUserPassword", metDevUserPassword.getText().toString());
} else {
String username = "WinBoLL";
String password = "WinBoLLPowerByZhanGSKen";
credential = Credentials.basic(username, password);
}
Request request = new Request.Builder()
.url(szUrl)
.header("Accept", "text/plain") // 设置正确的Content-Type头
.header("Authorization", credential)
.build();
OkHttpClient client = new OkHttpClient();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 处理网络请求失败
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
LogUtils.d(TAG, "Unexpected code " + response, Thread.currentThread().getStackTrace());
return;
}
try {
// 读取响应体作为字符串,注意这里可能需要解码
String text = response.body().string();
org.jsoup.nodes.Document doc = org.jsoup.Jsoup.parse(text);
LogUtils.v(TAG, doc.text());
// 使用id选择器找到具有特定id的元素
org.jsoup.nodes.Element elementWithId = doc.select("#LastRelease").first(); // 获取第一个匹配的元素
// 提取并打印元素的文本内容
mszNewestAppPackageName = elementWithId.text();
//ToastUtils.delayedShow(text + "\n" + mszNewestAppPackageName, 5000);
mHandler.sendMessage(mHandler.obtainMessage(MSG_APPUPDATE_CHECKED));
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
});
}
}).start();
}
};
YesNoAlertDialog.OnDialogResultListener mIsDownlaodUpdateListener = new YesNoAlertDialog.OnDialogResultListener() {
@Override
public void onYes() {
String szUrl = mszWinBoLLServerHost + "/studio/download.php?appname=" + mszAppAPKFolderName + "&apkname=" + mszNewestAppPackageName;
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(szUrl));
_mContext.startActivity(browserIntent);
}
@Override
public void onNo() {
}
};
public interface OnRequestDevUserInfoAutofillListener {
void requestAutofill(EditText etDevUserName, EditText etDevUserPassword);
}
public void setOnRequestDevUserInfoAutofillListener(OnRequestDevUserInfoAutofillListener l) {
mOnRequestDevUserInfoAutofillListener = l;
}
}

View File

@@ -1,160 +0,0 @@
package cc.winboll.studio.appbase;
import cc.winboll.studio.libappbase.LogUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/08/12 14:45:35
* @Describe 应用版本工具集
*/
public class AppVersionUtils {
public static final String TAG = "AppVersionUtils";
//
// 检查新版本是否成立
// szCurrentCode : 当前版本应用包名
// szNextCode : 新版本应用包名
// 返回 情况1当前版本是发布版
// 返回 true (新版本 > 当前版本)
// 情况1当前版本是Beta版
// true 新版本 == 当前版本
//
public static boolean isHasNewVersion2(String szCurrentName, String szNextName) {
LogUtils.d(TAG, String.format("isHasNewVersion2\nszCurrentName : %s\nszNextName : %s", szCurrentName, szNextName));
//szCurrentName = "AES_6.2.0-beta0_3234.apk";
//szNextName = "AES_6.1.12.apk";
//szCurrentName = "AES_6.2.0-beta0_3234.apk";
//szNextName = "AES_6.2.0.apk";
//szCurrentName = "AES_6.2.0-beta0_3234.apk";
//szNextName = "AES_6.2.2.apk";
//szCurrentName = "AES_6.2.0-beta0_3234.apk";
//szNextName = "AES_6.2.0.apk";
//szCurrentName = "AES_6.1.0.apk";
//szNextName = "AES_6.2.0.apk";
//LogUtils.d(TAG, "szCurrentName : " + szCurrentName);
//LogUtils.d(TAG, "szNextName : " + szNextName);
//boolean isVersionNewer = false;
//if(szCurrentName.equals(szNextName)) {
// isVersionNewer = false;
//} else {
//ToastUtils.show("szCurrent : " + szCurrent + "\nszNext : " + szNext);
//int nApk = szNextName.lastIndexOf(".apk");
//ToastUtils.show("nApk : " + Integer.toString(nApk));
//String szNextNoApkName = szNextName.substring(0, nApk);
//ToastUtils.show("szNextNoApkName : " + szNextNoApkName);
//String szCurrentNoApkName = szCurrentName.substring(0, szNextNoApkName.length());
//ToastUtils.show("szCurrentNoApkName : " + szCurrentNoApkName);
//String str1 = "3.4.50";
//String str2 = "3.3.60";
//String str1 = getCodeInPackageName(szCurrentName);
//String str2 = getCodeInPackageName(szNextName);
//String str1 = getCodeInPackageName(szNextName);
//String str2 = getCodeInPackageName(szCurrentName);
//Boolean isVersionNewer2 = checkNewVersion(str1,str2);
//ToastUtils.show("isVersionNewer2 : " + Boolean.toString(isVersionNewer2));
//ToastUtils.show(checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName)));
//return checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName));
//}
//return isVersionNewer;
if (checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName))) {
// 比 AES_6.2.0.apk 版本大,如 AES_6.2.1.apk。
// 比 AES_6.2.0-beta0_3234.apk 大,如 AES_6.2.1.apk。
//LogUtils.d(TAG, "App newer stage version is released. Release name : " + szNextName);
return true;
}
if (szCurrentName.matches(".*_\\d+\\.\\d+\\.\\d+-beta.*\\.apk")) {
String szCurrentReleasePackageName = getReleasePackageName(szCurrentName);
//LogUtils.d(TAG, "szCurrentReleasePackageName : " + szCurrentReleasePackageName);
if (szCurrentReleasePackageName.equals(szNextName)) {
// 与 AES_6.2.0-beta0_3234.apk 版本相同,如 AES_6.2.0.apk。
//LogUtils.d(TAG, "App stage version is released. Release name : " + szNextName);
return true;
}
}
//LogUtils.d(TAG, "App version is the newest. ");
return false;
}
public static boolean isHasNewStageReleaseVersion(String szCurrentName, String szNextName) {
LogUtils.d(TAG, String.format("isHasNewStageReleaseVersion\nszCurrentName : %s\nszNextName : %s", szCurrentName, szNextName));
//szCurrentName = "AES_6.2.12.apk";
//szNextName = "AES_6.3.12.apk";
if (checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName))) {
// 比 AES_6.2.0.apk 版本大,如 AES_6.2.1.apk。
//LogUtils.d(TAG, "App newer stage version is released. Release name : " + szNextName);
return true;
}
return false;
}
//
// 检查新版本是否成立
// szCurrentCode : 当前版本
// szNextCode : 新版本
// 返回 true 新版本 > 当前版本
//
public static Boolean checkNewVersion(String szCurrentCode, String szNextCode) {
if (szCurrentCode == null || szCurrentCode.equals("") || szNextCode == null || szNextCode.equals("")) {
LogUtils.d(TAG, String.format("checkNewVersion unexpected parameters:\nszCurrentCode : %s\nszNextCode : %s", szCurrentCode, szNextCode));
return false;
}
boolean isNew = false;
String[] appVersionCurrent = szCurrentCode.split("\\.");
String[] appVersionNext = szNextCode.split("\\.");
//根据位数最短的判断
int lim = appVersionCurrent.length > appVersionNext.length ? appVersionNext.length : appVersionCurrent.length;
//根据位数循环判断各个版本
for (int i = 0; i < lim; i++) {
if (Integer.parseInt(appVersionNext[i]) > Integer.parseInt(appVersionCurrent[i])) {
isNew = true;
return isNew;
} else if (Integer.parseInt(appVersionNext[i]) == Integer.parseInt(appVersionCurrent[i])) {
continue ;
} else {
isNew = false;
return isNew;
}
}
return isNew;
}
//
// 截取应用包名称版本号信息
// 如 AppUtils_7.0.4-beta1_0120.apk 版本号为 7.0.4
// 如 AppUtils_7.0.4.apk 版本号为 7.0.4
//
public static String getCodeInPackageName(String apkName) {
LogUtils.d(TAG, String.format("getCodeInPackageName apkName : %s", apkName));
//String apkName = "AppUtils_7.0.0.apk";
Pattern pattern = Pattern.compile("\\d+\\.\\d+\\.\\d+");
Matcher matcher = pattern.matcher(apkName);
if (matcher.find()) {
String version = matcher.group();
LogUtils.d(TAG, String.format("version is %s", version));
return version;
//System.out.println("Version number: " + version); // 输出7.0.0
}
LogUtils.d(TAG, String.format("No result."));
return "";
}
//
// 根据Beta版名称生成发布版应用包名称
// 如 AppUtils_7.0.4-beta1_0120.apk
// 发布版名称就为AppUtils_7.0.4.apk
//
public static String getReleasePackageName(String szBetaPackageName) {
//String szBetaPackageName = "AppUtils_7.0.4-beta1_0120.apk";
Pattern pattern = Pattern.compile(".*\\d+\\.\\d+\\.\\d+");
Matcher matcher = pattern.matcher(szBetaPackageName);
if (matcher.find()) {
String szReleasePackageName = matcher.group();
return szReleasePackageName + ".apk";
//System.out.println("Version number: " + version); // 输出7.0.0
}
return "";
}
}

View File

@@ -140,28 +140,8 @@ public class MainActivity extends Activity {
public void onAboutActivity(View view) {
LogUtils.d(TAG, "startAboutActivity() 调用");
Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class);
APPInfo appInfo = genDefaultAppInfo();
aboutIntent.putExtra(AboutActivity.EXTRA_APPINFO, appInfo);
startActivity(aboutIntent);
LogUtils.d(TAG, "startAboutActivity: 关于页面已启动");
}
private APPInfo genDefaultAppInfo() {
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
String branchName = "powerbell";
APPInfo appInfo = new APPInfo();
appInfo.setAppName(getString(R.string.app_name));
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=PowerBell");
appInfo.setAppAPKName("PowerBell");
appInfo.setAppAPKFolderName("PowerBell");
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
return appInfo;
}
}

View File

@@ -1,32 +0,0 @@
package cc.winboll.studio.appbase;
import android.content.Context;
import android.content.SharedPreferences;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/13 06:50
* @Describe 应用变量保存工具
*/
public class PrefUtils {
public static final String TAG = "PrefUtils";
//
// 保存字符串到SharedPreferences的函数
//
public static void saveString(Context context, String key, String value) {
SharedPreferences sharedPreferences = context.getSharedPreferences("myPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(key, value);
editor.apply();
}
//
// 从SharedPreferences读取字符串的函数
//
public static String getString(Context context, String key, String defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences("myPrefs", Context.MODE_PRIVATE);
return sharedPreferences.getString(key, defaultValue);
}
}

View File

@@ -1,61 +0,0 @@
package cc.winboll.studio.appbase;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/28 17:40:47
* @Date 2024/08/12 14:46:25
* @Describe 询问用户确定与否的选择框
*/
public class YesNoAlertDialog {
public static final String TAG = "YesNoAlertDialog";
public static void show(Context context, String szTitle, String szMessage, final OnDialogResultListener listener) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
context);
// set title
alertDialogBuilder.setTitle(szTitle);
// set dialog message
alertDialogBuilder
.setMessage(szMessage)
.setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener(){
@Override
public void onCancel(DialogInterface dialog) {
listener.onNo();
}
})
.setPositiveButton("YES", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, close
// current activity
listener.onYes();
}
})
.setNegativeButton("NO", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, just close
// the dialog box and do nothing
dialog.cancel();
}
});
// create alert dialog
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
}
public interface OnDialogResultListener {
abstract void onYes();
abstract void onNo();
}
}

View File

@@ -11,10 +11,11 @@
android:layout_height="wrap_content"
android:id="@+id/toolbar"/>
<LinearLayout
android:orientation="vertical"
<cc.winboll.studio.libappbase.views.AboutView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:id="@+id/aboutviewroot_ll"/>
android:id="@+id/aboutview"/>
</LinearLayout>

View File

@@ -4,15 +4,12 @@ apply from: '../.winboll/winboll_lib_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
android {
// 关键:改为你已安装的 SDK 32≥ targetSdkVersion 30兼容已安装环境
compileSdkVersion 32
// 直接使用已安装的构建工具 33.0.3(无需修改)
buildToolsVersion "33.0.3"
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 23
minSdkVersion 21
targetSdkVersion 30
}
buildTypes {

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Jan 06 14:17:27 HKT 2026
#Sun Jan 11 06:14:37 GMT 2026
stageCount=3
libraryProject=libappbase
baseVersion=15.14
baseVersion=15.15
publishVersion=15.14.2
buildCount=0
buildCount=14
baseBetaVersion=15.14.3

View File

@@ -4,6 +4,7 @@
package="cc.winboll.studio.libappbase">
<application>
<activity
android:name=".CrashHandler$CrashActivity"
android:label="CrashActivity"
@@ -27,6 +28,8 @@
</activity>
<activity android:name="cc.winboll.studio.libappbase.activities.AboutActivity"/>
</application>
</manifest>
</manifest>

View File

@@ -0,0 +1,214 @@
package cc.winboll.studio.libappbase.models;
import cc.winboll.studio.libappbase.R;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.Serializable;
/**
* @Describe 应用信息实体类,存储应用核心配置信息,实现序列化接口
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/11 12:29:00
* @LastEditTime 2026/01/12 00:15:00
*/
public class APPInfo implements Serializable {
// 常量定义区
public static final String TAG = "APPInfo";
// 成员属性区按功能归类排序统一私有访问权限Java7 兼容,注释清晰)
private String appName; // 应用名称
private int appIcon; // 应用图标资源ID
private String appDescription; // 应用描述文案
private String appGitName; // 应用Git仓库名称
private String appGitOwner; // 应用Git仓库拥有者账号
private String appGitAPPBranch; // 应用Git仓库对应分支
private String appGitAPPSubProjectFolder; // 应用Git仓库内子项目文件夹路径
private String appHomePage; // 应用官方主页地址
private String appAPKName; // 应用安装包名称
private String appAPKFolderName; // 应用安装包存储文件夹名称
private boolean isAddDebugTools; // 是否启用应用调试工具功能
// 构造方法区(按 参数从少到多 排序,逻辑清晰,便于调用选型)
/**
* 无参构造方法默认初始化WinBoLL应用基础配置
*/
public APPInfo() {
LogUtils.d(TAG, "APPInfo() 无参构造方法调用,执行默认配置初始化");
String szBranchName = "winboll";
this.appName = "WinBoLL";
this.appIcon = R.drawable.ic_winboll;
this.appDescription = "Hello, WinBoLl!";
this.appGitName = "WinBoLL";
this.appGitOwner = "Studio";
this.appGitAPPBranch = szBranchName;
this.appGitAPPSubProjectFolder = szBranchName;
this.appHomePage = "https://www.winboll.cc/apks/index.php?project=WinBoLL";
this.appAPKName = "WinBoLL";
this.appAPKFolderName = "WinBoLL";
this.isAddDebugTools = false;
LogUtils.d(TAG, "APPInfo() 无参构造初始化完成,默认应用名称:" + this.appName);
}
/**
* 多参构造方法(不含调试工具配置,默认关闭调试功能)
* @param appName 应用名称
* @param appIcon 应用图标资源ID
* @param appDescription 应用描述
* @param appGitName Git仓库名称
* @param appGitOwner Git仓库拥有者
* @param appGitAPPBranch Git仓库分支
* @param appGitAPPSubProjectFolder Git子项目文件夹
* @param appHomePage 应用主页
* @param appAPKName 应用包名
* @param appAPKFolderName 应用包存储文件夹名
*/
public APPInfo(String appName, int appIcon, String appDescription, String appGitName, String appGitOwner,
String appGitAPPBranch, String appGitAPPSubProjectFolder, String appHomePage,
String appAPKName, String appAPKFolderName) {
LogUtils.d(TAG, "APPInfo(多参无调试) 构造调用,入参应用名:" + appName + " | Git仓库名" + appGitName);
this.appName = appName;
this.appIcon = appIcon;
this.appDescription = appDescription;
this.appGitName = appGitName;
this.appGitOwner = appGitOwner;
this.appGitAPPBranch = appGitAPPBranch;
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
this.appHomePage = appHomePage;
this.appAPKName = appAPKName;
this.appAPKFolderName = appAPKFolderName;
this.isAddDebugTools = false;
LogUtils.d(TAG, "APPInfo(多参无调试) 构造初始化完成");
}
/**
* 全参构造方法(包含调试工具配置,支持自定义调试开关)
* @param appName 应用名称
* @param appIcon 应用图标资源ID
* @param appDescription 应用描述
* @param appGitName Git仓库名称
* @param appGitOwner Git仓库拥有者
* @param appGitAPPBranch Git仓库分支
* @param appGitAPPSubProjectFolder Git子项目文件夹
* @param appHomePage 应用主页
* @param appAPKName 应用包名
* @param appAPKFolderName 应用包存储文件夹名
* @param isAddDebugTools 是否开启调试工具
*/
public APPInfo(String appName, int appIcon, String appDescription, String appGitName, String appGitOwner,
String appGitAPPBranch, String appGitAPPSubProjectFolder, String appHomePage,
String appAPKName, String appAPKFolderName, boolean isAddDebugTools) {
LogUtils.d(TAG, "APPInfo(全参带调试) 构造调用,入参应用名:" + appName + " | 调试开关:" + isAddDebugTools);
this.appName = appName;
this.appIcon = appIcon;
this.appDescription = appDescription;
this.appGitName = appGitName;
this.appGitOwner = appGitOwner;
this.appGitAPPBranch = appGitAPPBranch;
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
this.appHomePage = appHomePage;
this.appAPKName = appAPKName;
this.appAPKFolderName = appAPKFolderName;
this.isAddDebugTools = isAddDebugTools;
LogUtils.d(TAG, "APPInfo(全参带调试) 构造初始化完成");
}
// Getter/Setter 方法区严格跟随成员属性定义顺序易查找维护仅Setter加调试日志
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
LogUtils.d(TAG, "setAppName() 调用,传入应用名称:" + appName);
this.appName = appName;
}
public int getAppIcon() {
return appIcon;
}
public void setAppIcon(int appIcon) {
LogUtils.d(TAG, "setAppIcon() 调用传入图标资源ID" + appIcon);
this.appIcon = appIcon;
}
public String getAppDescription() {
return appDescription;
}
public void setAppDescription(String appDescription) {
LogUtils.d(TAG, "setAppDescription() 调用,传入描述文案:" + appDescription);
this.appDescription = appDescription;
}
public String getAppGitName() {
return appGitName;
}
public void setAppGitName(String appGitName) {
LogUtils.d(TAG, "setAppGitName() 调用传入Git仓库名" + appGitName);
this.appGitName = appGitName;
}
public String getAppGitOwner() {
return appGitOwner;
}
public void setAppGitOwner(String appGitOwner) {
LogUtils.d(TAG, "setAppGitOwner() 调用传入Git拥有者" + appGitOwner);
this.appGitOwner = appGitOwner;
}
public String getAppGitAPPBranch() {
return appGitAPPBranch;
}
public void setAppGitAPPBranch(String appGitAPPBranch) {
LogUtils.d(TAG, "setAppGitAPPBranch() 调用传入Git分支" + appGitAPPBranch);
this.appGitAPPBranch = appGitAPPBranch;
}
public String getAppGitAPPSubProjectFolder() {
return appGitAPPSubProjectFolder;
}
public void setAppGitAPPSubProjectFolder(String appGitAPPSubProjectFolder) {
LogUtils.d(TAG, "setAppGitAPPSubProjectFolder() 调用传入Git子项目文件夹" + appGitAPPSubProjectFolder);
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
}
public String getAppHomePage() {
return appHomePage;
}
public void setAppHomePage(String appHomePage) {
LogUtils.d(TAG, "setAppHomePage() 调用,传入应用主页地址:" + appHomePage);
this.appHomePage = appHomePage;
}
public String getAppAPKName() {
return appAPKName;
}
public void setAppAPKName(String appAPKName) {
LogUtils.d(TAG, "setAppAPKName() 调用,传入应用包名:" + appAPKName);
this.appAPKName = appAPKName;
}
public String getAppAPKFolderName() {
return appAPKFolderName;
}
public void setAppAPKFolderName(String appAPKFolderName) {
LogUtils.d(TAG, "setAppAPKFolderName() 调用,传入包存储文件夹名:" + appAPKFolderName);
this.appAPKFolderName = appAPKFolderName;
}
public boolean isAddDebugTools() {
return isAddDebugTools;
}
public void setIsAddDebugTools(boolean isAddDebugTools) {
LogUtils.d(TAG, "setIsAddDebugTools() 调用,传入调试开关状态:" + isAddDebugTools);
this.isAddDebugTools = isAddDebugTools;
}
}

View File

@@ -0,0 +1,492 @@
package cc.winboll.studio.libappbase.views;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libappbase.models.APPInfo;
/**
* @Describe AboutView 原生实现关于页面无第三方依赖适配API30抽象通用功能控件邮件/网页跳转)
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/11 12:23:00
* @LastEditTime 2026/01/12 01:05:30
*/
public class AboutView extends LinearLayout {
// 全局常量区(标识、回调标识)
public static final String TAG = "AboutView";
public static final int MSG_APPUPDATE_CHECKED = 0;
// 固定链接常量
private static final String WINBOLL_OFFICIAL_HOME = "https://www.winboll.cc";
// 邮件相关常量(统一封装,便于维护)
private static final String EMAIL_TITLE = "联系WinBoLLStudio";
private static final String EMAIL_ADDRESS = "studio@winboll.cc";
private static final String EMAIL_TYPE = "message/rfc822";
// 布局尺寸常量统一管理适配多屏幕dp为基准单位
private static final int PADDING_LARGE = 32;
private static final int PADDING_MID = 16;
private static final int PADDING_SMALL = 8;
private static final int ICON_SIZE = 48;
private static final int LINE_HEIGHT = 1;
private static final int ITEM_ICON_SIZE = 24;
// 成员属性区(按 核心依赖→业务配置→视图相关 归类排序,注释清晰)
private Context mContext; // 上下文对象,全局复用
private APPInfo mAPPInfo; // 应用核心信息实体
private OnRequestDevUserInfoAutofillListener mOnRequestDevUserInfoAutofillListener; // 调试信息填充监听
private String mszAppName = ""; // 应用名称
private String mszAppVersionName = ""; // 应用版本号
private String mszAppDescription = ""; // 应用描述文案
private String mszHomePage = ""; // 应用主页/APK下载地址
private String mszGitea = ""; // 应用Git源码地址
private String mszAppGitName = ""; // 应用Git仓库名称
private String mszAppAPKName = ""; // 应用APK基础名称
private String mszAppAPKFolderName = ""; // 应用APK存储文件夹
private String mszCurrentAppPackageName = "";// 当前APK完整文件名
private String mszReleaseAPKName = ""; // 正式版APK完整文件名
private volatile String mszNewestAppPackageName = ""; // 最新版APK文件名支持异步更新
private String mszWinBoLLServerHost = ""; // 服务器地址
private int mnAppIcon = 0; // 应用图标资源ID
private boolean mIsAddDebugTools = false; // 是否启用调试工具标识
private EditText metDevUserName; // 调试用户名输入框
private EditText metDevUserPassword; // 调试密码输入框
// 构造方法区(按 参数从少到多 排序,适配 代码创建+XML引用 场景)
public AboutView(Context context) {
super(context);
LogUtils.d(TAG, "AboutView(Context) 构造方法调用,代码创建视图场景");
this.mContext = context;
initDefaultParams();
}
public AboutView(Context context, APPInfo appInfo) {
super(context);
LogUtils.d(TAG, "AboutView(Context,APPInfo) 构造调用入参APPInfo" + (appInfo == null ? "null" : appInfo.getAppName()));
this.mContext = context;
this.mAPPInfo = appInfo;
initAll();
}
public AboutView(Context context, AttributeSet attrs) {
super(context, attrs);
LogUtils.d(TAG, "AboutView(Context,AttributeSet) 构造调用XML布局引用场景");
this.mContext = context;
initDefaultParams();
}
public AboutView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LogUtils.d(TAG, "AboutView(Context,AttributeSet,int) 构造调用XML布局+样式配置defStyleAttr" + defStyleAttr);
this.mContext = context;
initDefaultParams();
}
// 对外公开方法区(供外部调用,职责单一,注释明确)
/**
* 一站式初始化所有关于页逻辑,包含参数、信息、视图全流程初始化
*/
public void initAll() {
LogUtils.d(TAG, "initAll() 一站式初始化调用APPInfo是否为空" + (mAPPInfo == null));
if (mAPPInfo == null) {
LogUtils.w(TAG, "initAll() 初始化终止APPInfo 为 null无法获取应用核心信息");
return;
}
// 基础布局配置
setOrientation(VERTICAL);
setPadding(dp2px(PADDING_MID), dp2px(PADDING_LARGE), dp2px(PADDING_MID), dp2px(PADDING_LARGE));
setGravity(Gravity.CENTER_HORIZONTAL);
// 按初始化流程执行,有序无冗余
initDefaultParams();
initAppBaseInfo();
initAppVersionInfo();
initServerConfig();
initAppLinkInfo();
initReleaseAPKInfo();
initAboutPageView();
LogUtils.d(TAG, "initAll() 所有初始化流程执行完成");
}
/**
* 重置应用信息并重新初始化关于页,支持动态更新页面内容
* @param appInfo 新的应用信息实体
*/
public void setAPPInfoAndInit(APPInfo appInfo) {
LogUtils.d(TAG, "setAPPInfoAndInit() 调用传入新APPInfo" + (appInfo == null ? "null" : appInfo.getAppName()));
this.mAPPInfo = appInfo;
removeAllViews();
initAll();
LogUtils.d(TAG, "setAPPInfoAndInit() 应用信息重置+页面重构完成");
}
/**
* 设置应用信息(兼容旧调用逻辑),设置后自动重构页面
* @param appInfo 应用核心信息实体
*/
public void setAPPInfo(APPInfo appInfo) {
LogUtils.d(TAG, "setAPPInfo() 调用传入APPInfo" + (appInfo == null ? "null" : appInfo.getAppName()));
this.mAPPInfo = appInfo;
removeAllViews();
initAll();
}
/**
* 设置调试信息自动填充监听,用于调试场景的信息回调
* @param l 监听回调接口实现
*/
public void setOnRequestDevUserInfoAutofillListener(OnRequestDevUserInfoAutofillListener l) {
LogUtils.d(TAG, "setOnRequestDevUserInfoAutofillListener() 调试监听设置完成");
this.mOnRequestDevUserInfoAutofillListener = l;
}
// 内部初始化方法区(按 基础→业务→视图 流程排序,单一职责)
/**
* 初始化默认兜底参数,防止空指针,为后续初始化做基础铺垫
*/
private void initDefaultParams() {
LogUtils.d(TAG, "initDefaultParams() 执行默认参数初始化");
mszWinBoLLServerHost = GlobalApplication.isDebugging() ? "https://yun-preivew.winboll.cc" : "https://yun.winboll.cc";
mnAppIcon = mnAppIcon == 0 ? R.drawable.ic_winboll : mnAppIcon;
mIsAddDebugTools = false;
LogUtils.d(TAG, "initDefaultParams() 完成,默认服务器地址:" + mszWinBoLLServerHost + "默认图标ID" + mnAppIcon);
}
/**
* 从APPInfo实体读取应用基础核心配置赋值到本地属性
*/
private void initAppBaseInfo() {
LogUtils.d(TAG, "initAppBaseInfo() 读取APPInfo基础配置");
if (mAPPInfo == null) {
LogUtils.w(TAG, "initAppBaseInfo() 跳过执行APPInfo 为 null");
return;
}
mszAppName = mAPPInfo.getAppName() == null ? "" : mAPPInfo.getAppName();
mszAppAPKFolderName = mAPPInfo.getAppAPKFolderName() == null ? "" : mAPPInfo.getAppAPKFolderName();
mszAppAPKName = mAPPInfo.getAppAPKName() == null ? "" : mAPPInfo.getAppAPKName();
mszAppGitName = mAPPInfo.getAppGitName() == null ? "" : mAPPInfo.getAppGitName();
mszAppDescription = mAPPInfo.getAppDescription() == null ? "" : mAPPInfo.getAppDescription();
mnAppIcon = mAPPInfo.getAppIcon() != 0 ? mAPPInfo.getAppIcon() : mnAppIcon;
mIsAddDebugTools = mAPPInfo.isAddDebugTools();
LogUtils.d(TAG, "initAppBaseInfo() 读取完成,应用名:" + mszAppName + ",调试开关:" + mIsAddDebugTools);
}
/**
* 初始化应用版本信息,从包管理中获取当前应用版本号
*/
private void initAppVersionInfo() {
LogUtils.d(TAG, "initAppVersionInfo() 初始化应用版本信息");
try {
mszAppVersionName = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
LogUtils.d(TAG, "initAppVersionInfo() 获取版本号失败默认赋值unknown", e);
mszAppVersionName = "unknown";
}
mszCurrentAppPackageName = String.format("%s_%s.apk", mszAppAPKName, mszAppVersionName);
LogUtils.d(TAG, "initAppVersionInfo() 完成,版本号:" + mszAppVersionName + "当前APK名" + mszCurrentAppPackageName);
}
/**
* 初始化服务器相关配置,预留扩展接口
*/
private void initServerConfig() {
LogUtils.d(TAG, "initServerConfig() 服务器配置初始化(预留扩展)");
}
/**
* 初始化应用相关链接(主页+Git源码地址动态拼接Git地址
*/
private void initAppLinkInfo() {
LogUtils.d(TAG, "initAppLinkInfo() 初始化应用链接信息");
if (mAPPInfo == null) {
LogUtils.w(TAG, "initAppLinkInfo() 跳过执行APPInfo 为 null");
return;
}
mszHomePage = mAPPInfo.getAppHomePage() == null ? "" : mAPPInfo.getAppHomePage();
// 分场景拼接Git地址兼容无分支配置场景
if (mAPPInfo.getAppGitAPPBranch() == null || mAPPInfo.getAppGitAPPBranch().trim().isEmpty()) {
mszGitea = String.format("https://gitea.winboll.cc/%s/%s", mAPPInfo.getAppGitOwner(), mszAppGitName);
} else {
mszGitea = String.format("https://gitea.winboll.cc/%s/%s/src/branch/%s/%s",
mAPPInfo.getAppGitOwner(), mszAppGitName,
mAPPInfo.getAppGitAPPBranch(), mAPPInfo.getAppGitAPPSubProjectFolder());
}
LogUtils.d(TAG, "initAppLinkInfo() 完成,应用主页:" + mszHomePage + "Git地址" + mszGitea);
}
/**
* 初始化正式版APK信息去除beta后缀适配正式包命名规范
*/
private void initReleaseAPKInfo() {
LogUtils.d(TAG, "initReleaseAPKInfo() 初始化正式版APK信息");
String szReleaseAppVersionName = "unknown";
try {
String szSubBetaSuffix = subBetaSuffix(mContext.getPackageName());
szReleaseAppVersionName = mContext.getPackageManager().getPackageInfo(szSubBetaSuffix, 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
LogUtils.d(TAG, "initReleaseAPKInfo() 获取正式版版本号失败", e);
}
mszReleaseAPKName = String.format("%s_%s.apk", mszAppAPKName, szReleaseAppVersionName);
LogUtils.d(TAG, "initReleaseAPKInfo() 完成正式版APK名" + mszReleaseAPKName);
}
/**
* 核心视图组装:按 图标→应用信息→分割线→通用功能控件 顺序构建页面
*/
private void initAboutPageView() {
LogUtils.d(TAG, "initAboutPageView() 开始组装关于页视图");
addAppIcon();
addAppInfoDesc();
addLineSeparator();
// 通用功能控件:网页跳转类+邮件类,复用抽象控件
addView(new WebJumpFunctionItemView(mContext, "WinBoLL 主页", WINBOLL_OFFICIAL_HOME, R.drawable.ic_winboll));
addView(new EmailFunctionItemView(mContext, "联系邮箱", "WinBoLLStudio<studio@winboll.cc>", R.drawable.ic_winboll));
if (!mszHomePage.isEmpty()) {
addView(new WebJumpFunctionItemView(mContext, "应用APK下载地址", mszHomePage, R.drawable.ic_winboll));
}
if (!mszGitea.isEmpty()) {
addView(new WebJumpFunctionItemView(mContext, "应用Git源码地址", mszGitea, R.drawable.ic_winboll));
}
LogUtils.d(TAG, "initAboutPageView() 视图组装完成,功能项加载完毕");
}
// 视图构建辅助方法区(基础视图组件)
/**
* 添加应用图标组件,居中展示
*/
private void addAppIcon() {
ImageView ivIcon = new ImageView(mContext);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dp2px(ICON_SIZE), dp2px(ICON_SIZE));
params.bottomMargin = dp2px(PADDING_MID);
ivIcon.setLayoutParams(params);
ivIcon.setImageResource(mnAppIcon);
ivIcon.setScaleType(ImageView.ScaleType.CENTER_CROP);
addView(ivIcon);
}
/**
* 添加应用名称+版本号+描述信息组件,垂直居中展示
*/
private void addAppInfoDesc() {
LinearLayout llDesc = new LinearLayout(mContext);
llDesc.setOrientation(VERTICAL);
llDesc.setGravity(Gravity.CENTER);
llDesc.setPadding(0, 0, 0, dp2px(PADDING_MID));
TextView tvAppName = new TextView(mContext);
tvAppName.setText(String.format("%s %s", mszAppName, mszAppVersionName));
tvAppName.setTextSize(18);
tvAppName.setTextColor(mContext.getResources().getColor(R.color.gray_900));
llDesc.addView(tvAppName);
if (!mszAppDescription.isEmpty()) {
TextView tvDesc = new TextView(mContext);
tvDesc.setText(mszAppDescription);
tvDesc.setTextSize(14);
tvDesc.setTextColor(mContext.getResources().getColor(R.color.gray_500));
tvDesc.setPadding(0, dp2px(PADDING_SMALL), 0, 0);
llDesc.addView(tvDesc);
}
addView(llDesc);
}
/**
* 添加视图分割线,区分不同功能模块
*/
private void addLineSeparator() {
View line = new View(mContext);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, dp2px(LINE_HEIGHT));
params.topMargin = dp2px(PADDING_SMALL);
params.bottomMargin = dp2px(PADDING_MID);
line.setLayoutParams(params);
line.setBackgroundColor(mContext.getResources().getColor(R.color.gray_200));
addView(line);
}
// 工具方法区(通用工具+业务工具,静态优先,便于复用)
/**
* dp 转 px 工具方法,适配不同屏幕密度,保证布局一致性
* @param dpValue dp单位尺寸
* @return 转换后的px单位尺寸
*/
private int dp2px(int dpValue) {
float density = mContext.getResources().getDisplayMetrics().density;
return (int) (dpValue * density + 0.5f);
}
/**
* 去除包名beta后缀适配正式版包名规范静态方法支持外部调用
* @param input 原始包名
* @return 去除beta后缀后的正式包名
*/
public static String subBetaSuffix(String input) {
LogUtils.d(TAG, "subBetaSuffix() 执行包名beta后缀去除原始包名" + input);
if (input != null && input.endsWith(".beta")) {
String result = input.substring(0, input.length() - ".beta".length());
LogUtils.d(TAG, "subBetaSuffix() 处理成功,正式包名:" + result);
return result;
}
LogUtils.d(TAG, "subBetaSuffix() 无需处理包名不含beta后缀");
return input == null ? "" : input;
}
// 内部抽象通用功能项基类 - 统一样式,减少冗余
private abstract class BaseFunctionItemView extends LinearLayout implements OnClickListener {
protected Context mItemContext;
protected String mTitle;
protected String mContent;
protected int mIconRes;
public BaseFunctionItemView(Context context, String title, String content, int iconRes) {
super(context);
this.mItemContext = context;
this.mTitle = title;
this.mContent = content;
this.mIconRes = iconRes;
initItemLayout();
initItemViews();
setOnClickListener(this);
}
// 统一布局配置
private void initItemLayout() {
setOrientation(HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
setPadding(dp2px(PADDING_MID), dp2px(PADDING_SMALL), dp2px(PADDING_MID), dp2px(PADDING_SMALL));
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
setClickable(true);
setBackgroundResource(android.R.drawable.list_selector_background);
}
// 统一视图构建
private void initItemViews() {
// 左侧图标
if (mIconRes != 0) {
ImageView ivIcon = new ImageView(mItemContext);
LayoutParams iconParams = new LayoutParams(dp2px(ITEM_ICON_SIZE), dp2px(ITEM_ICON_SIZE));
iconParams.rightMargin = dp2px(PADDING_SMALL);
ivIcon.setLayoutParams(iconParams);
ivIcon.setImageResource(mIconRes);
addView(ivIcon);
}
// 右侧文本容器
LinearLayout llText = new LinearLayout(mItemContext);
llText.setOrientation(VERTICAL);
llText.setLayoutParams(new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1.0f));
addView(llText);
// 标题
TextView tvTitle = new TextView(mItemContext);
tvTitle.setText(mTitle);
tvTitle.setTextSize(16);
tvTitle.setTextColor(mItemContext.getResources().getColor(R.color.gray_900));
llText.addView(tvTitle);
// 内容
TextView tvContent = new TextView(mItemContext);
tvContent.setText(mContent);
tvContent.setTextSize(14);
tvContent.setTextColor(getContentTextColor());
tvContent.setPadding(0, dp2px(PADDING_SMALL), 0, 0);
llText.addView(tvContent);
}
// 子类指定内容文本颜色
protected abstract int getContentTextColor();
}
// 邮件类功能控件 - 专属邮件唤起逻辑
private class EmailFunctionItemView extends BaseFunctionItemView {
public EmailFunctionItemView(Context context, String title, String content, int iconRes) {
super(context, title, content, iconRes);
}
@Override
protected int getContentTextColor() {
return mItemContext.getResources().getColor(R.color.blue_normal);
}
@Override
public void onClick(View v) {
LogUtils.d(TAG, "EmailFunctionItemView onClick 触发邮件唤起");
// 双方案邮件唤起逻辑
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
emailIntent.setData(Uri.parse("mailto:" + EMAIL_ADDRESS));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, EMAIL_TITLE);
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (emailIntent.resolveActivity(mItemContext.getPackageManager()) != null) {
mItemContext.startActivity(emailIntent);
LogUtils.d(TAG, "邮件唤起成功:系统纯邮件客户端");
return;
}
Intent fallbackIntent = new Intent(Intent.ACTION_SEND);
fallbackIntent.setType(EMAIL_TYPE);
fallbackIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{EMAIL_ADDRESS});
fallbackIntent.putExtra(Intent.EXTRA_SUBJECT, EMAIL_TITLE);
fallbackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (fallbackIntent.resolveActivity(mItemContext.getPackageManager()) != null) {
mItemContext.startActivity(fallbackIntent);
LogUtils.d(TAG, "邮件唤起成功:通用邮件应用");
} else {
ToastUtils.show("未找到可发送邮件的应用");
LogUtils.w(TAG, "邮件唤起失败:无可用邮件相关应用");
}
}
}
// 网页跳转类功能控件 - 专属网页跳转逻辑
private class WebJumpFunctionItemView extends BaseFunctionItemView {
public WebJumpFunctionItemView(Context context, String title, String content, int iconRes) {
super(context, title, content, iconRes);
}
@Override
protected int getContentTextColor() {
return mItemContext.getResources().getColor(R.color.blue_normal);
}
@Override
public void onClick(View v) {
LogUtils.d(TAG, "WebJumpFunctionItemView onClick 触发网页跳转,地址:" + mContent);
if (mContent.isEmpty()) {
ToastUtils.show("跳转地址为空");
LogUtils.w(TAG, "网页跳转失败:地址为空");
return;
}
try {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(mContent));
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mItemContext.startActivity(browserIntent);
LogUtils.d(TAG, "网页跳转成功");
} catch (Exception e) {
LogUtils.d(TAG, "网页跳转失败,异常捕获", e);
ToastUtils.show("链接无法打开");
}
}
}
// 内部接口区(置于类末尾,逻辑闭环)
public interface OnRequestDevUserInfoAutofillListener {
void requestAutofill(EditText etDevUserName, EditText etDevUserPassword);
}
}

View File

@@ -0,0 +1,20 @@
<?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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/toolbar"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:id="@+id/aboutviewroot_ll"/>
</LinearLayout>