应用关于介绍窗口改造中。。。20260110_211051_986

This commit is contained in:
2026-01-10 21:11:34 +08:00
parent f203ef2b0b
commit df4ad5ac61
14 changed files with 985 additions and 2 deletions

View File

@@ -2,7 +2,7 @@
[![](https://jitpack.io/v/ZhanGSKen/APPBase.svg)](https://jitpack.io/#ZhanGSKen/APPBase)
#### 介绍
WinBoLL 应用开发基础类库。
WinBoLL 安卓手机端安卓应用开发基础类库。
#### 软件架构
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。

View File

@@ -48,5 +48,12 @@ 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,5 +1,5 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Jan 06 14:17:40 HKT 2026
#Sat Jan 10 12:31:14 GMT 2026
stageCount=3
libraryProject=libappbase
baseVersion=15.14

View File

@@ -0,0 +1,169 @@
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

@@ -0,0 +1,102 @@
package cc.winboll.studio.appbase;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/10 20:39
* @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 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();
// 设置工具栏
Toolbar toolbar = findViewById(R.id.toolbar);
setActionBar(toolbar);
getActionBar().setSubtitle(TAG);
getActionBar().setDisplayHomeAsUpEnabled(true);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish(); // 点击导航栏返回按钮,触发 finish()
}
});
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);
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;
}
}

View File

@@ -0,0 +1,373 @@
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

@@ -0,0 +1,160 @@
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

@@ -136,5 +136,32 @@ public class MainActivity extends Activity {
// 启动意图(唤起浏览器)
context.startActivity(intent);
}
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

@@ -0,0 +1,32 @@
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

@@ -0,0 +1,61 @@
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

@@ -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>

View File

@@ -59,6 +59,18 @@
android:onClick="onToastUtilsTest"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="关于应用"
android:textSize="16sp"
android:textColor="@android:color/white"
android:background="#81C7F5"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onAboutActivity"
android:layout_margin="10dp"/>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AboutView">
<attr name="app_name" format="string" />
<attr name="app_apkfoldername" format="string" />
<attr name="app_apkname" format="string" />
<attr name="app_gitname" format="string" />
<attr name="app_gitowner" format="string" />
<attr name="app_gitappbranch" format="string" />
<attr name="app_gitappsubprojectfolder" format="string" />
<attr name="appdescription" format="string" />
<attr name="appicon" format="reference" />
<attr name="is_adddebugtools" format="boolean" />
</declare-styleable>
</resources>

View File

@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">AppBase</string>
<string name="app_description">WinBoLL 安卓手机端安卓应用开发基础类库。</string>
<string name="app_normal">Click here is switch to Normal APP</string>
<string name="app_debug">Click here is switch to APP DEBUG</string>
<string name="gitea_home">GITEA HOME</string>
<string name="app_update">APP UPDATE</string>
</resources>