Compare commits

..

93 Commits

Author SHA1 Message Date
ZhanGSKen 627fa5359e <aes>APK 15.20.20 release Publish. 2026-07-17 19:18:48 +08:00
BigPickle 6e01f45174 <libaes>feat: 新增SMTP邮件发送启用开关,禁用时sendMail跳过操作 2026-07-17 19:15:50 +08:00
ZhanGSKen 07f74c9f8f <libaes>Library Release 15.20.19 2026-07-17 15:03:22 +08:00
ZhanGSKen 2e0b51b9d2 <aes>APK 15.20.19 release Publish. 2026-07-17 15:03:05 +08:00
BigPickle f91b5b1b6a <libaes><aes>feat: 新增SMTP邮件发送功能(QQ邮箱兼容)
libaes模块:
- 新增 APPMSGMailUtils 邮件发送工具类,基于JavaMail SMTP协议
- 新增 SMTPConfigView 邮件配置视图控件,支持服务器/端口/发件人/授权码/收件人配置
- 新增 view_smtp_config.xml 布局文件
- 添加 android-mail:1.6.7 / android-activation:1.6.7 依赖(替代javax.mail解决Android AWT兼容问题)
- 添加 INTERNET / ACCESS_NETWORK_STATE 权限

aes模块:
- 新增 SMTPSettingsActivity 邮件设置页面
- 新增 activity_smtp_settings.xml 布局文件
- MainActivity toolbar菜单添加"SMTP设置"入口
- AndroidManifest.xml 注册 SMTPSettingsActivity
- build.gradle 添加 packagingOptions 解决META-INF重复文件冲突
2026-07-17 10:39:59 +08:00
ZhanGSKen 6657b39823 <libaes>Library Release 15.20.18 2026-07-11 20:07:17 +08:00
ZhanGSKen ae5fba531e <aes>APK 15.20.18 release Publish. 2026-07-11 20:07:00 +08:00
MiMo b59c274032 feat: 状态栏颜色跟随主题 colorPrimaryDark 属性动态变化
- 修改 AESTheme 主题 android:statusBarColor 从固定颜色改为引用 ?attr/colorPrimaryDark
- 同步修改 MyDebugActivityTheme 主题状态栏颜色配置
- 覆盖日间模式和夜间模式 (values/ values-night/)
- 主题切换时状态栏颜色自动跟随各主题定义的 colorPrimaryDark 值

修改文件:
  libaes/src/main/res/values/styles.xml
  libaes/src/main/res/values-night/styles.xml
  aes/src/main/res/values/styles.xml
  aes/src/main/res/values-night/styles.xml
2026-07-11 20:03:05 +08:00
ZhanGSKen 0c86530ca6 <libaes>Library Release 15.20.17 2026-06-29 11:29:33 +08:00
ZhanGSKen 9431dbb930 <aes>APK 15.20.17 release Publish. 2026-06-29 11:28:11 +08:00
GPT-OSS_20B f83295f64f feat: update libappbase dependency to latest 15.20.34
* Incremented libappbase version from 15.20.33 to 15.20.34 in libaes module.
  The latest release of libappbase is 15.20.34 and includes bug fixes
  for Android API 30 compatibility and several performance improvements.
* The change keeps Java 7 source/target levels, compileSdk/targetSdk
  unchanged and is fully backward‑compatible.
* No other project files were modified.
2026-06-29 11:24:07 +08:00
ZhanGSKen a71dd7d399 更新Git仓库地址 2026-06-24 08:44:36 +08:00
ZhanGSKen 3d48cd39fc <libaes>Library Release 15.20.16 2026-06-24 04:26:08 +08:00
ZhanGSKen 8f7fd504e8 <aes>APK 15.20.16 release Publish. 2026-06-24 04:25:55 +08:00
ZhanGSKen b20f53d06e 更新类库,编译调试。 2026-06-24 04:22:50 +08:00
ZhanGSKen 0a174f531f 移除米盟SDK依赖,统一类库使用https://jitpack.io/仓库 2026-06-08 21:20:10 +08:00
Studio 7d3b83b522 <libaes>Library Release 15.20.15 2026-06-03 20:29:53 +08:00
Studio 0ed6dbe6c2 <aes>APK 15.20.15 release Publish. 2026-06-03 20:29:39 +08:00
BigPickle 77c6776cf8 fix: 修复CrashHandler崩溃处理引用,改用CrashActivity直接调用
- App.java: CrashHandler.CrashActivity → CrashActivity,简化崩溃处理逻辑
- libaes/build.gradle: libappbase依赖版本 15.20.25 → 15.20.26
2026-06-03 20:27:13 +08:00
Studio f134b7c746 <libaes>Library Release 15.20.14 2026-06-03 07:07:35 +08:00
Studio 795593b3bf <aes>APK 15.20.14 release Publish. 2026-06-03 07:07:27 +08:00
Studio cda173fa9e <libaes>Library Release 15.20.13 2026-06-03 07:05:51 +08:00
Studio fe57aee469 <aes>APK 15.20.13 release Publish. 2026-06-03 07:05:34 +08:00
ZhanGSKen 3fc0883e0e 源码整理,更新类库 2026-06-03 07:03:06 +08:00
ZhanGSKen b162eb1a4e 优化应用创建函数,提高应用调试能力 2026-06-03 07:00:58 +08:00
STUDIO fdd8b64718 <libaes>Library Release 15.20.12 2026-06-02 08:54:36 +08:00
STUDIO 8d4fd95ff2 <aes>APK 15.20.12 release Publish. 2026-06-02 08:54:20 +08:00
ZhanGSKen 3b31a6a155 fix: AToolbar/ASupportToolbar 暗黑模式主题适配
AESAToolbar/AESASupportToolbar: @color 改为 ?attr 引用,
使渐变色跟随当前主题变体而非固定静态色。
所有主题暗黑模式配色采用头尾对调策略:
primary 保持不变,primaryDark ↔ accent 互换。
同步更新 values-night/colors.xml 静态颜色。
2026-06-02 08:50:35 +08:00
STUDIO 9eb873332c <libaes>Library Release 15.20.11 2026-06-02 02:23:32 +08:00
STUDIO 1b07a72236 <aes>APK 15.20.11 release Publish. 2026-06-02 02:23:15 +08:00
ZhanGSKen 8328e44b86 黑白主题着重提升黑色的专注感官,减少视觉流失感官色彩。 2026-06-02 02:18:42 +08:00
ZhanGSKen 981a8dd8cd fix: AESThemeUtil.init(null) NullPointerException 闪退修复
AESThemeUtil.init(null) 传入 null 时,Java 值传递导致
fillThemeStyleIDList 内部 new ArrayList 仅赋值给局部变量,
调用方 themeStyleIDList 仍为 null,随后的 addAll(null) 抛 NPE。

在 init() 中先创建新 ArrayList 再传入 fillThemeStyleIDList 以修复。
2026-06-02 01:59:15 +08:00
ZhanGSKen 6bc3cbe446 Merge branch 'aes' into merge 2026-06-02 01:54:28 +08:00
ZhanGSKen 79e42e72af 移除winboll项目 2026-06-02 01:54:03 +08:00
ZhanGSKen b59076d470 提交AESUtils init 初始化方案 2026-06-02 00:39:17 +08:00
qinglong 9e9486b488 合并模块WinBoLL 同步最新时间标签winboll-v15.20.4 2026-06-01 21:00:01 +08:00
qinglong 4b8967b253 合并模块WinBoLL 同步最新时间标签winboll-v15.20.3 2026-05-31 21:00:02 +08:00
ZhanGSKen 30fbf4f97e 主题功能重构,AESThemeUtil.init函数初始化未设置。 2026-05-28 21:27:13 +08:00
qinglong b1059c3f46 合并模块AES 同步最新时间标签aes-v15.20.10 2026-05-27 20:26:41 +08:00
ZhanGSKen bc882c5f80 <libaes>Library Release 15.20.10 2026-05-27 20:22:57 +08:00
ZhanGSKen 8ae36effdd <aes>APK 15.20.10 release Publish. 2026-05-27 20:22:45 +08:00
BigPickle 0cc9b0871e fix: ASupportToolbar/AToolbar 控件颜色不跟随主题切换
- AESASupportToolbar/AESAToolbar 样式的颜色引用从 @color/ 改为
  ?attr/,使其通过主题属性解析,随主题切换而变化
- AESTheme 基主题新增 colorPrimary/colorPrimaryDark/colorAccent
  显式定义,保持默认绿色外观不变
2026-05-27 20:19:54 +08:00
ZhanGSKen d13c44c666 菜单排列重组 2026-05-27 19:35:01 +08:00
ZhanGSKen f786d06682 Merge branch 'aes' into merge 2026-05-27 15:21:52 +08:00
ZhanGSKen 168d2e2d60 Merge remote-tracking branch 'origin/projects_keeper_tag' into merge 2026-05-27 15:21:47 +08:00
ZhanGSKen f303a77e28 更新基础类库 2026-05-27 15:16:12 +08:00
qinglong f3d723fbee 合并模块APPBase 同步最新时间标签appbase-v15.20.22 2026-05-27 15:00:01 +08:00
ZhanGSKen 04b5d062aa Merge branch 'aes' into merge 2026-05-24 10:59:01 +08:00
ZhanGSKen fce2bf2fb1 Merge remote-tracking branch 'origin/projects_keeper_tag' into merge 2026-05-24 10:58:54 +08:00
qinglong e3c30ea9a3 合并模块AES 同步最新时间标签aes-v15.20.9 2026-05-24 10:49:26 +08:00
ZhanGSKen 48a592cbd2 <libaes>Library Release 15.20.9 2026-05-24 10:49:00 +08:00
ZhanGSKen 5fcf0de951 <aes>APK 15.20.9 release Publish. 2026-05-24 10:48:48 +08:00
ZhanGSKen f027158d07 更新APPBase类库 2026-05-24 10:46:55 +08:00
qinglong ab4cef21f0 合并模块APPBase 同步最新时间标签appbase-v15.20.21 2026-05-24 10:00:01 +08:00
QingLong 2754a2ad7c 合并模块AES 同步最新时间标签aes-v15.20.8 2026-05-20 21:00:01 +08:00
ZhanGSKen 852f3d6cd8 <libaes>Library Release 15.20.8 2026-05-20 20:05:04 +08:00
ZhanGSKen c2cff1a2bb <aes>APK 15.20.8 release Publish. 2026-05-20 20:04:49 +08:00
ZhanGSKen 98dd2a60b8 添加网站https://jitpack.io接口编译配置 2026-05-20 20:02:37 +08:00
ZhanGSKen 25e966099f Merge remote-tracking branch 'origin/projects_keeper_tag' into aes 2026-05-20 19:56:45 +08:00
QingLong 8876896cbc 合并模块APPBase 同步最新时间标签appbase-v15.20.20 2026-05-20 17:00:01 +08:00
QingLong 28e6a8ee78 合并模块APPBase 同步最新时间标签appbase-v15.20.19 2026-05-20 15:00:01 +08:00
ZhanGSKen 0a69b54dd0 Merge branch 'aes' into merge 2026-05-20 12:09:20 +08:00
ZhanGSKen e87513b179 Merge branch 'projects_keeper_tag' into merge 2026-05-20 12:08:49 +08:00
ZhanGSKen 186b1656a7 更新联系邮箱 2026-05-20 11:19:18 +08:00
QingLong 7d872fd14c 合并模块APPBase 同步最新时间标签appbase-v15.20.18 2026-05-20 04:00:01 +08:00
ZhanGSKen 4ce22e8750 更换APPBase类库 2026-05-20 03:19:52 +08:00
QingLong d6fab2133f 合并模块AES 同步最新时间标签aes-v15.20.7 2026-05-19 21:00:01 +08:00
QingLong 45821b8daa 合并模块APPBase 同步最新时间标签appbase-v15.20.17 2026-05-19 21:00:01 +08:00
ZhanGSKen 5c8505dcbd <libaes>Library Release 15.20.7 2026-05-19 20:39:08 +08:00
ZhanGSKen 9c3963822e <aes>APK 15.20.7 release Publish. 2026-05-19 20:38:13 +08:00
ZhanGSKen 30532f8e8d 版本号对齐APPBase。 2026-05-19 20:26:18 +08:00
ZhanGSKen 2b98149e0f Merge branch 'aes' into merge 2026-05-19 19:45:12 +08:00
ZhanGSKen 80d9b764ee Merge branch 'projects_keeper_tag' into merge 2026-05-19 19:44:19 +08:00
ZhanGSKen d4f24a9b0c Merge branch 'aes' into merge 2026-05-19 19:30:17 +08:00
ZhanGSKen f357e353ee Merge branch 'projects_keeper_tag' into merge 2026-05-19 19:30:12 +08:00
QingLong edd312a525 合并模块APPBase 同步最新时间标签appbase-v15.20.16 2026-05-19 19:00:01 +08:00
QingLong aec680e36e 合并模块AES 同步最新时间标签aes-v15.20.6 2026-05-19 01:53:02 +08:00
ZhanGSKen 937f90aa07 <libaes>Library Release 15.20.6 2026-05-19 01:52:54 +08:00
ZhanGSKen 643e91bd71 <aes>APK 15.20.6 release Publish. 2026-05-19 01:52:47 +08:00
QingLong 1c6f7783e5 合并模块AES 同步最新时间标签aes-v15.20.5 2026-05-19 01:49:06 +08:00
ZhanGSKen f4c6cea623 <libaes>Library Release 15.20.5 2026-05-19 01:48:56 +08:00
ZhanGSKen a0309ce98a <aes>APK 15.20.5 release Publish. 2026-05-19 01:48:43 +08:00
ZhanGSKen 931671d85a <libaes>Library Release 15.20.4 2026-05-17 20:53:51 +08:00
ZhanGSKen 580fa87773 <aes>APK 15.20.4 release Publish. 2026-05-17 20:53:39 +08:00
LaizyBoy a26276e169 fix: 将libaes模块debugTextColor从红色改为灰色
- libaes普通模式debugTextColor改为#FF808080(灰色)
- libaes深色模式debugTextColor改为#FFAAAAAA(浅灰)
2026-05-17 20:49:19 +08:00
LaizyBoy a3bf1087aa fix: 修复libaes模块Java 7兼容性问题及XML注释语法错误
- 为libaes添加compileOptions,指定Java 7源码与目标兼容级别
- 修复view_adscontrol.xml中XML注释包含非法"--"及缺少关闭符的问题
- 更新aes与libaes的build.properties编译计数
2026-05-17 20:30:41 +08:00
ZhanGSKen 1e5295df95 Merge keeper tag branch sources. 2026-05-17 20:15:50 +08:00
ZhanGSKen 8cb9ea5cae 清理源码冗余模块 2026-05-17 20:07:28 +08:00
ZhanGSKen 8006a05749 恢复winboll/winboll分支的基础类库资料,以便项目初始化时的基础控件使用的分析辅助。 2026-05-13 20:52:54 +08:00
ZhanGSKen 3c9a6ec884 修改示例模板的编译配置文件,设置版本号模板。 2026-05-13 20:21:55 +08:00
ZhanGSKen 8b47043053 更新 LICENSE-Private-Demo
字符更新
2026-05-13 19:51:47 +08:00
ZhanGSKen 202eeed72b 更新 LICENSE-Private-Demo_docs/LICENSE-Private-Demo
字符更新
2026-05-13 19:48:38 +08:00
ZhanGSKen 4511bf8730 添加WinBoLL-Demo使用的WinBoLL基础类库的基线型号配置文件。整理创建winboll衍生分支所要具备的基础文件。 2026-05-13 19:25:51 +08:00
110 changed files with 1754 additions and 5357 deletions
+2 -44
View File
@@ -97,47 +97,5 @@ lint-results.html
## WinBoLL 基础应用(避免上传敏感配置)
/winboll.properties
/local.properties
/settings.gradle
/gradle.properties
## WinBoLL 项目配置
#.git
#.gitignore
#.gitmodules
#.gradle
#.winboll
#GenKeyStore
#LICENSE
#LICENSE-Private-Demo
#LICENSE-Private-Demo_docs
#README.md
#aes
#appbase
appkey.jks
appkey.keystore
autonfc
#build.gradle
contacts
debugtemp
gallery
gpsrelaysentinel
#gradle
gradle.properties
#gradle.properties-android-demo
#gradle.properties-androidx-demo
#gradlew
#libaes
#libappbase
libdebugtemp
libgpsrelaysentinel
#libwinboll
local.properties
#local.properties-demo
mymessagemanager
positions
powerbell
settings.gradle
#settings.gradle-demo
#winboll
winboll.properties
#winboll.properties-demo
#/settings.gradle
#/gradle.properties
+1
View File
@@ -0,0 +1 @@
15.20
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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
http://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.
+14 -14
View File
@@ -6,11 +6,11 @@
## 核心声明
本文档**唯一核心设计目的**通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
本文档**唯一核心设计目的**: 通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
## 一、文件宗旨与风险防控说明
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标:
1. 严格隔离公共开源分支与私有开发分支,通过授权文件标记实现分支属性一眼可辨,杜绝人为操作混淆
@@ -24,7 +24,7 @@
### 1. 公共开源分支唯一标识
**文件名LICENSE**
**文件名: LICENSE**
- 仅允许存在于公共主流分支 `winboll` 及官方公共衍生分支
@@ -34,7 +34,7 @@
### 2. 私有开发分支唯一标识
**文件名LICENSE-Private**
**文件名: LICENSE-Private**
- 仅允许存在于私有开发分支,**绝对禁止出现在公共 ****`winboll`**** 分支**
@@ -44,15 +44,15 @@
## 三、分支管理与合并风控规则(强制遵守)
1. **公共主流分支**固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
1. **公共主流分支**: 固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
2. **私有开发分支**统一从 `winboll` 分支检出,命名固定格式为 `private-demo-*`,与公共分支物理隔离。
2. **私有开发分支**: 统一从 `winboll` 分支检出,命名固定格式为 `private-demo-*`,与公共分支物理隔离。
3. **核心合并风控铁则**
- 私有分支 → 公共分支**永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
- 私有分支 → 公共分支: **永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
- 公共分支 → 私有分支允许正常拉取、同步上游更新,不影响私有开发迭代
- 公共分支 → 私有分支: 允许正常拉取、同步上游更新,不影响私有开发迭代
4. 所有仓库提交者、合并操作者,均视为已阅读并完全认可本规则,**人为执行私有分支向公共分支的合并操作,由操作人承担全部代码泄露、合规违约、项目安全风险**。
@@ -68,7 +68,7 @@
4. 将本规范文件 `LICENSE-Private-Demo` 复制并重命名为 `LICENSE-Private`,作为私有分支生效授权文件。
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**:
> 初始化私有开发分支,已切换私有授权文件,本分支禁止任何人为合并、推送至 winboll 公共分支
>
@@ -84,14 +84,14 @@
3. 仓库管理员需严格校验合并请求的分支标识与授权文件,发现带有 `LICENSE-Private` 标记的分支申请合并至公共分支,一律直接拒绝,并记录操作人信息。
4. 分支属性校验以根目录授权文件为唯一标准只要分支内存在 `LICENSE-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
4. 分支属性校验以根目录授权文件为唯一标准: 只要分支内存在 `LICENSE-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
## 六、分支状态校验与异常处理
- 合规公共分支仅存在 `LICENSE`,无 `LICENSE-Private`
- 合规公共分支: 仅存在 `LICENSE`,无 `LICENSE-Private`
- 合规私有分支仅存在 `LICENSE-Private`,无 `LICENSE`
- 合规私有分支: 仅存在 `LICENSE-Private`,无 `LICENSE`
- 异常状态两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
- 异常状态: 两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
> (注文档部分内容可能由 AI 生成)
> (注: 文档部分内容可能由 AI 生成)
+14 -14
View File
@@ -6,11 +6,11 @@
## 核心声明
本文档**唯一核心设计目的**通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
本文档**唯一核心设计目的**: 通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
## 一、文件宗旨与风险防控说明
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标:
1. 严格隔离公共开源分支与私有开发分支,通过授权文件标记实现分支属性一眼可辨,杜绝人为操作混淆
@@ -24,7 +24,7 @@
### 1. 公共开源分支唯一标识
**文件名LICENSE**
**文件名: LICENSE**
- 仅允许存在于公共主流分支 `winboll` 及官方公共衍生分支
@@ -34,7 +34,7 @@
### 2. 私有开发分支唯一标识
**文件名LICENSE-Private**
**文件名: LICENSE-Private**
- 仅允许存在于私有开发分支,**绝对禁止出现在公共 ****`winboll`**** 分支**
@@ -44,15 +44,15 @@
## 三、分支管理与合并风控规则(强制遵守)
1. **公共主流分支**固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
1. **公共主流分支**: 固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
2. **私有开发分支**统一从 `winboll` 分支检出,命名固定格式为 `private-demo-*`,与公共分支物理隔离。
2. **私有开发分支**: 统一从 `winboll` 分支检出,命名固定格式为 `private-demo-*`,与公共分支物理隔离。
3. **核心合并风控铁则**
- 私有分支 → 公共分支**永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
- 私有分支 → 公共分支: **永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
- 公共分支 → 私有分支允许正常拉取、同步上游更新,不影响私有开发迭代
- 公共分支 → 私有分支: 允许正常拉取、同步上游更新,不影响私有开发迭代
4. 所有仓库提交者、合并操作者,均视为已阅读并完全认可本规则,**人为执行私有分支向公共分支的合并操作,由操作人承担全部代码泄露、合规违约、项目安全风险**。
@@ -68,7 +68,7 @@
4. 将本规范文件 `LICENSE-Private-Demo` 复制并重命名为 `LICENSE-Private`,作为私有分支生效授权文件。
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**:
> 初始化私有开发分支,已切换私有授权文件,本分支禁止任何人为合并、推送至 winboll 公共分支
>
@@ -84,14 +84,14 @@
3. 仓库管理员需严格校验合并请求的分支标识与授权文件,发现带有 `LICENSE-Private` 标记的分支申请合并至公共分支,一律直接拒绝,并记录操作人信息。
4. 分支属性校验以根目录授权文件为唯一标准只要分支内存在 `LICENSE-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
4. 分支属性校验以根目录授权文件为唯一标准: 只要分支内存在 `LICENSE-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
## 六、分支状态校验与异常处理
- 合规公共分支仅存在 `LICENSE`,无 `LICENSE-Private`
- 合规公共分支: 仅存在 `LICENSE`,无 `LICENSE-Private`
- 合规私有分支仅存在 `LICENSE-Private`,无 `LICENSE`
- 合规私有分支: 仅存在 `LICENSE-Private`,无 `LICENSE`
- 异常状态两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
- 异常状态: 两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
> (注文档部分内容可能由 AI 生成)
> (注: 文档部分内容可能由 AI 生成)
+5 -5
View File
@@ -9,15 +9,15 @@ WinBoLL 手机源码计划,旨在通过核心项目 WinBoLL 构建手机端与
#### **仓库类型:功能说明**
☆ 基础项目分支 WinBoLL:手机端安卓应用开发基础模板。
☆ 应用项目分支 APPBase、AES、PowerBell、Positions**:安卓应用单一管理系列项目。
☆ 源码汇总管理 Projects_Keeper**:各类分支源码合并存档,不适宜作为开发库使用。
☆ 源码汇总管理 OriginMaster**:各类分支源码合并存档,不适宜作为开发库使用。
### 3. 源码合并管理推送路线图
⚠️ **注意**:仅仅展示不同应用模块源码的综合管理路线。分支合并操作时,必须具备 Git 管理经验。
★ WinBoLL → APPBase → Projects_Keeper
★ WinBoLL → AES → Projects_Keeper
★ WinBoLL → PowerBell → Projects_Keeper
★ WinBoLL → Positions → Projects_Keeper
★ WinBoLL → APPBase → OriginMaster
★ WinBoLL → AES → OriginMaster
★ WinBoLL → PowerBell → OriginMaster
★ WinBoLL → Positions → OriginMaster
## 二、WinBoLL 项目核心信息
+1 -1
View File
@@ -20,7 +20,7 @@ WinBoLL AndroidX 可视化元素类库。
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
3. 提交代码 : ZhanGSKen(ZhanGSKen<ZhanGSKen@QQ.COM>)
4. 新建 Pull Request
+8 -4
View File
@@ -26,7 +26,10 @@ android {
applicationId "cc.winboll.studio.aes"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
//1. Android 官方规则
//-  versionCode  类型:int 整型
//- Java int 最大值:2147483647
versionCode 1520000
// versionName 更新后需要手动设置
// 项目模块目录的 build.gradle 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
@@ -35,11 +38,12 @@ android {
versionName = genVersionName("${versionName}")
}
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
exclude 'META-INF/NOTICE.md'
exclude 'META-INF/LICENSE.md'
}
}
dependencies {
+4 -4
View File
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun May 17 20:53:39 HKT 2026
stageCount=5
#Fri Jul 17 19:18:48 HKT 2026
stageCount=21
libraryProject=libaes
baseVersion=15.20
publishVersion=15.20.4
publishVersion=15.20.20
buildCount=0
baseBetaVersion=15.20.5
baseBetaVersion=15.20.21
+2
View File
@@ -40,6 +40,8 @@
<activity android:name=".AboutActivity"/>
<activity android:name=".SMTPSettingsActivity" android:exported="true"/>
</application>
</manifest>
@@ -65,7 +65,7 @@ public class AboutActivity extends BaseWinBoLLActivity {
appInfo.setAppName(getString(R.string.app_name));
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("AES");
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
@@ -5,10 +5,14 @@ package cc.winboll.studio.aes;
* @Date 2024/06/13 19:03:58
* @Describe AES应用类
*/
import android.view.Gravity;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.CrashActivity;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libappbase.utils.CrashHandleNotifyUtils;
import java.io.PrintWriter;
import java.io.StringWriter;
public class App extends GlobalApplication {
@@ -17,13 +21,25 @@ public class App extends GlobalApplication {
@Override
public void onCreate() {
super.onCreate();
setIsDebugging(BuildConfig.DEBUG);
//setIsDebugging(false);
WinBoLLActivityManager.init(this);
try {
super.onCreate();
ToastUtils.init(this);
WinBoLLActivityManager.init(this);
AESThemeUtil.init(null);
} catch (Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.close();
String stackTraceStr = sw.toString();
CrashHandleNotifyUtils.handleUncaughtException(
this,
getPackageName(),
stackTraceStr,
CrashActivity.class
);
}
// 初始化 Toast 框架
ToastUtils.init(this);
}
@Override
@@ -84,11 +84,12 @@ public class MainActivity extends DrawerFragmentActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.toolbar_main, menu);
// if(App.isDebugging()) {
// getMenuInflater().inflate(cc.winboll.studio.libaes.R.menu.toolbar_studio_debug, menu);
// }
return super.onCreateOptionsMenu(menu);
return true;
}
@Override
@@ -182,6 +183,8 @@ public class MainActivity extends DrawerFragmentActivity {
} else if (nItemId == R.id.item_settings) {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
} else if (nItemId == R.id.item_smtp_settings) {
WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, SMTPSettingsActivity.class);
} else if (nItemId == R.id.item_about) {
// Intent intent = new Intent(this, AboutActivity.class);
// startActivity(intent);
@@ -0,0 +1,51 @@
package cc.winboll.studio.aes;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2026/07/16
* @Describe SMTP邮件设置窗口,使用SMTPConfigView控件进行配置操作
*/
public class SMTPSettingsActivity extends BaseWinBoLLActivity {
public static final String TAG = "SMTPSettingsActivity";
private Toolbar mToolbar;
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_smtp_settings);
initToolbar();
}
private void initToolbar() {
LogUtils.d(TAG, "initToolbar() 开始初始化");
mToolbar = findViewById(R.id.toolbar);
if (mToolbar == null) {
LogUtils.e(TAG, "initToolbar() | Toolbar未找到");
return;
}
setSupportActionBar(mToolbar);
mToolbar.setSubtitle(getTag());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "导航栏 点击返回按钮");
finish();
}
});
LogUtils.d(TAG, "initToolbar() 配置完成");
}
}
@@ -1,11 +1,7 @@
package cc.winboll.studio.aes;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.winboll.studio.libaes.views.ADsControlView;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
@@ -20,7 +16,7 @@ public class SettingsActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
ADsControlView adsControlView = (ADsControlView) findViewById(R.id.ads_control_view);
//ADsControlView adsControlView = (ADsControlView) findViewById(R.id.ads_control_view);
// adsControlView.setOnAdsModeSelectedListener(new ADsControlView.OnAdsModeSelectedListener() {
// @Override
@@ -6,12 +6,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent" android:background="@drawable/bg_container_border">
<cc.winboll.studio.libaes.views.ADsControlView
android:id="@+id/ads_control_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_frame"
android:padding="10dp"/>
</LinearLayout>
@@ -1,22 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?attr/activityBackgroundColor">
android:background="@drawable/bg_container_border">
<androidx.appcompat.widget.Toolbar
<cc.winboll.studio.libaes.views.ASupportToolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<androidx.recyclerview.widget.RecyclerView
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="10dp"
android:id="@+id/task_scheduler_recycler_view"/>
android:layout_height="match_parent">
<cc.winboll.studio.libaes.views.SMTPConfigView
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</ScrollView>
</LinearLayout>
+3
View File
@@ -35,6 +35,9 @@
<item
android:id="@+id/item_settings"
android:title="Settings"/>
<item
android:id="@+id/item_smtp_settings"
android:title="SMTP设置"/>
<item
android:id="@+id/item_about"
android:title="About"/>
+1 -1
View File
@@ -5,7 +5,7 @@
</style>
<style name="MyDebugActivityTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="android:statusBarColor">?attr/colorPrimaryDark</item>
<item name="colorTittle">@color/mainWindowTextColor</item>
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
<item name="colorText">@color/debugTextColor</item>
+1 -1
View File
@@ -5,7 +5,7 @@
</style>
<style name="MyDebugActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="android:statusBarColor">?attr/colorPrimaryDark</item>
<item name="colorTittle">@color/mainWindowTextColor</item>
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
<item name="colorText">@color/debugTextColor</item>
+1 -1
View File
@@ -20,7 +20,7 @@ WinBoLL 安卓手机端安卓应用开发基础类库。
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
3. 提交代码 : ZhanGSKen(ZhanGSKen<ZhanGSKen@QQ.COM>)
4. 新建 Pull Request
+4 -1
View File
@@ -26,7 +26,10 @@ android {
applicationId "cc.winboll.studio.appbase"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
//1. Android 官方规则
//-  versionCode  类型:int 整型
//- Java int 最大值:2147483647
versionCode 1520000
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
+4 -4
View File
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun May 17 16:16:36 HKT 2026
stageCount=16
#Wed May 27 14:51:29 HKT 2026
stageCount=23
libraryProject=libappbase
baseVersion=15.20
publishVersion=15.20.15
publishVersion=15.20.22
buildCount=0
baseBetaVersion=15.20.16
baseBetaVersion=15.20.23
@@ -37,7 +37,7 @@ public class AboutActivity extends Activity {
appInfo.setAppName("APPBase");
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitName("APPBase");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
@@ -22,12 +22,6 @@ public class App extends GlobalApplication {
@Override
public void onCreate() {
super.onCreate();
// 如果应用不在调试状态,就根据编译类型设置调试状态
if (isDebugging() != true) {
setIsDebugging(BuildConfig.DEBUG);
}
// release 版调试码
//setIsDebugging(!BuildConfig.DEBUG);
// 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用)
ToastUtils.init(getApplicationContext());
@@ -2,8 +2,8 @@ package cc.winboll.studio.appbase.model;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
/**
+21
View File
@@ -0,0 +1,21 @@
# 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
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# 保持与旧版Gradle插件的兼容
android.disableAutomaticComponentCreation=true
Vendored Regular → Executable
View File
+8 -8
View File
@@ -20,15 +20,11 @@ android {
}
}
// 米盟 SDK
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
}
dependencies {
@@ -57,7 +53,7 @@ dependencies {
//api 'androidx.fragment:fragment:1.1.0'
// 米盟
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//注意:以下5个库必须要引入
//implementation 'androidx.appcompat:appcompat:1.4.1'
api 'androidx.recyclerview:recyclerview:1.0.0'
@@ -66,9 +62,13 @@ dependencies {
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// WinBoLL库 nexus.winboll.cc 地址
api 'cc.winboll.studio:libappbase:15.20.9'
api 'cc.winboll.studio:libappbase:15.20.34'
// 备用库 jitpack.io 地址
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.15.3'
//api 'com.github.ZhanGSKen:libappbase:appbase-v15.20.33'
// JavaMail SMTP (Android兼容版)
api 'com.sun.mail:android-mail:1.6.7'
api 'com.sun.mail:android-activation:1.6.7'
api fileTree(dir: 'libs', include: ['*.jar'])
}
+4 -4
View File
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun May 17 20:53:39 HKT 2026
stageCount=5
#Fri Jul 17 19:18:48 HKT 2026
stageCount=21
libraryProject=libaes
baseVersion=15.20
publishVersion=15.20.4
publishVersion=15.20.20
buildCount=0
baseBetaVersion=15.20.5
baseBetaVersion=15.20.21
+2
View File
@@ -5,6 +5,8 @@
package="cc.winboll.studio.libaes">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<application
@@ -24,18 +24,18 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import cc.winboll.studio.libaes.DrawerMenuDataAdapter;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.AESThemeBean;
import cc.winboll.studio.libaes.models.DrawerMenuBean;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.DevelopUtils;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libaes.views.ADrawerMenuListView;
import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import com.baoyz.widget.PullRefreshLayout;
import java.util.ArrayList;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
public abstract class DrawerFragmentActivity extends AppCompatActivity implements IWinBoLLActivity, AdapterView.OnItemClickListener {
@@ -44,7 +44,6 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
static final String SHAREDPREFERENCES_NAME = "SHAREDPREFERENCES_NAME";
static final String DRAWER_THEME_TYPE = "DRAWER_THEME_TYPE";
//protected Context mContext;
ActivityType mActivityType;
ActionBarDrawerToggle mActionBarDrawerToggle;
DrawerLayout mDrawerLayout;
@@ -59,13 +58,14 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
public enum ActivityType { Main, Secondary }
protected volatile AESThemeBean.ThemeType mThemeType;
protected ArrayList<DrawerMenuBean> malDrawerMenuItem;
abstract protected ActivityType initActivityType();
//abstract protected View initContentView(LayoutInflater inflater, ViewGroup rootView);
@Override
protected void onCreate(Bundle savedInstanceState) {
// 替换:使用工具类统一应用主题
AESThemeUtil.applyAppCompatTheme(this);
mThemeType = AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
super.onCreate(savedInstanceState);
WinBoLLActivityManager.getInstance().add(this);
mActivityType = initActivityType();
@@ -78,53 +78,32 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onDestroy() {
WinBoLLActivityManager.getInstance().registeRemove(this);
WinBoLLActivityManager.getInstance().registeRemove(this);
super.onDestroy();
// 修复:释放广告资源,避免内存泄漏
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
if (adsBannerView != null) {
adsBannerView.releaseAdResources();
}
// 修复:释放广告资源,避免内存泄漏
// ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
// if (adsBannerView != null) {
// adsBannerView.releaseAdResources();
// }
}
/*@Override
public Intent getIntent() {
// TODO: Implement this method
return super.getIntent();
}
public Context getContext() {
return this.mContext;
}*/
@Override
public MenuInflater getMenuInflater() {
// TODO: Implement this method
return super.getMenuInflater();
}
/*public void setSubtitle(CharSequence context) {
// TODO: Implement this method
getSupportActionBar().setSubtitle(context);
}*/
@Override
public void recreate() {
super.recreate();
}
/*@Override
public boolean moveTaskToBack(boolean nonRoot) {
return super.moveTaskToBack(nonRoot);
}*/
@Override
public void startActivity(Intent intent) {
super.startActivity(intent);
@@ -135,26 +114,6 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
super.startActivityForResult(intent, requestCode, options);
}
/*@Override
public FragmentManager getSupportFragmentManager() {
return super.getSupportFragmentManager();
}
public void setSubtitle(int resId) {
// TODO: Implement this method
getSupportActionBar().setSubtitle(resId);
}
public void setTitle(CharSequence context) {
// TODO: Implement this method
getSupportActionBar().setTitle(context);
}
public void setTitle(int resId) {
// TODO: Implement this method
getSupportActionBar().setTitle(resId);
}*/
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return super.getSharedPreferences(name, mode);
@@ -162,7 +121,6 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
@Override
public Context getApplicationContext() {
// TODO: Implement this method
return super.getApplicationContext();
}
@@ -173,25 +131,27 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (AESThemeUtil.onAppThemeItemSelected(this, item)) {
// 替换为 DrawerFragmentActivity 专属点击处理方法
if (AESThemeUtil.onWinBoLLThemeItemSelected(this, item)) {
recreate();
} if (DevelopUtils.onDevelopItemSelected(this, item)) {
LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId()));
} else {
return super.onOptionsItemSelected(item);
}
}
if (DevelopUtils.onDevelopItemSelected(this, item)) {
LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId()));
} else {
return super.onOptionsItemSelected(item);
}
return true;
return true;
}
@Override
protected void onResume() {
super.onResume();
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
if (adsBannerView != null) {
adsBannerView.resumeADs(DrawerFragmentActivity.this);
}
// ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
// if (adsBannerView != null) {
// adsBannerView.resumeADs(DrawerFragmentActivity.this);
// }
}
void initRootView() {
@@ -213,14 +173,13 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
mPullRefreshLayout = findViewById(R.id.activitydrawerfragmentPullRefreshLayout1);
mPullRefreshLayout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//LogUtils.d(TAG, "onRefresh");
reinitDrawerMenuItemList(malDrawerMenuItem);
mDrawerMenuDataAdapter.notifyDataSetChanged();
mPullRefreshLayout.setRefreshing(false);
}
});
@Override
public void onRefresh() {
reinitDrawerMenuItemList(malDrawerMenuItem);
mDrawerMenuDataAdapter.notifyDataSetChanged();
mPullRefreshLayout.setRefreshing(false);
}
});
malDrawerMenuItem = new ArrayList<DrawerMenuBean>();
@@ -236,68 +195,51 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
mActionBarDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.lib_name, R.string.lib_name) {
@Override
public void onDrawerOpened(View drawerView) {//完全打开时触发
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
mIsDrawerOpened = true;
mIsDrawerOpening = false;
//Toast.makeText(MainActivity.this,"onDrawerOpened",Toast.LENGTH_SHORT).show();
}
@Override
public void onDrawerClosed(View drawerView) {//完全关闭时触发
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
mIsDrawerOpened = false;
mIsDrawerClosing = false;
//Toast.makeText(MainActivity.this,"onDrawerClosed",Toast.LENGTH_SHORT).show();
}
/**
* 当抽屉被滑动的时候调用此方法
* slideOffset表示 滑动的幅度(0-1
*/
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
}
/**
* 当抽屉滑动状态改变的时候被调用
* 状态值是STATE_IDLE(闲置--0, STATE_DRAGGING(拖拽的--1, STATE_SETTLING(固定--2)中之一。
*具体状态可以慢慢调试
*/
@Override
public void onDrawerStateChanged(int newState) {
super.onDrawerStateChanged(newState);
}
};
//设置显示旋转菜单
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
//通过下面这句实现toolbar和Drawer的联动:如果没有这行代码,箭头是不会随着侧滑菜单的开关而变换的(或者没有箭头),
// 可以尝试一下,不影响正常侧滑
mActionBarDrawerToggle.syncState();
mDrawerLayout.setDrawerListener(mActionBarDrawerToggle);
//去掉侧滑的默认图标(动画箭头图标),也可以选择不去,
//不去的话把这一行注释掉或者改成true,然后把toolbar.setNavigationIcon注释掉就行了
//mActionBarDrawerToggle.setDrawerIndicatorEnabled(false);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mIsDrawerOpened || mIsDrawerOpening) {
mIsDrawerClosing = true;
mIsDrawerOpening = false;
mDrawerLayout.closeDrawer(mPullRefreshLayout);
return;
}
if (!mIsDrawerOpened || mIsDrawerClosing) {
mIsDrawerOpening = true;
mIsDrawerClosing = false;
mDrawerLayout.openDrawer(mPullRefreshLayout);
return;
}
}
});
@Override
public void onClick(View v) {
if (mIsDrawerOpened || mIsDrawerOpening) {
mIsDrawerClosing = true;
mIsDrawerOpening = false;
mDrawerLayout.closeDrawer(mPullRefreshLayout);
return;
}
if (!mIsDrawerOpened || mIsDrawerClosing) {
mIsDrawerOpening = true;
mIsDrawerClosing = false;
mDrawerLayout.openDrawer(mPullRefreshLayout);
return;
}
}
});
initDrawerMenuItemList(malDrawerMenuItem);
}
@@ -305,12 +247,11 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
void initSecondaryRootView() {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//LogUtils.d(TAG, "onClick " + Integer.toString(v.getId()));
finish();
}
});
@Override
public void onClick(View v) {
finish();
}
});
}
public <T extends Fragment> int removeFragment(T fragment) {
@@ -375,13 +316,13 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (mActivityType == ActivityType.Main) {
// 主题菜单
AESThemeUtil.inflateMenu(this, menu);
// 调试工具菜单
if (GlobalApplication.isDebugging()) {
DevelopUtils.inflateMenu(this, menu);
}
// 应用信息菜单
// 替换为兼容版菜单加载方法
AESThemeUtil.inflateCompatThemeMenu(this, menu);
// 调试工具菜单
if (GlobalApplication.isDebugging()) {
DevelopUtils.inflateMenu(this, menu);
}
// 应用信息菜单
getMenuInflater().inflate(R.menu.toolbar_drawerbase, menu);
}
return super.onCreateOptionsMenu(menu);
@@ -392,3 +333,4 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
super.onActivityResult(who, targetFragment, requestCode);
}
}
@@ -8,8 +8,9 @@ package cc.winboll.studio.libaes.models;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
import java.util.ArrayList;
public class AESThemeBean extends BaseBean {
@@ -41,13 +42,28 @@ public class AESThemeBean extends BaseBean {
return name;
}
}
public static void fillThemeStyleIDList(ArrayList<Integer> themeStyleIDList) {
if (themeStyleIDList == null) {
themeStyleIDList = new ArrayList<Integer>();
}
themeStyleIDList.clear();
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.AESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.DepthAESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.SkyAESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.GoldenAESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.BearingAESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.MemorAESTheme);
themeStyleIDList.add(cc.winboll.studio.libaes.R.style.TaoAESTheme);
}
// 保存当前主题
int currentThemeStyleID = getThemeStyleID(ThemeType.AES);
public AESThemeBean() {
}
public AESThemeBean(int currentThemeStyleID) {
this.currentThemeStyleID = currentThemeStyleID;
}
@@ -59,7 +75,7 @@ public class AESThemeBean extends BaseBean {
public int getCurrentThemeTypeID() {
return this.currentThemeStyleID;
}
@Override
public String getName() {
return AESThemeBean.class.getName();
@@ -74,8 +90,7 @@ public class AESThemeBean extends BaseBean {
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if(super.initObjectsFromJsonReader(jsonReader, name)) { return true; }
else{
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("currentThemeTypeID")) {
setCurrentThemeTypeID(jsonReader.nextInt());
} else {
@@ -90,7 +105,7 @@ public class AESThemeBean extends BaseBean {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if(!initObjectsFromJsonReader(jsonReader, name)) {
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
@@ -7,7 +7,7 @@ package cc.winboll.studio.libaes.models;
import android.content.Context;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
public class WinBoLLClientServiceBean extends BaseBean {
@@ -12,7 +12,6 @@ import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.views.ASupportToolbar;
import cc.winboll.studio.libappbase.LogUtils;
public class TestASupportToolbarActivity extends AppCompatActivity implements IWinBoLLActivity {
@@ -28,11 +27,12 @@ public class TestASupportToolbarActivity extends AppCompatActivity implements IW
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
LogUtils.d(TAG, "onCreate() start");
AESThemeUtil.applyAppTheme(this);
// 替换此处:原 applyAppTheme -> 新方法 applyAppCompatTheme
AESThemeUtil.applyAppCompatTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_testasupporttoolbar);
LogUtils.d(TAG, "setContentView() done");
@@ -45,3 +45,4 @@ public class TestASupportToolbarActivity extends AppCompatActivity implements IW
LogUtils.d(TAG, "onCreate() end");
}
}
@@ -14,10 +14,11 @@ import cc.winboll.studio.libaes.utils.AESThemeUtil;
public class TestAToolbarActivity extends Activity {
public static final String TAG = "TestAToolbarActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
AESThemeUtil.applyAppTheme(this);
// 原生Activity 使用 applyTheme
AESThemeUtil.applyTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_testatoolbar);
Toolbar toolbar = findViewById(R.id.activitytestatoolbarAToolbar1);
@@ -26,3 +27,4 @@ public class TestAToolbarActivity extends Activity {
}
}
@@ -13,199 +13,159 @@ import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
import cc.winboll.studio.libaes.models.AESThemeBean;
import java.util.ArrayList;
public class AESThemeUtil {
public static final String TAG = "AESThemeUtil";
private static final String SHAREDPREFERENCES_NAME = "SHAREDPREFERENCES_NAME";
private static final String DRAWER_THEME_TYPE = "DRAWER_THEME_TYPE";
static final String SHAREDPREFERENCES_NAME = "SHAREDPREFERENCES_NAME";
static final String DRAWER_THEME_TYPE = "DRAWER_THEME_TYPE";
// 私有静态集合,外部不可直接修改
private static ArrayList<Integer> themeStyleIDList = new ArrayList<>();
protected volatile AESThemeBean.ThemeType mThemeType;
// 移除无用实例成员 mThemeType,工具类不保留实例字段
public static <T extends Context> int getThemeTypeID(T context) {
AESThemeBean bean = AESThemeBean.loadBean(context, AESThemeBean.class);
return bean == null ? AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.AES): bean.getCurrentThemeTypeID();
/**
* 初始化主题样式ID集合
*/
public static void init(ArrayList<Integer> themeStyleIDList) {
if (themeStyleIDList == null) {
themeStyleIDList = new ArrayList<Integer>();
AESThemeBean.fillThemeStyleIDList(themeStyleIDList);
}
AESThemeUtil.themeStyleIDList.clear();
AESThemeUtil.themeStyleIDList.addAll(themeStyleIDList);
}
public static <T extends Context> void saveThemeStyleID(T context, int nThemeTypeID) {
AESThemeBean bean = new AESThemeBean(nThemeTypeID);
/**
* 获取当前主题样式ID
*/
public static int getThemeTypeID(Context context) {
AESThemeBean bean = AESThemeBean.loadBean(context, AESThemeBean.class);
return bean == null ? getThemeStyleID(AESThemeBean.ThemeType.AES) : bean.getCurrentThemeTypeID();
}
/**
* 保存主题样式ID
*/
public static void saveThemeStyleID(Context context, int themeTypeID) {
AESThemeBean bean = new AESThemeBean(themeTypeID);
AESThemeBean.saveBean(context, bean);
}
public static <T extends Activity> void applyAppTheme(T activity) {
// ====================== 应用主题 - 规范重载 ======================
/**
* 应用当前持久化主题(通用 Activity)
*/
public static void applyTheme(Activity activity) {
activity.setTheme(getThemeTypeID(activity));
}
public static <T extends AppCompatActivity> void applyAppCompatTheme(T activity) {
/**
* 应用指定主题(通用 Activity)
*/
public static void applyTheme(Activity activity, AESThemeBean.ThemeType themeType) {
activity.setTheme(getThemeStyleID(themeType));
}
/**
* 应用当前持久化主题(AppCompat 兼容 Activity
*/
public static void applyAppCompatTheme(AppCompatActivity activity) {
activity.setTheme(getThemeTypeID(activity));
}
/*public static <T extends WinBoLLActivity> void applyWinBoLLTheme(T activity) {
activity.setTheme(getThemeTypeID(activity.getApplicationContext()));
}*/
public static <T extends Activity> void applyAppTheme(Activity activity, AESThemeBean.ThemeType themeType) {
activity.setTheme(AESThemeBean.getThemeStyleID(themeType));
/**
* 应用指定主题(AppCompat 兼容 Activity
*/
public static void applyAppCompatTheme(AppCompatActivity activity, AESThemeBean.ThemeType themeType) {
activity.setTheme(getThemeStyleID(themeType));
}
public static <T extends AppCompatActivity> void applyAppCompatTheme(Activity activity, AESThemeBean.ThemeType themeType) {
activity.setTheme(AESThemeBean.getThemeStyleID(themeType));
}
/*public static <T extends WinBoLLActivity> void applyWinBoLLTheme(Activity activity, AESThemeBean.ThemeType themeType) {
activity.setTheme(AESThemeBean.getThemeStyleID(themeType));
}*/
public static <T extends Activity> void inflateMenu(T activity, Menu menu) {
// ====================== 加载菜单 ======================
/**
* 加载主题菜单(通用 Activity)
*/
public static void inflateThemeMenu(Activity activity, Menu menu) {
activity.getMenuInflater().inflate(R.menu.toolbar_apptheme, menu);
}
public static <T extends AppCompatActivity> void inflateCompatMenu(T activity, Menu menu) {
/**
* 加载主题菜单(AppCompat Activity
*/
public static void inflateCompatThemeMenu(AppCompatActivity activity, Menu menu) {
activity.getMenuInflater().inflate(R.menu.toolbar_apptheme, menu);
}
/*public static <T extends WinBoLLActivity> void inflateWinBoLLMenu(T activity, Menu menu) {
activity.getMenuInflater().inflate(R.menu.toolbar_apptheme, menu);
}*/
public static <T extends Activity> boolean onAppThemeItemSelected(T activity, MenuItem item) {
int nThemeStyleID;
if (R.id.item_depththeme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.DEPTH);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_skytheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.SKY);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_goldentheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.GOLDEN);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_bearingtheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.BEARING);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_memortheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.MEMOR);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_taotheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.TAO);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_defaulttheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.AES);
saveThemeStyleID(activity, nThemeStyleID);
return true;
// ====================== 菜单点击统一核心逻辑(消除重复代码) ======================
/**
* 主题菜单项点击统一处理
* @param context 上下文(用于持久化)
* @param item 点击的菜单项
* @return 是否消费点击事件
*/
public static boolean handleThemeMenuClick(Context context, MenuItem item) {
int themeStyleId;
int itemId = item.getItemId();
if (R.id.item_depththeme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.DEPTH);
} else if (R.id.item_skytheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.SKY);
} else if (R.id.item_goldentheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.GOLDEN);
} else if (R.id.item_bearingtheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.BEARING);
} else if (R.id.item_memortheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.MEMOR);
} else if (R.id.item_taotheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.TAO);
} else if (R.id.item_defaulttheme == itemId) {
themeStyleId = getThemeStyleID(AESThemeBean.ThemeType.AES);
} else {
return false;
}
return false;
saveThemeStyleID(context, themeStyleId);
return true;
}
public static <T extends AppCompatActivity> boolean onAppCompatThemeItemSelected(T activity, MenuItem item) {
int nThemeStyleID;
if (R.id.item_depththeme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.DEPTH);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_skytheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.SKY);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_goldentheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.GOLDEN);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_bearingtheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.BEARING);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_memortheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.MEMOR);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_taotheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.TAO);
saveThemeStyleID(activity, nThemeStyleID);
return true;
} else if (R.id.item_defaulttheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.AES);
saveThemeStyleID(activity, nThemeStyleID);
return true;
}
return false;
// 对外暴露不同 Activity 类型的入口,内部调用统一核心方法
public static boolean onThemeItemSelected(Activity activity, MenuItem item) {
return handleThemeMenuClick(activity, item);
}
public static <T extends AppCompatActivity> boolean onWinBoLLThemeItemSelected(T activity, MenuItem item) {
int nThemeStyleID;
if (R.id.item_depththeme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.DEPTH);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_skytheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.SKY);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_goldentheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.GOLDEN);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_bearingtheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.BEARING);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_memortheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.MEMOR);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_taotheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.TAO);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_defaulttheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.AES);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
}
return false;
public static boolean onAppCompatThemeItemSelected(AppCompatActivity activity, MenuItem item) {
return handleThemeMenuClick(activity, item);
}
public static <T extends DrawerFragmentActivity> boolean onWinBoLLThemeItemSelected(T activity, MenuItem item) {
int nThemeStyleID;
if (R.id.item_depththeme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.DEPTH);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_skytheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.SKY);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_goldentheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.GOLDEN);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_bearingtheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.BEARING);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_memortheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.MEMOR);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_taotheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.TAO);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
} else if (R.id.item_defaulttheme == item.getItemId()) {
nThemeStyleID = AESThemeBean.getThemeStyleID(AESThemeBean.ThemeType.AES);
saveThemeStyleID(activity.getApplicationContext(), nThemeStyleID);
return true;
}
public static boolean onWinBoLLThemeItemSelected(AppCompatActivity activity, MenuItem item) {
// 使用 Application 上下文保存,避免 Activity 泄漏
return handleThemeMenuClick(activity.getApplicationContext(), item);
}
return false;
public static boolean onWinBoLLThemeItemSelected(DrawerFragmentActivity activity, MenuItem item) {
return handleThemeMenuClick(activity.getApplicationContext(), item);
}
// ====================== 主题类型转换工具 ======================
/**
* 根据枚举获取对应样式ID
*/
public static int getThemeStyleID(AESThemeBean.ThemeType themeType) {
return themeStyleIDList.get(themeType.ordinal());
}
/**
* 根据样式ID反向获取主题枚举
*/
public static AESThemeBean.ThemeType getThemeStyleType(int themeStyleID) {
for (int i = 0; i < themeStyleIDList.size(); i++) {
if (themeStyleIDList.get(i) == themeStyleID) {
return AESThemeBean.ThemeType.values()[i];
}
}
return AESThemeBean.ThemeType.values()[0];
}
}
@@ -0,0 +1,182 @@
package cc.winboll.studio.libaes.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import cc.winboll.studio.libappbase.LogUtils;
import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2026/07/16
* @Describe APPMSG邮件发送工具类,使用QQ邮件SMTP服务发送邮件
*/
public class APPMSGMailUtils {
public static final String TAG = "APPMSGMailUtils";
//
// SharedPreferences 配置
//
private static final String SP_NAME = "appmsg_mail_config";
private static final String KEY_SMTP_SERVER = "smtp_server";
private static final String KEY_SMTP_PORT = "smtp_port";
private static final String KEY_SMTP_SENDER = "smtp_sender";
private static final String KEY_SMTP_AUTH_CODE = "smtp_auth_code";
private static final String KEY_SMTP_RECIPIENT = "smtp_recipient";
private static final String KEY_SMTP_ENABLED = "smtp_enabled";
private static final String DEFAULT_SMTP_SERVER = "smtp.qq.com";
private static final String DEFAULT_SMTP_PORT = "465";
//
// 保存SMTP配置到SharedPreferences
//
public static void saveConfig(Context context, String server, String port,
String sender, String authCode, String recipient) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(KEY_SMTP_SERVER, server);
editor.putString(KEY_SMTP_PORT, port);
editor.putString(KEY_SMTP_SENDER, sender);
editor.putString(KEY_SMTP_AUTH_CODE, authCode);
editor.putString(KEY_SMTP_RECIPIENT, recipient);
editor.apply();
LogUtils.d(TAG, "saveConfig: SMTP配置已保存");
}
//
// 从SharedPreferences读取SMTP配置
//
public static String getServer(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
return sp.getString(KEY_SMTP_SERVER, DEFAULT_SMTP_SERVER);
}
public static String getPort(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
return sp.getString(KEY_SMTP_PORT, DEFAULT_SMTP_PORT);
}
public static String getSender(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
return sp.getString(KEY_SMTP_SENDER, "");
}
public static String getAuthCode(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
return sp.getString(KEY_SMTP_AUTH_CODE, "");
}
public static String getRecipient(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
return sp.getString(KEY_SMTP_RECIPIENT, "");
}
//
// SMTP邮件发送开关
//
public static void setEnabled(Context context, boolean enabled) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean(KEY_SMTP_ENABLED, enabled);
editor.apply();
LogUtils.d(TAG, "setEnabled: " + enabled);
}
public static boolean isEnabled(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
return sp.getBoolean(KEY_SMTP_ENABLED, false);
}
//
// 发送邮件(异步,在后台线程执行)
//
public static void sendMail(final Context context, final String subject,
final String content, final String recipients) {
if (!isEnabled(context)) {
LogUtils.d(TAG, "sendMail: SMTP邮件发送已禁用,跳过操作");
return;
}
final String server = getServer(context);
final String port = getPort(context);
final String sender = getSender(context);
final String authCode = getAuthCode(context);
if (sender == null || sender.length() == 0) {
showToast(context, "请先配置发件人邮箱");
return;
}
if (authCode == null || authCode.length() == 0) {
showToast(context, "请先配置邮箱授权码");
return;
}
if (recipients == null || recipients.length() == 0) {
showToast(context, "收件人不能为空");
return;
}
showToast(context, "正在发送邮件...");
new Thread(new Runnable() {
@Override
public void run() {
try {
Properties props = new Properties();
props.put("mail.smtp.host", server);
props.put("mail.smtp.port", port);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.socketFactory.port", port);
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(sender, authCode);
}
});
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(sender));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(recipients));
message.setSubject(subject);
message.setText(content);
Transport.send(message);
LogUtils.d(TAG, "sendMail: 邮件发送成功");
showToast(context, "邮件发送成功");
} catch (Exception e) {
LogUtils.e(TAG, "sendMail: 邮件发送失败", e);
showToast(context, "邮件发送失败: " + e.getMessage());
}
}
}).start();
}
//
// 在主线程显示Toast提示
//
private static void showToast(final Context context, final String message) {
if (context == null || message == null) {
return;
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(context.getApplicationContext(),
message, Toast.LENGTH_SHORT).show();
}
});
}
}
@@ -1,491 +0,0 @@
package cc.winboll.studio.libaes.views;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.enums.ADsMode;
import cc.winboll.studio.libaes.utils.MimoUtils;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import com.miui.zeus.mimo.sdk.ADParams;
import com.miui.zeus.mimo.sdk.BannerAd;
import com.miui.zeus.mimo.sdk.MimoCustomController;
import com.miui.zeus.mimo.sdk.MimoLocation;
import com.miui.zeus.mimo.sdk.MimoSdk;
import java.util.ArrayList;
import java.util.List;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/18 14:41
* @Describe WinBoLL 横幅广告类
*/
public class ADsBannerView extends LinearLayout {
public static final String TAG = "ADsBannerView";
private String BANNER_POS_ID = "802e356f1726f9ff39c69308bfd6f06a";
private String BANNER_POS_ID_WINBOLL_BETA = "d129ee5a263911f981a6dc7a9802e3e7";
private String BANNER_POS_ID_WINBOLL = "4ec30efdb32271765b9a4efac902828b";
/*
private String BANNER_POS_ID = "802e356f1726f9ff39c69308bfd6f06a";
private String BANNER_POS_ID_WINBOLL_BETA = "802e356f1726f9ff39c69308bfd6f06a";
private String BANNER_POS_ID_WINBOLL = "802e356f1726f9ff39c69308bfd6f06a";
*/
Context mContext;
View mMianView;
SharedPreferences mSharedPreferences;
ViewGroup mContainer;
BannerAd mBannerAd;
List<BannerAd> mAllBanners = new ArrayList<>();
// 新增:主线程Handler,确保广告操作在主线程执行
private Handler mMainHandler;
public ADsBannerView(Context context) {
super(context);
initView(context);
}
public ADsBannerView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public ADsBannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
public ADsBannerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView(context);
}
void initView(Context context) {
this.mContext = context;
initMimoSdk(this.mContext);
// 初始化主线程Handler(关键:确保广告操作在主线程执行)
mMainHandler = new Handler(Looper.getMainLooper());
this.mMianView = inflate(this.mContext, R.layout.view_adsbanner, null);
mContainer = this.mMianView.findViewById(R.id.ads_container);
addView(this.mMianView);
}
public void resumeADs(final Activity activity) {
// 没有设置米盟广告支持就退出
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
// 2. 释放之前的广告资源
if (mBannerAd != null) {
mBannerAd.destroy();
}
return;
}
// 修复:优化广告请求逻辑(添加生命周期判断 + 主线程执行)
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
if (ADsControlView.getAdsModeFromStatic(this.mContext) == ADsMode.MIMO_SDK) {
LogUtils.i(TAG, "已设置播放米盟广告,正在播放...");
mMainHandler.postDelayed(new Runnable() {
@Override
public void run() {
// 再次校验生命周期,避免延迟执行时Activity已销毁
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
fetchAd(activity);
}
}
}, 1000); // 延迟1秒请求广告,提升页面加载体验
}
}
}
/**
* 释放广告资源(关键:避免内存泄漏和空Context调用)
*/
public void releaseAdResources() {
// 没有设置米盟广告支持就退出
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
return;
}
LogUtils.d(TAG, "releaseAdResources()");
// 移除Handler回调
if (mMainHandler != null) {
mMainHandler.removeCallbacksAndMessages(null);
}
// 销毁所有广告实例
if (mAllBanners != null && !mAllBanners.isEmpty()) {
for (BannerAd ad : mAllBanners) {
if (ad != null) {
ad.destroy();
}
}
mAllBanners.clear();
}
// 置空当前广告引用
mBannerAd = null;
// 移除广告容器中的视图
if (mContainer != null) {
mContainer.removeAllViews();
}
}
/**
* 显示广告(核心修复:传递安全的Context + 生命周期校验)
*/
private void showAd(final Activity activity) {
// 没有设置米盟广告支持就退出
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
return;
}
LogUtils.d(TAG, "showAd()");
// 1. 生命周期校验:避免Activity已销毁时操作UI
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
LogUtils.e(TAG, "showAd: Activity is finishing or destroyed");
return;
}
// 2. 非空校验:广告实例和容器
if (mBannerAd == null || mContainer == null) {
LogUtils.e(TAG, "showAd: BannerAd or Container is null");
return;
}
// 3. 创建广告容器(使用ApplicationContext避免内存泄漏)
final FrameLayout container = new FrameLayout(activity.getApplicationContext());
container.setPadding(0, 0, 0, MimoUtils.dpToPx(activity, 10));
mContainer.addView(container, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
));
// if (mIsBiddingWin) {
// mBannerAd.setPrice(getPrice());
// }
// 4. 显示广告:传递ApplicationContext,避免Activity Context失效
mBannerAd.showAd(activity, container, new BannerAd.BannerInteractionListener() {
@Override
public void onAdClick() {
LogUtils.d(TAG, "onAdClick");
}
@Override
public void onAdShow() {
LogUtils.d(TAG, "onAdShow");
}
@Override
public void onAdDismiss() {
LogUtils.d(TAG, "onAdDismiss");
// 修复:移除容器时校验Activity状态
if (activity != null && !activity.isFinishing() && !activity.isDestroyed() && mContainer != null) {
mContainer.removeView(container);
}
}
@Override
public void onRenderSuccess() {
LogUtils.d(TAG, "onRenderSuccess");
}
@Override
public void onRenderFail(int code, String msg) {
LogUtils.e(TAG, "onRenderFail errorCode " + code + " errorMsg " + msg);
// 修复:渲染失败时移除容器
if (activity != null && !activity.isFinishing() && !activity.isDestroyed() && mContainer != null) {
mContainer.removeView(container);
}
}
});
}
/**
* 请求广告(核心修复:Context安全校验 + 异常捕获 + 资源管理)
*/
private void fetchAd(final Activity activity) {
// 没有设置米盟广告支持就退出
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
return;
}
LogUtils.d(TAG, "fetchAd()");
// 1. 双重校验:Activity未销毁 + Context非空
if (activity == null || activity.isFinishing() || activity.isDestroyed() || activity.getApplicationContext() == null) {
LogUtils.e(TAG, "fetchAd: Invalid Context or Activity state");
return;
}
// 2. 释放之前的广告资源,避免内存泄漏
if (mBannerAd != null) {
mBannerAd.destroy();
}
// 3. 初始化广告(使用ApplicationContext,避免Activity Context失效)
try {
mBannerAd = new BannerAd();
mAllBanners.add(mBannerAd);
} catch (Exception e) {
LogUtils.e(TAG, "fetchAd: Init BannerAd failed", e);
return;
}
// 4. 设置下载监听
mBannerAd.setDownLoadListener(new BannerAd.BannerDownloadListener() {
@Override
public void onDownloadStarted() {
LogUtils.d(TAG, "onDownloadStarted");
}
@Override
public void onDownloadPaused() {
LogUtils.d(TAG, "onDownloadPaused");
}
@Override
public void onDownloadFailed(int errorCode) {
String msg = "onDownloadFailed, errorCode = " + errorCode;
LogUtils.d(TAG, msg);
//ToastUtils.show(msg);
}
@Override
public void onDownloadFinished() {
LogUtils.d(TAG, "onDownloadFinished");
}
@Override
public void onDownloadProgressUpdated(int progress) {
LogUtils.d(TAG, "onDownloadProgressUpdated " + progress + "%");
}
@Override
public void onInstallFailed(int errorCode) {
LogUtils.d(TAG, "onInstallFailed, errorCode = " + errorCode);
}
@Override
public void onInstallStart() {
LogUtils.d(TAG, "onInstallStart");
}
@Override
public void onInstallSuccess() {
LogUtils.d(TAG, "onInstallSuccess");
}
@Override
public void onDownloadCancel() {
LogUtils.d(TAG, "onDownloadCancel");
}
});
// 5. 构建广告参数并请求
String currentAD_ID = getAD_ID();
LogUtils.d(TAG, String.format("currentAD_ID = %s", currentAD_ID));
ADParams params = new ADParams.Builder().setUpId(currentAD_ID).build();
mBannerAd.loadAd(params, new BannerAd.BannerLoadListener() {
@Override
public void onBannerAdLoadSuccess() {
LogUtils.d(TAG, "onBannerAdLoadSuccess()");
// 修复:广告加载成功后校验Activity状态
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
showAd(activity);
//ToastUtils.show("showAd()");
}
}
@Override
public void onAdLoadFailed(int errorCode, String errorMsg) {
String msg = "onAdLoadFailed: errorCode = " + errorCode + ", errorMsg = " + errorMsg;
LogUtils.d(TAG, msg);
removeAllBanners();
}
});
}
void removeAllBanners() {
// 没有设置米盟广告支持就退出
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
return;
}
// 修复:加载失败时移除当前广告实例
if (mAllBanners.contains(mBannerAd)) {
mAllBanners.remove(mBannerAd);
}
mBannerAd.destroy();
mBannerAd = null;
}
/**
* 根据当前秒数获取广告ID(原逻辑保留)
*/
private String getAD_ID() {
long currentSecond = System.currentTimeMillis() / 1000;
return (currentSecond % 2 == 0) ? BANNER_POS_ID :
(GlobalApplication.isDebugging() ? BANNER_POS_ID_WINBOLL_BETA : BANNER_POS_ID_WINBOLL);
}
/**
* 获取广告价格(原逻辑保留,添加空指针校验)
*/
// private long getPrice() {
// if (mBannerAd == null) {
// return 0;
// }
// Map<String, Object> map = mBannerAd.getMediaExtraInfo();
// if (map == null || map.isEmpty() || !map.containsKey("price")) {
// LogUtils.w(TAG, "getPrice: media extra info is null or no price key");
// return 0;
// }
// Object priceObj = map.get("price");
// if (priceObj instanceof Long) {
// return (Long) priceObj;
// } else if (priceObj instanceof Integer) {
// return ((Integer) priceObj).longValue();
// } else {
// LogUtils.e(TAG, "getPrice: price type is invalid");
// return 0;
// }
// }
/**
* 显示隐私协议弹窗(原逻辑保留,优化Context使用)
*/
// private void showPrivacy() {
// // 校验Activity状态,避免弹窗泄露
// if (getActivity() == null || getActivity().isFinishing() || getActivity().isDestroyed()) {
// return;
// }
// ADsMode adsMode = ADsControlView.getAdsModeFromStatic(this.mContext);
// if (adsMode == ADsMode.STANDALONE) {
// ADsControlView.updateAdsModeByStatic(this.mContext, ADsMode.STANDALONE);
// LogUtils.i(TAG, "单机模式,广告已处于不可用状态...");
// Toast.makeText(getActivity().getApplicationContext(), "单机模式,广告已处于不可用状态...", Toast.LENGTH_SHORT).show();
// return;
// } else if (adsMode == ADsMode.MIMO_SDK) {
// ADsControlView.updateAdsModeByStatic(this.mContext, ADsMode.MIMO_SDK);
// LogUtils.i(TAG, "米盟广告SDK支持模式,现在初始化SDK...");
// initMimoSdk();
// return;
// }
// else {
// LogUtils.i(TAG, "开始弹出隐私协议...");
// AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// builder.setTitle("用户须知");
// builder.setMessage("小米广告SDK隐私政策: https://dev.mi.com/distribute/doc/details?pId=1688, 请复制到浏览器查看");
// builder.setIcon(R.drawable.ic_launcher);
// builder.setCancelable(false); // 点击对话框以外的区域不消失
// builder.setPositiveButton("同意", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// getSharedPreferences().edit()
// .putString(PRIVACY_VALUE, String.valueOf(1))
// .apply();
// initMimoSdk();
// dialog.dismiss();
// }
// });
// builder.setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// getSharedPreferences().edit()
// .putString(PRIVACY_VALUE, String.valueOf(0))
// .apply();
// dialog.dismiss();
// }
// });
// AlertDialog dialog = builder.create();
//
// // 配置弹窗位置(底部全屏)
// Window window = dialog.getWindow();
// if (window != null) {
// window.setGravity(Gravity.BOTTOM);
// WindowManager m = getActivity().getWindowManager();
// Display d = m.getDefaultDisplay();
// WindowManager.LayoutParams p = window.getAttributes();
// p.width = d.getWidth();
// window.setAttributes(p);
// }
// dialog.show();
// }
// }
/**
* 初始化米盟SDK(核心修复:传递ApplicationContext + 异常捕获)
*/
private void initMimoSdk(Context context) {
// 1. 安全获取ApplicationContext,避免Activity Context失效
Context appContext = context.getApplicationContext();
if (appContext == null) {
LogUtils.e(TAG, "initMimoSdk: ApplicationContext is null");
return;
}
// 2. 初始化SDK,捕获异常避免崩溃
try {
MimoSdk.init(appContext, new MimoCustomController() {
@Override
public boolean isCanUseLocation() {
return true;
}
@Override
public MimoLocation getMimoLocation() {
return null;
}
@Override
public boolean isCanUseWifiState() {
return true;
}
@Override
public boolean alist() {
return true;
}
}, new MimoSdk.InitCallback() {
@Override
public void success() {
LogUtils.d(TAG, "MimoSdk init success");
}
@Override
public void fail(int code, String msg) {
LogUtils.e(TAG, "MimoSdk init fail, code=" + code + ",msg=" + msg);
}
});
MimoSdk.setDebugOn(true);
} catch (Exception e) {
LogUtils.e(TAG, "initMimoSdk: init failed", e);
}
}
/**
* 获取SharedPreferences实例(原逻辑保留,添加空指针校验)
*/
// SharedPreferences getSharedPreferences() {
//// if (mSharedPreferences == null) {
//// // 修复:使用ApplicationContext获取SharedPreferences,避免Activity Context泄露
//// Context appContext = getActivity().getApplicationContext();
//// if (appContext != null) {
//// mSharedPreferences = appContext.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
//// } else {
//// LogUtils.e(TAG, "getSharedPreferences: ApplicationContext is null");
//// // 降级方案:若ApplicationContext为空,使用Activity Context(仅作兼容)
//// mSharedPreferences = getActivity().getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
//// }
//// }
// return mSharedPreferences;
// }
}
@@ -1,633 +0,0 @@
package cc.winboll.studio.libaes.views;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.Html;
import android.util.AttributeSet;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.PopupWindow;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.enums.ADsMode;
import cc.winboll.studio.libaes.enums.PrivacyAgreeStatus;
import cc.winboll.studio.libaes.utils.WebUtils;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import com.miui.zeus.mimo.sdk.MimoCustomController;
import com.miui.zeus.mimo.sdk.MimoLocation;
import com.miui.zeus.mimo.sdk.MimoSdk;
import java.lang.reflect.Field;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/26 17:51
* @LastEditTime 2026/01/08 11:00:00 HKT
* @Describe 广告模式控制控件(Java 7 兼容,云宝物语模式)
* 核心修改:将PopupMenu锚点绑定到view_popmenu_anchor_point控件,菜单精准显示在锚点位置
*/
public class ADsControlView extends LinearLayout {
public static final String TAG = "ADsControlView";
// SP存储配置
private static final String SP_NAME = "ads_control_config";
private static final String KEY_SELECTED_MODE = "selected_ads_mode";
ADsMode mADsMode;
private static final String PRIVACY_VALUE = "privacy_value";
PrivacyAgreeStatus mPrivacyAgreeStatus;
// Handler消息标识
private static final int MSG_UPDATE_MODE = 1001;
// 控件引用
private RadioGroup rgADsMode;
private RadioButton rbStandalone;
private RadioButton rbMimoSDK;
private RadioButton rbStoreQrcode;
private RelativeLayout rlWinbollStore;
private ImageView ivWinbollStoreQrcode;
// 新增:锚点控件引用
private TextView viewPopmenuAnchorPoint;
// 外部监听、SP实例、Handler实例
private OnAdsModeSelectedListener listener;
private SharedPreferences sharedPreferences;
private InternalHandler mHandler;
private Context mContext;
// 静态列表:存储所有已创建的控件实例
private static final java.util.List<ADsControlView> sControlViews = new java.util.ArrayList<ADsControlView>();
// 常量定义
private static final String WECHAT_STORE_URL = "https://store.weixin.qq.com/shop/b/XhrPkZgoeHo4zug";
private static final int MENU_ITEM_OPEN_STORE = 1001;
// 构造方法(Java 7 兼容)
public ADsControlView(Context context) {
super(context);
initView(context);
}
public ADsControlView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
@SuppressWarnings("deprecation")
public ADsControlView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
public void setPrivacyAgreeStatus(PrivacyAgreeStatus privacyAgreeStatus) {
this.mPrivacyAgreeStatus = privacyAgreeStatus;
sharedPreferences.edit().putString(PRIVACY_VALUE, this.mPrivacyAgreeStatus.name()).apply();
}
public PrivacyAgreeStatus getPrivacyAgreeStatus() {
String privacyAgreeStatusStr = sharedPreferences.getString(PRIVACY_VALUE, PrivacyAgreeStatus.UN_SIGNED.name());
PrivacyAgreeStatus privacyAgreeStatus = PrivacyAgreeStatus.fromString(privacyAgreeStatusStr);
return privacyAgreeStatus;
}
public void setADsMode(ADsMode mADsMode) {
this.mADsMode = mADsMode;
sharedPreferences.edit().putString(KEY_SELECTED_MODE, this.mADsMode.name()).apply();
updateStoreQrcodeLayoutVisibility(mADsMode);
}
public ADsMode getADsMode() {
String savedModeStr = sharedPreferences.getString(KEY_SELECTED_MODE, ADsMode.STANDALONE.name());
mADsMode = ADsMode.fromValue(savedModeStr);
return mADsMode;
}
/**
* 初始化视图、SP、Handler
*/
private void initView(final Context context) {
this.mContext = context;
// 加载布局
LayoutInflater.from(context).inflate(R.layout.view_adscontrol, this, true);
// 初始化SP
sharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
// 绑定控件
rgADsMode = (RadioGroup) findViewById(R.id.rg_ads_mode);
rbStandalone = (RadioButton) findViewById(R.id.rb_standalone);
rbMimoSDK = (RadioButton) findViewById(R.id.rb_mimo_sdk);
rbStoreQrcode = (RadioButton) findViewById(R.id.rb_store_qrcode);
rlWinbollStore = (RelativeLayout) findViewById(R.id.rl_winboll_store);
ivWinbollStoreQrcode = (ImageView) findViewById(R.id.iv_winboll_store);
// 绑定锚点控件
viewPopmenuAnchorPoint = (TextView) findViewById(R.id.view_popmenu_anchor_point);
// 初始化Handler
mHandler = new InternalHandler(Looper.getMainLooper());
// 核心修改:初始化图片的点击和长按事件(锚点改为view_popmenu_anchor_point
initImageViewClickAndLongClick();
// 注册控件实例
registerControlView(this);
// 从SP读取初始模式
setSelectedMode(getADsMode());
// 单选组选择事件监听
rgADsMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.rb_standalone) {
setADsMode(ADsMode.STANDALONE);
if (listener != null) listener.onModeSelected(ADsMode.STANDALONE);
} else if (checkedId == R.id.rb_mimo_sdk) {
handlePrivacyLogic((Activity) context, PrivacyAgreeStatus.UN_SIGNED, new OnPrivacyChangeListener() {
@Override
public void onAgreePrivacy() {
setADsMode(ADsMode.MIMO_SDK);
if (listener != null) listener.onModeSelected(ADsMode.MIMO_SDK);
}
@Override
public void onDisagreePrivacy() {
setADsMode(ADsMode.STANDALONE);
setSelectedMode(ADsMode.STANDALONE);
if (listener != null) listener.onModeSelected(ADsMode.STANDALONE);
}
});
} else if (checkedId == R.id.rb_store_qrcode) {
setADsMode(ADsMode.STORE_QRCODE);
if (listener != null) listener.onModeSelected(ADsMode.STORE_QRCODE);
}
}
});
}
/**
* 初始化图片的点击和长按事件
* 核心:将PopupMenu锚点绑定到view_popmenu_anchor_point控件
*/
private void initImageViewClickAndLongClick() {
if (ivWinbollStoreQrcode == null || viewPopmenuAnchorPoint == null) {
LogUtils.e(TAG, "initImageViewClickAndLongClick: 控件引用为空");
return;
}
// 1. 点击事件:简化为提示信息
ivWinbollStoreQrcode.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
ToastUtils.show("长按图片可打开微信小店");
LogUtils.d(TAG, "图片点击:提示用户长按打开微信小店");
}
});
// 2. 长按事件:锚点改为view_popmenu_anchor_point
ivWinbollStoreQrcode.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
// 计算锚点控件的屏幕坐标(用于菜单位置微调)
int[] anchorLocation = new int[2];
viewPopmenuAnchorPoint.getLocationOnScreen(anchorLocation);
final int anchorX = anchorLocation[0];
final int anchorY = anchorLocation[1];
// 创建PopupMenu,锚点绑定到view_popmenu_anchor_point
PopupMenu popupMenu = new PopupMenu(mContext, viewPopmenuAnchorPoint);
// 设置菜单重力:相对锚点居中显示
popupMenu.setGravity(Gravity.CENTER);
Menu menu = popupMenu.getMenu();
menu.add(Menu.NONE, MENU_ITEM_OPEN_STORE, Menu.NONE, "打开微信小店");
// 设置菜单点击事件
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == MENU_ITEM_OPEN_STORE) {
WebUtils.openUrlInBrowser(mContext, WECHAT_STORE_URL);
return true;
}
return false;
}
});
try {
// 反射获取PopupWindow,微调菜单位置(可选)
Field popupField = PopupMenu.class.getDeclaredField("mPopup");
popupField.setAccessible(true);
Object popupObject = popupField.get(popupMenu);
if (popupObject instanceof PopupWindow) {
final PopupWindow popupWindow = (PopupWindow) popupObject;
popupWindow.setAnimationStyle(0); // 关闭默认动画
// 延迟微调菜单位置(确保布局测量完成)
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
int menuX = anchorX + viewPopmenuAnchorPoint.getWidth() / 2 - popupWindow.getWidth() / 2;
int menuY = anchorY + viewPopmenuAnchorPoint.getHeight() / 2 - popupWindow.getHeight() / 2;
if (!popupWindow.isShowing()) {
popupWindow.showAtLocation(viewPopmenuAnchorPoint, Gravity.NO_GRAVITY, menuX, menuY);
}
}
}, 30);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
LogUtils.e(TAG, "反射获取PopupWindow失败", e);
}
// 显示菜单
popupMenu.show();
LogUtils.d(TAG, "长按图片,菜单锚点为view_popmenu_anchor_point");
return true;
}
});
// 设置控件可交互标识
ivWinbollStoreQrcode.setClickable(true);
ivWinbollStoreQrcode.setFocusable(true);
ivWinbollStoreQrcode.setLongClickable(true);
viewPopmenuAnchorPoint.setClickable(false); // 锚点控件不可点击
viewPopmenuAnchorPoint.setLongClickable(false);
}
/**
* 从ImageView中提取Bitmap(保留方法,无实际调用)
*/
private Bitmap getBitmapFromImageView(ImageView imageView) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
return null;
}
/**
* 压缩Bitmap(备用方法,无实际调用)
*/
private Bitmap compressBitmapBySize(Bitmap src, int maxWidth, int maxHeight) {
if (src == null) return null;
int width = src.getWidth();
int height = src.getHeight();
float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
int newWidth = (int) (width * scale);
int newHeight = (int) (height * scale);
return Bitmap.createScaledBitmap(src, newWidth, newHeight, true);
}
/**
* 计算Bitmap采样率(备用方法,无实际调用)
*/
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
* 从ImageView反射获取资源ID(备用方法,无实际调用)
*/
private int getResIdFromImageView(ImageView imageView) {
try {
Field field = ImageView.class.getDeclaredField("mSrcResource");
field.setAccessible(true);
return field.getInt(imageView);
} catch (NoSuchFieldException | IllegalAccessException e) {
LogUtils.e(TAG, "getResIdFromImageView: 反射失败", e);
return 0;
}
}
/**
* 更新二维码布局显示状态
*/
private void updateStoreQrcodeLayoutVisibility(ADsMode mode) {
if (rlWinbollStore == null) return;
rlWinbollStore.setVisibility(mode == ADsMode.STORE_QRCODE ? View.VISIBLE : View.GONE);
}
/**
* 清理SP中的隐私协议状态
*/
public static void cleanPrivacyStatus(Context context) {
if (context == null) {
LogUtils.e(TAG, "cleanPrivacyStatus: Context is null");
return;
}
SharedPreferences sp = getPrivacySharedPreferences(context);
sp.edit().remove(PRIVACY_VALUE).apply();
LogUtils.i(TAG, "隐私协议状态清理成功");
ToastUtils.show("隐私协议状态已清理");
}
/**
* 获取隐私协议SP实例
*/
private static SharedPreferences getPrivacySharedPreferences(Context context) {
Context appContext = context.getApplicationContext();
if (appContext != null) {
return appContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
return context.getSharedPreferences(PRIVACY_VALUE, Context.MODE_PRIVATE);
}
/**
* 处理隐私协议逻辑
*/
private static void handlePrivacyLogic(final Activity activity, PrivacyAgreeStatus privacyAgreeStatus, final OnPrivacyChangeListener onPrivacyChangeListener) {
if (privacyAgreeStatus == PrivacyAgreeStatus.REJECTED) {
Toast.makeText(activity.getApplicationContext(), "已拒绝隐私协议,广告已处于不可用状态", Toast.LENGTH_SHORT).show();
return;
} else if (privacyAgreeStatus == PrivacyAgreeStatus.AGREED) {
initMimoSdkStatic(activity.getApplicationContext());
return;
} else {
AlertDialog dialog = createPrivacyDialog(activity, onPrivacyChangeListener);
Window window = dialog.getWindow();
if (window != null) {
window.setGravity(Gravity.BOTTOM);
WindowManager m = activity.getWindowManager();
Display d = m.getDefaultDisplay();
WindowManager.LayoutParams p = window.getAttributes();
p.width = d.getWidth();
window.setAttributes(p);
}
dialog.show();
}
}
/**
* 初始化米盟SDK
*/
private static void initMimoSdkStatic(Context appContext) {
if (appContext == null) return;
try {
MimoSdk.init(appContext, new MimoCustomController() {
@Override
public boolean isCanUseLocation() {
return true;
}
@Override
public MimoLocation getMimoLocation() {
return null;
}
@Override
public boolean isCanUseWifiState() {
return true;
}
@Override
public boolean alist() {
return true;
}
}, new MimoSdk.InitCallback() {
@Override
public void success() {
LogUtils.d(TAG, "米盟SDK初始化成功");
}
@Override
public void fail(int code, String msg) {
LogUtils.e(TAG, "米盟SDK初始化失败:" + code + ", " + msg);
}
});
MimoSdk.setDebugOn(true);
} catch (Exception e) {
LogUtils.e(TAG, "米盟SDK初始化异常", e);
}
}
/**
* 静态方法:更新SP中的模式
*/
public static void updateAdsModeByStatic(Context context, ADsMode mode) {
if (context == null || mode == null) return;
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
sp.edit().putString(KEY_SELECTED_MODE, mode.name()).apply();
InternalHandler.sendUpdateModeMessage(mode);
}
/**
* 静态方法:读取SP中的模式
*/
public static ADsMode getAdsModeFromStatic(Context context) {
if (context == null) return ADsMode.STANDALONE;
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
String savedModeStr = sp.getString(KEY_SELECTED_MODE, ADsMode.STANDALONE.name());
return ADsMode.fromValue(savedModeStr);
}
/**
* 注册控件实例
*/
private static void registerControlView(ADsControlView view) {
synchronized (sControlViews) {
if (!sControlViews.contains(view)) {
sControlViews.add(view);
}
}
}
/**
* 移除控件实例
*/
private static void unregisterControlView(ADsControlView view) {
synchronized (sControlViews) {
sControlViews.remove(view);
}
}
/**
* 设置选中模式
*/
private void setSelectedMode(final ADsMode mode) {
final ADsMode mode2 = (mode == null) ? ADsMode.STANDALONE : mode;
if (Looper.myLooper() == Looper.getMainLooper()) {
if (mode2 == ADsMode.STANDALONE) {
rbStandalone.setChecked(true);
} else if (mode2 == ADsMode.MIMO_SDK) {
rbMimoSDK.setChecked(true);
} else if (mode2 == ADsMode.STORE_QRCODE) {
rbStoreQrcode.setChecked(true);
}
updateStoreQrcodeLayoutVisibility(mode2);
} else {
mHandler.post(new Runnable() {
@Override
public void run() {
setSelectedMode(mode2);
}
});
}
}
/**
* 获取选中模式
*/
public ADsMode getSelectedMode() {
int checkedId = rgADsMode.getCheckedRadioButtonId();
if (checkedId == R.id.rb_mimo_sdk) {
return ADsMode.MIMO_SDK;
} else if (checkedId == R.id.rb_store_qrcode) {
return ADsMode.STORE_QRCODE;
} else {
return ADsMode.STANDALONE;
}
}
/**
* 设置外部监听
*/
public void setOnAdsModeSelectedListener(OnAdsModeSelectedListener listener) {
this.listener = listener;
}
/**
* 内部Handler类
*/
private static class InternalHandler extends Handler {
static volatile InternalHandler _InternalHandler;
public InternalHandler(Looper looper) {
super(looper);
_InternalHandler = this;
}
public static void sendUpdateModeMessage(ADsMode mode) {
if (mode == null || _InternalHandler == null) return;
Message msg = _InternalHandler.obtainMessage();
msg.what = MSG_UPDATE_MODE;
msg.obj = mode;
_InternalHandler.sendMessage(msg);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == MSG_UPDATE_MODE) {
ADsMode mode = (ADsMode) msg.obj;
if (mode == null) return;
synchronized (sControlViews) {
for (ADsControlView view : sControlViews) {
if (view != null && view.isShown() && view.isAttachedToWindow()) {
view.setSelectedMode(mode);
view.updateStoreQrcodeLayoutVisibility(mode);
}
}
}
}
}
}
/**
* 生命周期:控件销毁
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
unregisterControlView(this);
}
/**
* 外部监听接口
*/
public interface OnAdsModeSelectedListener {
void onModeSelected(ADsMode selectedMode);
}
/**
* 隐私协议监听接口
*/
public interface OnPrivacyChangeListener {
void onAgreePrivacy();
void onDisagreePrivacy();
}
/**
* 创建隐私协议对话框
*/
private static AlertDialog createPrivacyDialog(final Activity activity, final OnPrivacyChangeListener onPrivacyChangeListener) {
View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_privacy_agreement, null);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setView(dialogView).setCancelable(false);
final AlertDialog dialog = builder.create();
final TextView tvPrivacyUrl = (TextView) dialogView.findViewById(R.id.tv_privacy_url);
Button btnAgree = (Button) dialogView.findViewById(R.id.btn_agree);
Button btnDisagree = (Button) dialogView.findViewById(R.id.btn_disagree);
tvPrivacyUrl.setText(Html.fromHtml("<u>" + tvPrivacyUrl.getText().toString() + "</u>"));
tvPrivacyUrl.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String url = tvPrivacyUrl.getText().toString().trim();
ToastUtils.show("隐私协议链接:" + url);
}
});
tvPrivacyUrl.setClickable(true);
tvPrivacyUrl.setFocusable(true);
btnAgree.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onPrivacyChangeListener != null) {
onPrivacyChangeListener.onAgreePrivacy();
}
dialog.dismiss();
}
});
btnDisagree.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onPrivacyChangeListener != null) {
onPrivacyChangeListener.onDisagreePrivacy();
}
dialog.dismiss();
}
});
return dialog;
}
}
@@ -0,0 +1,113 @@
package cc.winboll.studio.libaes.views;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.utils.APPMSGMailUtils;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2026/07/16
* @Describe SMTP邮件配置视图控件,用于设置QQ邮件SMTP服务参数
*/
public class SMTPConfigView extends LinearLayout {
public static final String TAG = "SMTPConfigView";
private EditText mEtServer;
private EditText mEtPort;
private EditText mEtSender;
private EditText mEtAuthCode;
private EditText mEtRecipient;
private CheckBox mCbEnabled;
public SMTPConfigView(Context context) {
super(context);
initView(context);
}
public SMTPConfigView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
private void initView(Context context) {
LogUtils.d(TAG, "initView: 初始化SMTP配置视图");
View.inflate(context, R.layout.view_smtp_config, this);
mEtServer = (EditText) findViewById(R.id.et_smtp_server);
mEtPort = (EditText) findViewById(R.id.et_smtp_port);
mEtSender = (EditText) findViewById(R.id.et_smtp_sender);
mEtAuthCode = (EditText) findViewById(R.id.et_smtp_auth_code);
mEtRecipient = (EditText) findViewById(R.id.et_smtp_recipient);
mCbEnabled = (CheckBox) findViewById(R.id.cb_smtp_enabled);
mCbEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
APPMSGMailUtils.setEnabled(getContext(), isChecked);
}
});
// 加载已保存的配置
loadConfig();
// 保存按钮点击事件
findViewById(R.id.btn_smtp_save).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
saveConfig();
}
});
// 发送测试邮件按钮点击事件
findViewById(R.id.btn_smtp_test_send).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
saveConfig();
Context ctx = getContext();
String recipient = mEtRecipient.getText().toString().trim();
APPMSGMailUtils.sendMail(ctx, "SMTP测试邮件",
"这是一封SMTP配置测试邮件。如果您收到此邮件,说明SMTP配置正确。",
recipient);
}
});
}
//
// 从SharedPreferences加载已保存的SMTP配置
//
private void loadConfig() {
Context context = getContext();
mEtServer.setText(APPMSGMailUtils.getServer(context));
mEtPort.setText(APPMSGMailUtils.getPort(context));
mEtSender.setText(APPMSGMailUtils.getSender(context));
mEtAuthCode.setText(APPMSGMailUtils.getAuthCode(context));
mEtRecipient.setText(APPMSGMailUtils.getRecipient(context));
mCbEnabled.setChecked(APPMSGMailUtils.isEnabled(context));
LogUtils.d(TAG, "loadConfig: SMTP配置已加载");
}
//
// 将当前输入的SMTP配置保存到SharedPreferences
//
private void saveConfig() {
Context context = getContext();
String server = mEtServer.getText().toString().trim();
String port = mEtPort.getText().toString().trim();
String sender = mEtSender.getText().toString().trim();
String authCode = mEtAuthCode.getText().toString().trim();
String recipient = mEtRecipient.getText().toString().trim();
APPMSGMailUtils.saveConfig(context, server, port, sender, authCode, recipient);
Toast.makeText(context, "SMTP配置已保存", Toast.LENGTH_SHORT).show();
LogUtils.d(TAG, "saveConfig: SMTP配置已保存");
}
}
@@ -56,10 +56,5 @@
</LinearLayout>
<cc.winboll.studio.libaes.views.ADsBannerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/adsbanner"/>
</LinearLayout>
@@ -0,0 +1,110 @@
<?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"
android:padding="16dp"
android:background="@drawable/bg_container_border">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SMTP邮件配置"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="12dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SMTP服务器"
android:textSize="14sp"
android:layout_marginTop="8dp"/>
<EditText
android:id="@+id/et_smtp_server"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="smtp.qq.com"
android:inputType="textUri"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="端口"
android:textSize="14sp"
android:layout_marginTop="8dp"/>
<EditText
android:id="@+id/et_smtp_port"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="465"
android:inputType="number"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发件人邮箱"
android:textSize="14sp"
android:layout_marginTop="8dp"/>
<EditText
android:id="@+id/et_smtp_sender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="your@qq.com"
android:inputType="textEmailAddress"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="授权码"
android:textSize="14sp"
android:layout_marginTop="8dp"/>
<EditText
android:id="@+id/et_smtp_auth_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="QQ邮箱授权码"
android:inputType="textPassword"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="默认收件人"
android:textSize="14sp"
android:layout_marginTop="8dp"/>
<EditText
android:id="@+id/et_smtp_recipient"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="recipient@example.com"
android:inputType="textEmailAddress"/>
<CheckBox
android:id="@+id/cb_smtp_enabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启用SMTP邮件发送"
android:textSize="14sp"
android:layout_marginTop="12dp"/>
<Button
android:id="@+id/btn_smtp_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存配置"
android:layout_marginTop="16dp"/>
<Button
android:id="@+id/btn_smtp_test_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发送测试邮件"
android:layout_marginTop="8dp"/>
</LinearLayout>
+8 -8
View File
@@ -3,15 +3,15 @@
<color name="colorTextColor">#FFFFFFFF</color>
<color name="colorPrimary">#FF03AB4E</color>
<color name="colorPrimaryDark">#FF027C39</color>
<color name="colorAccent">#FF3DDC84</color>
<color name="colorPrimaryDark">#FF3DDC84</color>
<color name="colorAccent">#FF027C39</color>
<color name="colorText">#FFFFFB8D</color>
<color name="colorToastFrame">#FFA9A9A9</color>
<color name="colorToastFrame">#FF555555</color>
<color name="colorToastShadow">#FF000000</color>
<color name="colorToastBackgroung">#FFFFFFFF</color>
<color name="colorAToolbarStartColor">#FF7D3F12</color>
<color name="colorAToolbarCenterColor">#FFCC6E2B</color>
<color name="colorAToolbarEndColor">#FFF4B98F</color>
<color name="colorToastBackgroung">#FF3A3A3A</color>
<color name="colorAToolbarStartColor">#FF5A3A1A</color>
<color name="colorAToolbarCenterColor">#FFA05A2A</color>
<color name="colorAToolbarEndColor">#FFD4A07A</color>
<color name="colorACardShadow">@color/colorPrimaryDark</color>
<color name="colorACardFrame">@color/colorPrimary</color>
@@ -24,7 +24,7 @@
<color name="colorOHPCTSSecondaryProgress">@color/colorPrimary</color>
<color name="colorOHPCTSProgress">@color/colorPrimaryDark</color>
<color name="toolbarBackgroundColor">#FF03AB4E</color>
<color name="toolbarBackgroundColor">#FF3DDC84</color>
<color name="toolbarTextColor">#FFFFFFFF</color>
<color name="mainWindowBackgroundColor">#FF2C2C2C</color>
<color name="mainWindowTextColor">#FFFFFFFF</color>
+23 -20
View File
@@ -2,6 +2,9 @@
<resources>
<style name="AESTheme" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">#FF03AB4E</item>
<item name="colorPrimaryDark">#FF3DDC84</item>
<item name="colorAccent">#FF027C39</item>
<item name="themeDebug">@style/DebugActivityTheme</item>
<item name="aboutViewBackgroundColor">@color/mainWindowBackgroundColor</item>
<item name="aboutViewTextColor">@color/mainWindowTextColor</item>
@@ -22,7 +25,7 @@
<item name="mainWindowTextColor">@color/mainWindowTextColor</item>
<item name="mainWindowDarkBackgroundColor">@color/mainWindowBackgroundColor</item>
<item name="mainWindowDarkTextColor">@color/mainWindowTextColor</item>
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="android:statusBarColor">?attr/colorPrimaryDark</item>
<item name="aToolbar">@style/AESAToolbar</item>
<item name="aSupportToolbar">@style/AESASupportToolbar</item>
@@ -40,52 +43,52 @@
<style name="AESAToolbar">
<item name="attrAToolbarTitleTextColor">@color/colorTextColor</item>
<item name="attrAToolbarStartColor">@color/colorPrimaryDark</item>
<item name="attrAToolbarCenterColor">@color/colorPrimary</item>
<item name="attrAToolbarEndColor">@color/colorAccent</item>
<item name="attrAToolbarStartColor">?attr/colorPrimaryDark</item>
<item name="attrAToolbarCenterColor">?attr/colorPrimary</item>
<item name="attrAToolbarEndColor">?attr/colorAccent</item>
</style>
<style name="AESASupportToolbar">
<item name="attrASupportToolbarTitleTextColor">@color/colorTextColor</item>
<item name="attrASupportToolbarStartColor">@color/colorPrimaryDark</item>
<item name="attrASupportToolbarCenterColor">@color/colorPrimary</item>
<item name="attrASupportToolbarEndColor">@color/colorAccent</item>
<item name="attrASupportToolbarStartColor">?attr/colorPrimaryDark</item>
<item name="attrASupportToolbarCenterColor">?attr/colorPrimary</item>
<item name="attrASupportToolbarEndColor">?attr/colorAccent</item>
</style>
<style name="DepthAESTheme" parent="AESTheme">
<item name="colorPrimary">#FF0065EC</item>
<item name="colorPrimaryDark">#FF004DB4</item>
<item name="colorAccent">#FF4A97FF</item>
<item name="colorPrimaryDark">#FF4A97FF</item>
<item name="colorAccent">#FF004DB4</item>
</style>
<style name="SkyAESTheme" parent="AESTheme">
<item name="colorPrimary">#FF00A6FF</item>
<item name="colorPrimaryDark">#FF007ABB</item>
<item name="colorAccent">#FF84D4FF</item>
<item name="colorPrimaryDark">#FF84D4FF</item>
<item name="colorAccent">#FF007ABB</item>
</style>
<style name="GoldenAESTheme" parent="AESTheme">
<item name="colorPrimary">#FFF0CA11</item>
<item name="colorPrimaryDark">#FFD3AF00</item>
<item name="colorAccent">#FFFFE35C</item>
<item name="colorPrimaryDark">#FFFFE35C</item>
<item name="colorAccent">#FFD3AF00</item>
</style>
<style name="BearingAESTheme" parent="AESTheme">
<item name="colorPrimary">#FF840FFF</item>
<item name="colorPrimaryDark">#FF6900D7</item>
<item name="colorAccent">#FFBA78FF</item>
<item name="colorPrimaryDark">#FFBA78FF</item>
<item name="colorAccent">#FF6900D7</item>
</style>
<style name="MemorAESTheme" parent="AESTheme">
<item name="colorPrimary">#FFFF00F5</item>
<item name="colorPrimaryDark">#FFE500DC</item>
<item name="colorAccent">#FFFF76FA</item>
<item name="colorPrimaryDark">#FFFF76FA</item>
<item name="colorAccent">#FFE500DC</item>
</style>
<style name="TaoAESTheme" parent="AESTheme">
<item name="colorPrimary">#FFACACAC</item>
<item name="colorPrimaryDark">#FF898989</item>
<item name="colorAccent">#FFD8D8D8</item>
<item name="colorPrimary">#FF7E7E7E</item>
<item name="colorPrimaryDark">#FFE2E2E2</item>
<item name="colorAccent">#FF000000</item>
</style>
<style name="NormalDialogStyle" parent="Theme.AppCompat.Dialog">
+14 -11
View File
@@ -2,6 +2,9 @@
<resources>
<style name="AESTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#FF03AB4E</item>
<item name="colorPrimaryDark">#FF027C39</item>
<item name="colorAccent">#FF3DDC84</item>
<item name="themeDebug">@style/DebugActivityTheme</item>
<item name="aboutViewBackgroundColor">@color/mainWindowBackgroundColor</item>
<item name="aboutViewTextColor">@color/mainWindowTextColor</item>
@@ -22,7 +25,7 @@
<item name="mainWindowTextColor">@color/mainWindowTextColor</item>
<item name="mainWindowDarkBackgroundColor">@color/mainWindowBackgroundColor</item>
<item name="mainWindowDarkTextColor">@color/mainWindowTextColor</item>
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="android:statusBarColor">?attr/colorPrimaryDark</item>
<item name="aToolbar">@style/AESAToolbar</item>
<item name="aSupportToolbar">@style/AESASupportToolbar</item>
@@ -40,16 +43,16 @@
<style name="AESAToolbar">
<item name="attrAToolbarTitleTextColor">@color/colorTextColor</item>
<item name="attrAToolbarStartColor">@color/colorPrimaryDark</item>
<item name="attrAToolbarCenterColor">@color/colorPrimary</item>
<item name="attrAToolbarEndColor">@color/colorAccent</item>
<item name="attrAToolbarStartColor">?attr/colorPrimaryDark</item>
<item name="attrAToolbarCenterColor">?attr/colorPrimary</item>
<item name="attrAToolbarEndColor">?attr/colorAccent</item>
</style>
<style name="AESASupportToolbar">
<item name="attrASupportToolbarTitleTextColor">@color/colorTextColor</item>
<item name="attrASupportToolbarStartColor">@color/colorPrimaryDark</item>
<item name="attrASupportToolbarCenterColor">@color/colorPrimary</item>
<item name="attrASupportToolbarEndColor">@color/colorAccent</item>
<item name="attrASupportToolbarStartColor">?attr/colorPrimaryDark</item>
<item name="attrASupportToolbarCenterColor">?attr/colorPrimary</item>
<item name="attrASupportToolbarEndColor">?attr/colorAccent</item>
</style>
<style name="DepthAESTheme" parent="AESTheme">
@@ -83,9 +86,9 @@
</style>
<style name="TaoAESTheme" parent="AESTheme">
<item name="colorPrimary">#FFACACAC</item>
<item name="colorPrimaryDark">#FF898989</item>
<item name="colorAccent">#FFD8D8D8</item>
<item name="colorPrimary">#FF7E7E7E</item>
<item name="colorPrimaryDark">#FF000000</item>
<item name="colorAccent">#FFE2E2E2</item>
</style>
<style name="NormalDialogStyle" parent="Theme.AppCompat.Light.Dialog">
@@ -109,4 +112,4 @@
<item name="@android:windowExitAnimation">@anim/normal_dialog_exit_corner</item>
</style>
</resources>
</resources>
+4 -4
View File
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun May 17 16:16:36 HKT 2026
stageCount=16
#Wed May 27 14:51:29 HKT 2026
stageCount=23
libraryProject=libappbase
baseVersion=15.20
publishVersion=15.20.15
publishVersion=15.20.22
buildCount=0
baseBetaVersion=15.20.16
baseBetaVersion=15.20.23
@@ -2,6 +2,7 @@ package cc.winboll.studio.libappbase;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
/**
@@ -129,11 +129,10 @@ public class GlobalApplication extends Application {
// 初始化单例实例(确保在所有初始化操作前完成)
sInstance = this;
restoreDebugStatus();
// 初始化基础组件(日志、崩溃处理、Toast)
initCoreComponents();
// 恢复/初始化调试模式状态(从本地文件读取,无文件则默认关闭调试
restoreDebugStatus();
// 新增:初始化服务器地址(从 SP 读取到内存,提高后续访问效率)
// 初始化服务器地址(从 SP 读取到内存,提高后续访问效率
initWinbollHost();
LogUtils.d(TAG, "GlobalApplication 初始化完成,单例实例已创建");
@@ -144,7 +143,11 @@ public class GlobalApplication extends Application {
*/
private void initCoreComponents() {
// 初始化日志工具(传入 Application 上下文)
LogUtils.init(this);
// 调试状态下初始化日志工具
if (GlobalApplication.isDebugging()) {
LogUtils.init(this);
}
// 初始化全局异常处理器(捕获应用崩溃信息,用于调试或上报)
CrashHandler.init(this);
// 初始化 Toast 工具(统一 Toast 样式、避免内存泄漏等)
@@ -27,6 +27,11 @@ public class LogActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!GlobalApplication.isDebugging()) {
ToastUtils.show("非调试状态日志功能不可用");
finish();
}
// 设置布局文件(包含 LogView 控件)
setContentView(R.layout.activity_log);
@@ -67,45 +67,29 @@ public class LogUtils {
// ====================== 初始化入口 ======================
public static void init(final Context context) {
init(context, LOG_LEVEL.Off);
init(context, LOG_LEVEL.Off);
}
public static void init(final Context context, final LOG_LEVEL logLevel) {
Log.d(TAG, "init 执行日志工具初始化");
_mContext = context;
if (!GlobalApplication.isDebugging()) {
return;
}
Log.d(TAG, "init 执行日志工具初始化");
_mContext = context;
if (GlobalApplication.isDebugging()) {
initDebugDir();
} else {
initReleaseDir();
}
initLogUtilsDir();
initLogConfigBean();
addClassTAGList();
loadTAGBeanSettings();
checkAndTrimLogFileSize();
initLogConfigBean();
addClassTAGList();
loadTAGBeanSettings();
checkAndTrimLogFileSize();
_IsInited = true;
Log.d(TAG, "init 日志工具初始化完成");
_IsInited = true;
Log.d(TAG, "init 执行日志工具初始化完成");
}
// ====================== 目录初始化 ======================
private static void initDebugDir() {
final Context appContext = _mContext.getApplicationContext();
_mfLogCacheDir = new File(appContext.getExternalCacheDir(), TAG);
if (!_mfLogCacheDir.exists()) {
_mfLogCacheDir.mkdirs();
}
_mfLogCatchFile = new File(_mfLogCacheDir, "log.txt");
_mfLogDataDir = appContext.getExternalFilesDir(TAG);
if (!_mfLogDataDir.exists()) {
_mfLogDataDir.mkdirs();
}
_mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json");
}
private static void initReleaseDir() {
private static void initLogUtilsDir() {
final Context appContext = _mContext.getApplicationContext();
_mfLogCacheDir = new File(appContext.getCacheDir(), TAG);
if (!_mfLogCacheDir.exists()) {
@@ -113,7 +97,7 @@ public class LogUtils {
}
_mfLogCatchFile = new File(_mfLogCacheDir, "log.txt");
_mfLogDataDir = new File(appContext.getFilesDir(), TAG);
_mfLogDataDir = appContext.getExternalFilesDir(TAG);
if (!_mfLogDataDir.exists()) {
_mfLogDataDir.mkdirs();
}
@@ -136,7 +120,7 @@ public class LogUtils {
}
final long KEEP_FILE_SIZE = 25000L; // ~25KB 确保剪贴板可完整复制
final long MAX_FILE_SIZE = 2*KEEP_FILE_SIZE;
final long MAX_FILE_SIZE = 2 * KEEP_FILE_SIZE;
final long fileSize = _mfLogCatchFile.length();
if (fileSize <= MAX_FILE_SIZE) {
@@ -266,6 +250,9 @@ public class LogUtils {
}
public static void setTAGListEnable(final String tag, final boolean isEnable) {
if (!_IsInited) {
return;
}
final Iterator<Map.Entry<String, Boolean>> iterator = mapTAGList.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<String, Boolean> entry = iterator.next();
@@ -279,6 +266,9 @@ public class LogUtils {
}
public static void setALlTAGListEnable(final boolean isEnable) {
if (!_IsInited) {
return;
}
for (final Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) {
entry.setValue(isEnable);
}
@@ -288,15 +278,22 @@ public class LogUtils {
// ====================== 日志级别控制 ======================
public static void setLogLevel(final LOG_LEVEL logLevel) {
if (_mLogUtilsBean == null) {
Log.d(TAG, "setLogLevel LogUtils未初始化,忽略设置日志级别");
return;
}
_mLogUtilsBean.setLogLevel(logLevel);
_mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean);
}
public static LOG_LEVEL getLogLevel() {
return _mLogUtilsBean.getLogLevel();
return _mLogUtilsBean == null ?LOG_LEVEL.Off: _mLogUtilsBean.getLogLevel();
}
private static boolean isLoggable(final String tag, final LOG_LEVEL logLevel) {
if (!GlobalApplication.isDebugging()) {
return false;
}
if (!_IsInited) {
return false;
}
@@ -2,6 +2,7 @@ package cc.winboll.studio.libappbase;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
/**
@@ -2,6 +2,7 @@ package cc.winboll.studio.libappbase;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
/**
@@ -1,6 +1,7 @@
package cc.winboll.studio.libappbase;
import android.os.FileObserver;
import java.io.File;
import java.lang.ref.WeakReference;
/**
@@ -51,30 +52,38 @@ public class LogViewThread extends Thread {
*/
@Override
public void run() {
// 获取日志缓存目录路径(从 LogUtils 统一获取,确保路径一致性)
String logDirPath = LogUtils.getLogCacheDir().getPath();
LogUtils.d(TAG, "启动日志文件监听,监听目录:" + logDirPath);
// 调试状态进行日志输出任务
if (GlobalApplication.isDebugging()) {
// 获取日志缓存目录路径(从 LogUtils 统一获取,确保路径一致性)
File logDir = LogUtils.getLogCacheDir();
if (logDir == null) {
LogUtils.d(TAG, "日志缓存目录未初始化,线程退出");
return;
}
String logDirPath = logDir.getPath();
LogUtils.d(TAG, "启动日志文件监听,监听目录:" + logDirPath);
// 初始化日志文件监听器(监听目标目录的文件事件)
mLogListener = new LogListener(logDirPath);
// 开始监听文件事件(非阻塞,内部通过 Native 层实现)
mLogListener.startWatching();
// 初始化日志文件监听器(监听目标目录的文件事件)
mLogListener = new LogListener(logDirPath);
// 开始监听文件事件(非阻塞,内部通过 Native 层实现)
mLogListener.startWatching();
// 循环等待退出标志(每 1 秒检查一次,降低 CPU 占用)
while (!isExit()) {
try {
Thread.sleep(1000); // 休眠 1 秒,避免忙等
} catch (InterruptedException e) {
// 线程被中断时,恢复中断标志并退出循环(避免无限阻塞)
Thread.currentThread().interrupt();
LogUtils.d(TAG, "日志监听线程被中断,准备退出。" + e);
break;
}
}
// 循环等待退出标志(每 1 秒检查一次,降低 CPU 占用)
while (!isExit()) {
try {
Thread.sleep(1000); // 休眠 1 秒,避免忙等
} catch (InterruptedException e) {
// 线程被中断时,恢复中断标志并退出循环(避免无限阻塞)
Thread.currentThread().interrupt();
LogUtils.d(TAG, "日志监听线程被中断,准备退出。" + e);
break;
}
}
// 收到退出标志,停止监听并释放资源
mLogListener.stopWatching();
LogUtils.d(TAG, "日志文件监听已停止,线程退出");
// 收到退出标志,停止监听并释放资源
mLogListener.stopWatching();
LogUtils.d(TAG, "日志文件监听已停止,线程退出");
}
}
/**
@@ -0,0 +1,415 @@
package cc.winboll.studio.libappbase.models.libs1520000;
import android.content.Context;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.UTF8FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
/**
* WinBoLL JSON 数据模型基类(抽象类)
* 定义 Json Bean 的核心规范:序列化/反序列化、文件持久化、列表处理等通用逻辑,
* 子类需实现抽象方法,完成自身字段JSON读写业务逻辑
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @CreateTime 2026/05/19 22:33:00
* @EditTime 2026/05/19 23:15:48
* @param <T> 泛型约束,限定子类必须继承自BaseBean
*/
public abstract class BaseBean<T extends BaseBean> {
// ====================== 静态常量 ======================
/** 日志输出标识TAG */
public static final String TAG = "BaseBean";
/** JSON存储Bean类名字段Key,用于类型校验 */
static final String BEAN_NAME = "BeanName";
// ====================== 构造方法 ======================
/**
* 无参空构造,满足反射实例化要求
*/
public BaseBean() {
}
// ====================== 抽象方法 ======================
/**
* 获取当前实体类全类名
* @return 全限定类名字符串
*/
public abstract String getName();
/**
* 从JSON读取器解析构建实体对象
* @param jsonReader JSON读取流
* @return 解析完成实体对象
* @throws IOException IO读写异常
*/
public abstract T readBeanFromJsonReader(final JsonReader jsonReader) throws IOException;
// ====================== 路径获取相关 ======================
/**
* 获取单个实体默认存储JSON文件路径
* @param context 应用上下文
* @return 文件绝对路径
*/
public String getBeanJsonFilePath(final Context context) {
return context.getExternalFilesDir(TAG) + "/" + getName() + ".json";
}
/**
* 获取实体列表默认存储JSON文件路径
* @param context 应用上下文
* @return 文件绝对路径
*/
public String getBeanListJsonFilePath(final Context context) {
return context.getExternalFilesDir(TAG) + "/" + getName() + "_List.json";
}
// ====================== JSON序列化基础方法 ======================
/**
* 写入基础Bean标识字段至JSON
* @param jsonWriter JSON写入流
* @throws IOException 写入异常
*/
public void writeThisToJsonWriter(final JsonWriter jsonWriter) throws IOException {
jsonWriter.name(BEAN_NAME).value(getName());
}
/**
* 基类通用字段解析回调
* @param jsonReader JSON读取流
* @param name 字段名
* @return 是否完成当前字段解析
* @throws IOException 读取异常
*/
public boolean initObjectsFromJsonReader(final JsonReader jsonReader, final String name) throws IOException {
return false;
}
// ====================== 实体字符串序列化 ======================
@Override
public String toString() {
LogUtils.d(TAG, "执行BaseBean实体转JSON字符串");
final StringWriter stringWriter = new StringWriter();
final JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setIndent(" ");
try {
jsonWriter.beginObject();
writeThisToJsonWriter(jsonWriter);
jsonWriter.endObject();
return stringWriter.toString();
} catch (final IOException e) {
LogUtils.d(TAG, "实体转JSON字符串异常", Thread.currentThread().getStackTrace());
}
return "";
}
// ====================== 列表序列化工具 ======================
/**
* Bean列表转为格式化JSON数组字符串
* @param beanList 实体集合
* @param <T> 实体泛型
* @return JSON字符串
*/
public static <T extends BaseBean> String toStringByBeanList(final ArrayList<T> beanList) {
LogUtils.d(TAG, "执行Bean列表序列化JSON操作");
try {
final StringWriter stringWriter = new StringWriter();
final JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setIndent(" ");
jsonWriter.beginArray();
for (int i = 0; i < beanList.size(); i++) {
jsonWriter.beginObject();
beanList.get(i).writeThisToJsonWriter(jsonWriter);
jsonWriter.endObject();
}
jsonWriter.endArray();
jsonWriter.close();
return stringWriter.toString();
} catch (final IOException e) {
LogUtils.d(TAG, "列表序列化JSON异常", Thread.currentThread().getStackTrace());
}
return "";
}
// ====================== JSON字符串解析实体 ======================
/**
* JSON文本解析为单个实体对象
* @param szBean JSON文本
* @param clazz 实体Class字节码
* @param <T> 实体泛型
* @return 解析完成实体
* @throws IOException 解析IO异常
*/
public static <T extends BaseBean> T parseStringToBean(final String szBean, final Class<T> clazz) throws IOException {
LogUtils.d(TAG, "进入字符串解析实体方法,目标Class:" + clazz.getSimpleName());
final StringReader stringReader = new StringReader(szBean);
final JsonReader jsonReader = new JsonReader(stringReader);
try {
final T beanTemp = clazz.newInstance();
return (T) beanTemp.readBeanFromJsonReader(jsonReader);
} catch (final InstantiationException e) {
LogUtils.d(TAG, "实体反射实例化失败(InstantiationException)", Thread.currentThread().getStackTrace());
} catch (final IllegalAccessException e) {
LogUtils.d(TAG, "实体反射权限访问失败(IllegalAccessException)", Thread.currentThread().getStackTrace());
}
return null;
}
/**
* JSON数组文本解析填充实体列表
* @param szBeanList JSON数组文本
* @param beanList 目标存储集合
* @param clazz 实体字节码
* @param <T> 实体泛型
* @return 解析结果
*/
public static <T extends BaseBean> boolean parseStringToBeanList(final String szBeanList, ArrayList<T> beanList, final Class<T> clazz) {
LogUtils.d(TAG, "进入列表字符串解析方法");
try {
if (beanList == null) {
beanList = new ArrayList<T>();
} else {
beanList.clear();
}
final StringReader stringReader = new StringReader(szBeanList);
final JsonReader jsonReader = new JsonReader(stringReader);
jsonReader.beginArray();
while (jsonReader.hasNext()) {
final T beanTemp = clazz.newInstance();
final T bean = (T) beanTemp.readBeanFromJsonReader(jsonReader);
if (bean != null) {
beanList.add(bean);
}
}
jsonReader.endArray();
return true;
} catch (final InstantiationException e) {
LogUtils.d(TAG, "列表解析反射实例化异常", Thread.currentThread().getStackTrace());
} catch (final IllegalAccessException e) {
LogUtils.d(TAG, "列表解析反射权限异常", Thread.currentThread().getStackTrace());
} catch (final IOException e) {
LogUtils.d(TAG, "列表解析JSON读写异常", Thread.currentThread().getStackTrace());
}
return false;
}
// ====================== 文件加载实体 ======================
/**
* 从默认路径加载单个实体
* @param context 上下文
* @param clazz 实体字节码
* @param <T> 实体泛型
* @return 加载实体
*/
public static <T extends BaseBean> T loadBean(final Context context, final Class<T> clazz) {
LogUtils.d(TAG, "执行默认路径加载实体数据");
try {
final T beanTemp = clazz.newInstance();
return loadBeanFromFile(beanTemp.getBeanJsonFilePath(context), clazz);
} catch (final InstantiationException e) {
LogUtils.d(TAG, "加载实体反射实例化失败", Thread.currentThread().getStackTrace());
} catch (final IllegalAccessException e) {
LogUtils.d(TAG, "加载实体反射权限失败", Thread.currentThread().getStackTrace());
}
return null;
}
/**
* 自定义文件路径加载单个实体
* @param szFilePath 文件路径
* @param clazz 实体字节码
* @param <T> 实体泛型
* @return 实体对象
*/
public static <T extends BaseBean> T loadBeanFromFile(final String szFilePath, final Class<T> clazz) {
LogUtils.d(TAG, "指定路径加载实体,路径:" + szFilePath);
try {
final File file = new File(szFilePath);
if (file.exists()) {
final T beanTemp = clazz.newInstance();
final String json = UTF8FileUtils.readStringFromFile(szFilePath);
return beanTemp.parseStringToBean(json, clazz);
}
} catch (final InstantiationException e) {
LogUtils.d(TAG, "文件加载实体反射实例化异常", Thread.currentThread().getStackTrace());
} catch (final IllegalAccessException e) {
LogUtils.d(TAG, "文件加载实体反射权限异常", Thread.currentThread().getStackTrace());
} catch (final IOException e) {
LogUtils.d(TAG, "文件读取JSON数据异常", Thread.currentThread().getStackTrace());
}
return null;
}
/**
* 默认路径加载实体列表
* @param context 上下文
* @param beanListDst 目标集合
* @param clazz 实体字节码
* @param <T> 实体泛型
* @return 加载结果
*/
public static <T extends BaseBean> boolean loadBeanList(final Context context, final ArrayList<T> beanListDst, final Class<T> clazz) {
LogUtils.d(TAG, "默认路径加载实体列表数据");
try {
final T beanTemp = clazz.newInstance();
return loadBeanListFromFile(beanTemp.getBeanListJsonFilePath(context), beanListDst, clazz);
} catch (final InstantiationException e) {
LogUtils.d(TAG, "列表加载反射实例化异常", Thread.currentThread().getStackTrace());
} catch (final IllegalAccessException e) {
LogUtils.d(TAG, "列表加载反射权限异常", Thread.currentThread().getStackTrace());
}
return false;
}
/**
* 自定义路径加载实体列表
* @param szFilePath 文件路径
* @param beanList 目标集合
* @param clazz 实体字节码
* @param <T> 实体泛型
* @return 加载结果
*/
public static <T extends BaseBean> boolean loadBeanListFromFile(final String szFilePath, final ArrayList<T> beanList, final Class<T> clazz) {
LogUtils.d(TAG, "指定路径加载实体列表,路径:" + szFilePath);
try {
final File file = new File(szFilePath);
if (file.exists()) {
final String listJson = UTF8FileUtils.readStringFromFile(szFilePath);
return parseStringToBeanList(listJson, beanList, clazz);
}
} catch (final IOException e) {
LogUtils.d(TAG, "列表文件读取异常", Thread.currentThread().getStackTrace());
}
return false;
}
// ====================== 实体数据保存文件 ======================
/**
* 默认路径保存单个实体
* @param context 上下文
* @param bean 待保存实体
* @param <T> 实体泛型
* @return 保存结果
*/
public static <T extends BaseBean> boolean saveBean(final Context context, final T bean) {
return saveBeanToFile(bean.getBeanJsonFilePath(context), bean);
}
/**
* 自定义路径保存单个实体
* @param szFilePath 保存路径
* @param bean 待保存实体
* @param <T> 实体泛型
* @return 保存结果
*/
public static <T extends BaseBean> boolean saveBeanToFile(final String szFilePath, final T bean) {
LogUtils.d(TAG, "执行实体数据写入文件操作");
try {
final String json = bean.toString();
UTF8FileUtils.writeStringToFile(szFilePath, json);
return true;
} catch (final IOException e) {
LogUtils.d(TAG, "实体写入文件异常", Thread.currentThread().getStackTrace());
}
return false;
}
/**
* 默认路径保存实体列表
* @param context 上下文
* @param beanList 实体集合
* @param clazz 实体字节码
* @param <T> 实体泛型
* @return 保存结果
*/
public static <T extends BaseBean> boolean saveBeanList(final Context context, final ArrayList<T> beanList, final Class<T> clazz) {
LogUtils.d(TAG, "默认路径保存实体列表数据");
try {
final T beanTemp = clazz.newInstance();
return saveBeanListToFile(beanTemp.getBeanListJsonFilePath(context), beanList);
} catch (final InstantiationException e) {
LogUtils.d(TAG, "列表保存反射实例化异常", Thread.currentThread().getStackTrace());
} catch (final IllegalAccessException e) {
LogUtils.d(TAG, "列表保存反射权限异常", Thread.currentThread().getStackTrace());
}
return false;
}
/**
* 自定义路径保存实体列表
* @param szFilePath 保存路径
* @param beanList 实体集合
* @param <T> 实体泛型
* @return 保存结果
*/
public static <T extends BaseBean> boolean saveBeanListToFile(final String szFilePath, final ArrayList<T> beanList) {
LogUtils.d(TAG, "指定路径保存实体列表数据");
try {
final String json = toStringByBeanList(beanList);
UTF8FileUtils.writeStringToFile(szFilePath, json);
return true;
} catch (final IOException e) {
LogUtils.d(TAG, "列表数据写入文件异常", Thread.currentThread().getStackTrace());
}
return false;
}
// ====================== Bean类型一致性校验 ======================
/**
* 校验本地JSON列表内实体类型是否统一
* @param szFilePath 列表文件路径
* @param clazz 目标校验实体Class
* @param <T> 实体泛型
* @return 校验结果信息
*/
public static <T extends BaseBean> String checkIsTheSameBeanListAndFile(final String szFilePath, final Class<T> clazz) {
final StringBuilder sbResult = new StringBuilder();
final String szErrorInfo = "Check Is The Same Bean List And File Error : ";
try {
int sameCount = 0;
int totalCount = 0;
final T beanTemp = clazz.newInstance();
final String targetBeanName = beanTemp.getName();
final String listJson = UTF8FileUtils.readStringFromFile(szFilePath);
final StringReader stringReader = new StringReader(listJson);
final JsonReader jsonReader = new JsonReader(stringReader);
jsonReader.beginArray();
while (jsonReader.hasNext()) {
totalCount++;
jsonReader.beginObject();
while (jsonReader.hasNext()) {
final String name = jsonReader.nextName();
if (BEAN_NAME.equals(name)) {
if (targetBeanName.equals(jsonReader.nextString())) {
sameCount++;
}
} else {
jsonReader.skipValue();
}
}
jsonReader.endObject();
}
jsonReader.endArray();
if (sameCount != totalCount) {
sbResult.append("Total : ").append(totalCount).append(" Diff : ").append(totalCount - sameCount);
}
} catch (final InstantiationException e) {
sbResult.append(szErrorInfo).append(e);
LogUtils.d(TAG, "类型校验反射实例化异常", Thread.currentThread().getStackTrace());
} catch (final IllegalAccessException e) {
sbResult.append(szErrorInfo).append(e);
LogUtils.d(TAG, "类型校验反射权限异常", Thread.currentThread().getStackTrace());
} catch (final IOException e) {
sbResult.append(szErrorInfo).append(e);
LogUtils.d(TAG, "类型校验文件读取解析异常", Thread.currentThread().getStackTrace());
}
return sbResult.toString();
}
}
@@ -1,6 +1,8 @@
package cc.winboll.studio.libappbase.views;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
@@ -12,6 +14,7 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
@@ -74,12 +77,12 @@ public class AboutView extends LinearLayout {
private EditText metDevUserPassword;
// ===================================== 页面视图控件 =====================================
private DebugSwitchImageView ivAppIcon;
private DebugSwitchInfoImageView ivAppIcon;
private TextView tvAppNameVersion;
private TextView tvAppDesc;
private LinearLayout llFunctionContainer;
private ImageButton ibSebugStepOver;
private ImageButton ibSigngetDialog;
private ImageButton ibDebugUnlock;
private ImageButton ibWinBoLLHostDialog;
// ===================================== 构造方法(按参数从少到多排序) =====================================
@@ -193,12 +196,12 @@ public class AboutView extends LinearLayout {
llFunctionContainer = findViewById(R.id.ll_function_container);
// 功能按钮绑定
ibSebugStepOver = findViewById(R.id.ib_debug_step_over);
ibSigngetDialog = findViewById(R.id.ib_signgetdialog);
ibDebugUnlock = findViewById(R.id.ib_debug_unlock);
ibWinBoLLHostDialog = findViewById(R.id.ib_winbollhostdialog);
// 调试按钮统一只在调试模式显示
ibWinBoLLHostDialog.setVisibility(GlobalApplication.isDebugging() ? View.VISIBLE : View.GONE);
//ibSigngetDialog.setVisibility(GlobalApplication.isDebugging() ? View.VISIBLE : View.GONE);
//ibDebugUnlock.setVisibility(GlobalApplication.isDebugging() ? View.VISIBLE : View.GONE);
ibSebugStepOver.setVisibility(GlobalApplication.isDebugging() ? View.VISIBLE : View.GONE);
// 绑定按钮点击事件
@@ -310,6 +313,47 @@ public class AboutView extends LinearLayout {
LogUtils.d(TAG, "initAboutPageView():视图组装完成,功能项加载完毕");
}
// ===================================== 调试解锁弹窗 =====================================
private void showDebugUnlockDialog() {
final AlertDialog dialog = new AlertDialog.Builder(mContext).create();
dialog.setTitle("应用调试解锁");
dialog.setCanceledOnTouchOutside(true);
final EditText etToken = new EditText(mContext);
etToken.setHint("请输入调试Token");
dialog.setView(etToken);
dialog.setButton(DialogInterface.BUTTON_POSITIVE, "调试解锁", (DialogInterface.OnClickListener) null);
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "关闭", (DialogInterface.OnClickListener) null);
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface d) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String inputToken = etToken.getText().toString().trim();
String savedToken = DebugSwitchInfoImageView.getDebugToken();
if (savedToken != null && savedToken.equals(inputToken)) {
GlobalApplication.setIsDebugging(true);
GlobalApplication.saveDebugStatus(GlobalApplication.getInstance());
Toast.makeText(mContext, "调试解锁成功,重启应用后生效", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(mContext, "调试Token不匹配", Toast.LENGTH_SHORT).show();
dialog.dismiss();
}
}
});
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
}
});
dialog.show();
}
// ===================================== 内部工具/事件方法 =====================================
/**
* 绑定功能按钮点击事件,处理正版校验、调试地址配置弹窗唤起
@@ -335,6 +379,15 @@ public class AboutView extends LinearLayout {
new DebugHostDialog(mContext).show();
}
});
// 应用调试解锁按钮
ibDebugUnlock.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "ibDebugUnlock onClick:弹出调试解锁对话框");
showDebugUnlockDialog();
}
});
LogUtils.d(TAG, "setBtnClickListener():功能按钮点击事件绑定完成");
}
@@ -0,0 +1,127 @@
package cc.winboll.studio.libappbase.views;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import java.util.UUID;
import cc.winboll.studio.libappbase.GlobalApplication;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/04/06 19:32
* @Describe 应用Logo控件,连续点击6次弹出调试Token对话框,支持复制与重置
*/
public class DebugSwitchInfoImageView extends ImageView {
public static final String TAG = "DebugSwitchInfoImageView";
// 连续点击计数
private int mClickCount = 0;
// 目标点击次数
private static final int TARGET_CLICK_COUNT = 7;
private static String mDebugToken = null;
private static final String SP_DEBUG_TOKEN = "debug_token_prefs";
private static final String KEY_DEBUG_TOKEN = "debug_token";
public static String getDebugToken() {
if (mDebugToken != null) {
return mDebugToken;
}
Context context = GlobalApplication.getInstance();
if (context != null) {
SharedPreferences sp = context.getSharedPreferences(SP_DEBUG_TOKEN, Context.MODE_PRIVATE);
mDebugToken = sp.getString(KEY_DEBUG_TOKEN, null);
if (mDebugToken == null) {
mDebugToken = UUID.randomUUID().toString();
sp.edit().putString(KEY_DEBUG_TOKEN, mDebugToken).apply();
}
}
return mDebugToken;
}
public static void resetDebugToken() {
Context context = GlobalApplication.getInstance();
if (context != null) {
mDebugToken = UUID.randomUUID().toString();
SharedPreferences sp = context.getSharedPreferences(SP_DEBUG_TOKEN, Context.MODE_PRIVATE);
sp.edit().putString(KEY_DEBUG_TOKEN, mDebugToken).apply();
}
}
private void showDebugTokenDialog() {
final AlertDialog dialog = new AlertDialog.Builder(getContext()).create();
dialog.setTitle("调试Token");
dialog.setMessage(getDebugToken());
dialog.setCanceledOnTouchOutside(false);
dialog.setButton(DialogInterface.BUTTON_POSITIVE, "复制到剪贴板", (DialogInterface.OnClickListener) null);
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, "重置", (DialogInterface.OnClickListener) null);
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "关闭", (DialogInterface.OnClickListener) null);
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface d) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ClipboardManager cm = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText("DebugToken", getDebugToken()));
}
});
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
resetDebugToken();
dialog.setMessage(getDebugToken());
}
});
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
}
});
dialog.show();
}
public DebugSwitchInfoImageView(Context context) {
super(context);
init();
}
public DebugSwitchInfoImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DebugSwitchInfoImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public DebugSwitchInfoImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mClickCount++;
if (mClickCount >= TARGET_CLICK_COUNT) {
mClickCount = 0;
showDebugTokenDialog();
}
}
});
}
}
@@ -15,7 +15,7 @@
android:paddingRight="16dp"
android:paddingBottom="16dp">
<cc.winboll.studio.libappbase.views.DebugSwitchImageView
<cc.winboll.studio.libappbase.views.DebugSwitchInfoImageView
android:id="@+id/iv_app_icon"
android:layout_width="48dp"
android:layout_height="48dp"
@@ -81,7 +81,8 @@
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_key"
android:id="@+id/ib_signgetdialog"
android:id="@+id/ib_debug_unlock"
android:contentDescription="应用调试解锁"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:background="@null"/>
@@ -15,7 +15,7 @@
android:paddingRight="16dp"
android:paddingBottom="16dp">
<cc.winboll.studio.libappbase.views.DebugSwitchImageView
<cc.winboll.studio.libappbase.views.DebugSwitchInfoImageView
android:id="@+id/iv_app_icon"
android:layout_width="48dp"
android:layout_height="48dp"
@@ -81,7 +81,8 @@
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_key"
android:id="@+id/ib_signgetdialog"
android:id="@+id/ib_debug_unlock"
android:contentDescription="应用调试解锁"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:background="@null"/>
+3
View File
@@ -60,4 +60,7 @@
<!-- DebugLogStyle 应用调试日志样式属性 -->
<attr name="debugTextColor" format="color"/>
<!-- 边框圆角属性 -->
<attr name="borderCornerRadius" format="dimension"/>
</resources>
+5
View File
@@ -0,0 +1,5 @@
// AES 项目编译设置
include ':aes'
include ':libaes'
rootProject.name = "aes"
-4
View File
@@ -96,7 +96,3 @@
// AutoNFC 项目编译设置
//include ':autonfc'
//rootProject.name = "autonfc"
// StallHelper 项目编译设置
//include ':stallhelper'
//rootProject.name = "stallhelper"
-4
View File
@@ -1,4 +0,0 @@
# StallHelper
# 排档辅助管理工具
## LargeStall Stand Auxiliary Management Tool.
-1
View File
@@ -1 +0,0 @@
-80
View File
@@ -1,80 +0,0 @@
apply plugin: 'com.android.application'
apply from: '../.winboll/winboll_app_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
def genVersionName(def versionName){
// 检查编译标志位配置
assert (winbollBuildProps['stageCount'] != null)
assert (winbollBuildProps['baseVersion'] != null)
// 保存基础版本号
winbollBuildProps.setProperty("baseVersion", "${versionName}");
//保存编译标志配置
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
fos.close();
// 返回编译版本号
return "${versionName}." + winbollBuildProps['stageCount']
}
android {
compileSdkVersion 30
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
defaultConfig {
applicationId "cc.winboll.studio.stallhelper"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.20"
if(true) {
versionName = genVersionName("${versionName}")
}
}
}
dependencies {
// 下拉控件
api 'com.baoyz.pullrefreshlayout:library:1.2.0'
// 拼音搜索
// https://mvnrepository.com/artifact/com.github.open-android/pinyin4j
api 'com.github.open-android:pinyin4j:2.5.0'
// SSH
api 'com.jcraft:jsch:0.1.55'
// Html 解析
api 'org.jsoup:jsoup:1.13.1'
// 二维码类库
api 'com.google.zxing:core:3.4.1'
api 'com.journeyapps:zxing-android-embedded:3.6.0'
// 应用介绍页类库
api 'io.github.medyo:android-about-page:2.0.0'
// 网络连接类库
api 'com.squareup.okhttp3:okhttp:4.4.1'
// AndroidX 类库
api 'androidx.appcompat:appcompat:1.1.0'
api 'androidx.cardview:cardview:1.0.0'
//api 'com.google.android.material:material:1.4.0'
//api 'androidx.viewpager:viewpager:1.0.0'
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
//api 'androidx.fragment:fragment:1.1.0'
//注意:以下5个库必须要引入
//implementation 'androidx.appcompat:appcompat:1.4.1'
api 'androidx.recyclerview:recyclerview:1.0.0'
api 'com.google.code.gson:gson:2.8.5'
api 'com.github.bumptech.glide:glide:4.9.0'
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// WinBoLL库 nexus.winboll.cc 地址
api 'cc.winboll.studio:libaes:15.20.4'
api 'cc.winboll.studio:libappbase:15.20.15'
api fileTree(dir: 'libs', include: ['*.jar'])
}
-8
View File
@@ -1,8 +0,0 @@
#Created by .winboll/winboll_app_build.gradle
#Mon May 18 19:22:43 HKT 2026
stageCount=3
libraryProject=
baseVersion=15.20
publishVersion=15.20.2
buildCount=0
baseBetaVersion=15.20.3
-143
View File
@@ -1,143 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# ============================== 基础通用规则 ==============================
# 保留系统组件
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
# 保留 WinBoLL 核心包及子类(合并简化规则)
-keep class cc.winboll.studio.** { *; }
-keepclassmembers class cc.winboll.studio.** { *; }
# 保留所有类中的 public static final String TAG 字段(便于日志定位)
-keepclassmembers class * {
public static final java.lang.String TAG;
}
# 保留序列化类(避免Parcelable/Gson解析异常
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保留 R 文件(避免资源ID混淆)
-keepclassmembers class **.R$* {
public static <fields>;
}
# 保留 native 方法(避免JNI调用失败)
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留注解和泛型(避免反射/序列化异常)
-keepattributes *Annotation*
-keepattributes Signature
# 屏蔽 Java 8+ 警告(适配 Java 7 语法)
-dontwarn java.lang.invoke.*
-dontwarn android.support.v8.renderscript.*
-dontwarn java.util.function.**
# ============================== 第三方框架专项规则 ==============================
# OkHttp 4.4.1(米盟广告请求依赖,完善Lambda兼容)
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-keep class okhttp3.internal.** { *; }
-keep class okio.** { *; }
-dontwarn okhttp3.internal.platform.**
-dontwarn okio.**
# ============================== 必要补充规则 ==============================
# OkHttp 4.4.1 补充规则(Java 7 兼容)
-keep class okhttp3.internal.concurrent.** { *; }
-keep class okhttp3.internal.connection.** { *; }
-dontwarn okhttp3.internal.concurrent.TaskRunner
-dontwarn okhttp3.internal.connection.RealCall
# Glide 4.9.0(米盟广告图片加载依赖)
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$ImageType {
**[] $VALUES;
public *;
}
-keepclassmembers class * implements com.bumptech.glide.module.AppGlideModule {
<init>();
}
-dontwarn com.bumptech.glide.**
# Gson 2.8.5(米盟广告数据序列化依赖)
-keep class com.google.gson.** { *; }
-keep interface com.google.gson.** { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# 米盟 SDK(核心广告组件,完整保留避免加载失败)
-keep class com.miui.zeus.** { *; }
-keep interface com.miui.zeus.** { *; }
# 保留米盟日志字段(便于广告加载失败排查)
-keepclassmembers class com.miui.zeus.mimo.sdk.** {
public static final java.lang.String TAG;
}
# RecyclerView 1.0.0(米盟广告布局渲染依赖)
-keep class androidx.recyclerview.** { *; }
-keep interface androidx.recyclerview.** { *; }
-keepclassmembers class androidx.recyclerview.widget.RecyclerView$Adapter {
public *;
}
# 其他第三方框架(按引入依赖保留,无则可删除)
# XXPermissions 18.63
-keep class com.hjq.permissions.** { *; }
-keep interface com.hjq.permissions.** { *; }
# ZXing 二维码(核心解析组件)
-keep class com.google.zxing.** { *; }
-keep class com.journeyapps.zxing.** { *; }
# Jsoup HTML解析
-keep class org.jsoup.** { *; }
# Pinyin4j 拼音搜索
-keep class net.sourceforge.pinyin4j.** { *; }
# JSch SSH组件
-keep class com.jcraft.jsch.** { *; }
# AndroidX 基础组件
-keep class androidx.appcompat.** { *; }
-keep interface androidx.appcompat.** { *; }
# ============================== 优化与调试配置 ==============================
# 优化级别(平衡混淆效果与性能)
-optimizationpasses 5
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 调试辅助(保留行号便于崩溃定位)
-verbose
-dontpreverify
-dontusemixedcaseclassnames
-keepattributes SourceFile,LineNumberTable
-15
View File
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application
tools:replace="android:icon"
android:icon="@drawable/ic_stall_beta">
<!-- Put flavor specific code here -->
</application>
</manifest>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">排档辅助管理工具+</string>
</resources>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">StallHelper+</string>
</resources>
-51
View File
@@ -1,51 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.stallhelper">
<!-- 拥有完全的网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".App"
android:allowBackup="true"
android:icon="@drawable/ic_stall"
android:label="@string/app_name"
android:theme="@style/MyStallHelperTheme"
android:supportsRtl="true">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="standard"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".activities.AboutActivity"
android:label="AboutActivity"/>
<activity
android:name="cc.winboll.studio.stallhelper.activities.PreNullDiningTableActivity"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name="cc.winboll.studio.stallhelper.activities.CompleteDiningTableActivity"
android:windowSoftInputMode="adjustResize"/>
<activity android:name="cc.winboll.studio.stallhelper.activities.NoteRecordHelperActivity"/>
<activity android:name="cc.winboll.studio.stallhelper.activities.TaskSchedulerActivity"/>
</application>
</manifest>
@@ -1,36 +0,0 @@
package cc.winboll.studio.stallhelper;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2025/01/22 00:11:41
* @Describe 全局应用类
*/
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libappbase.BuildConfig;
public class App extends GlobalApplication {
public static final String TAG = "App";
@Override
public void onCreate() {
super.onCreate();
if (isDebugging() != true) {
setIsDebugging(BuildConfig.DEBUG);
}
//setIsDebugging(false);
WinBoLLActivityManager.init(this);
// 初始化 Toast 框架
ToastUtils.init(this);
}
@Override
public void onTerminate() {
super.onTerminate();
ToastUtils.release();
}
}
@@ -1,135 +0,0 @@
package cc.winboll.studio.stallhelper;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.stallhelper.R;
import cc.winboll.studio.stallhelper.activities.AboutActivity;
import cc.winboll.studio.stallhelper.activities.CompleteDiningTableActivity;
import cc.winboll.studio.stallhelper.activities.NoteRecordHelperActivity;
import cc.winboll.studio.stallhelper.activities.PreNullDiningTableActivity;
import cc.winboll.studio.stallhelper.activities.TaskSchedulerActivity;
import cc.winboll.studio.stallhelper.activities.WinBoLLActivity;
final public class MainActivity extends WinBoLLActivity {
public static final String TAG = "MainActivity";
public static final int REQUEST_HOME_ACTIVITY = 0;
public static final int REQUEST_ABOUT_ACTIVITY = 1;
Context mContext;
private Toolbar mToolbar;
// @Override
// protected boolean isEnableDisplayHomeAsUp() {
// return false;
// }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = getApplicationContext();
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
}
@Override
public String getTag() {
return TAG;
}
// @Override
// protected boolean isAddWinBoLLToolBar() {
// return true;
// }
//
// @Override
// protected Toolbar initToolBar() {
// return findViewById(R.id.activitymainToolbar1);
// }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_about) {
startActivity(new Intent(this, AboutActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
void openAboutActivity() {
Intent intent = new Intent(getApplicationContext(), AboutActivity.class);
startActivity(intent);
//App.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), AboutActivity.class);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (resultCode) {
case REQUEST_HOME_ACTIVITY : {
LogUtils.d(TAG, "REQUEST_HOME_ACTIVITY");
break;
}
case REQUEST_ABOUT_ACTIVITY : {
LogUtils.d(TAG, "REQUEST_ABOUT_ACTIVITY");
break;
}
default : {
super.onActivityResult(requestCode, resultCode, data);
}
}
}
public void onPreNullDiningTable(View view) {
//ToastUtils.show("onDiningTable");
Intent intent = new Intent(getApplicationContext(), PreNullDiningTableActivity.class);
startActivity(intent);
//App.getWinBoLLActivityManager().startWinBoLLActivity(mContext, PreNullDiningTableActivity.class);
// try {
// WinBoLLActivity clazzActivity = PreNullDiningTableActivity.class.newInstance();
// String tag = clazzActivity.getTag();
// LogUtils.d(TAG, "String tag = clazzActivity.getTag(); tag " + tag);
// Intent intent = new Intent(getApplicationContext(), PreNullDiningTableActivity.class);
// startWinBoLLActivity(intent, tag);
// } catch (IllegalAccessException e) {} catch (InstantiationException e) {}
}
public void onCompleteDiningTable(View view) {
//ToastUtils.show("onDiningTable");
Intent intent = new Intent(getApplicationContext(), CompleteDiningTableActivity.class);
startActivity(intent);
//App.getWinBoLLActivityManager().startWinBoLLActivity(mContext, CompleteDiningTableActivity.class);
// try {
// WinBoLLActivity clazzActivity = CompleteDiningTableActivity.class.newInstance();
// String tag = clazzActivity.getTag();
// LogUtils.d(TAG, "String tag = clazzActivity.getTag(); tag " + tag);
// Intent intent = new Intent(getApplicationContext(), CompleteDiningTableActivity.class);
// startWinBoLLActivity(intent, tag);
// } catch (IllegalAccessException e) {} catch (InstantiationException e) {}
}
public void onNoteRecordHelper(View view) {
Intent intent = new Intent(getApplicationContext(), NoteRecordHelperActivity.class);
startActivity(intent);
}
public void onTaskScheduler(View view) {
Intent intent = new Intent(getApplicationContext(), TaskSchedulerActivity.class);
startActivity(intent);
}
}
@@ -1,50 +0,0 @@
package cc.winboll.studio.stallhelper.activities;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.stallhelper.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/11 12:55
* @Describe AboutActivity
*/
public class AboutActivity extends AppCompatActivity {
public static final String TAG = "AboutActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
AboutView aboutView = findViewById(R.id.aboutview);
aboutView.setAPPInfo(genDefaultAppInfo());
}
private APPInfo genDefaultAppInfo() {
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
String branchName = "stallhelper";
APPInfo appInfo = new APPInfo();
appInfo.setAppName("StallHelper");
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("StallHelper");
appInfo.setAppGitOwner("ZhanGSKen");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
appInfo.setAppHomePage("http://10.8.0.4:9876/apks/index-manager.php?project=StallHelper");
appInfo.setAppAPKName("StallHelper");
appInfo.setAppAPKFolderName("StallHelper");
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
return appInfo;
}
}
@@ -1,371 +0,0 @@
package cc.winboll.studio.stallhelper.activities;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/10/29 09:22:24
* @Describe 餐桌表补全窗口
*/
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.stallhelper.App;
import cc.winboll.studio.stallhelper.R;
import cc.winboll.studio.stallhelper.beans.DiningTableBean;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
public class CompleteDiningTableActivity extends WinBoLLActivity {
public static final String TAG = "CompleteDiningTableActivity";
// 某一号晚上的餐桌预定补全列表
ArrayList<DiningTableBean> mCompleteTableInfoList;
// 原始预定信息内容输入框
EditText metOriginBookText;
EditText metPreBookList;
EditText metCompleteDate;
ScrollView mScrollView;
ArrayList<String> mszOriginBookList;
@Override
public String getTag() {
return TAG;
}
// @Override
// protected boolean isEnableDisplayHomeAsUp() {
// return true;
// }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_completediningtable);
setTitle("餐桌表补全");
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
StringBuilder sbDateNow = new StringBuilder("当前日期:");
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timeString = sdf.format(now);
sbDateNow.append(timeString);
TextView tvDateNow = findViewById(R.id.activitycompletediningtableTextView2);
tvDateNow.setText(sbDateNow.toString());
metOriginBookText = findViewById(R.id.activitycompletediningtableEditText1);
metPreBookList = findViewById(R.id.activitycompletediningtableEditText2);
metCompleteDate = findViewById(R.id.activitycompletediningtableEditText3);
mScrollView = findViewById(R.id.activitycompletediningtableScrollView1);
}
// @Override
// protected Toolbar initToolBar() {
// return findViewById(R.id.activitycompletediningtableToolbar1);
// }
//
// @Override
// protected boolean isAddWinBoLLToolBar() {
// return false;
// }
boolean isInCompleteRule(DiningTableBean item) {
String szCompleteDate = metCompleteDate.getText().toString() + "";
if (item.getDate().equals(szCompleteDate) && item.getDinnerType() == DiningTableBean.DinnerType.Evening) {
return true;
}
return false;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
WinBoLLActivityManager.getInstance().finish(this);
}
return super.onOptionsItemSelected(item);
}
void createNullCompleteTableInfoList() {
mCompleteTableInfoList = new ArrayList<DiningTableBean>();
String szCompleteDate = metCompleteDate.getText().toString() + "";
mCompleteTableInfoList.add(new DiningTableBean("201", 18, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("202", 8, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("203", 8, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("205", 10, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("206", 0, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("207", 0, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("208", 8, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("209", 10, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("210", 8, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("211", 12, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("212", 8, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("213", 9, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("666", 14, true, szCompleteDate));
mCompleteTableInfoList.add(new DiningTableBean("888", 20, true, szCompleteDate));
for (int i = 1; i < 23; i++) {
DiningTableBean tempBean = new DiningTableBean(Integer.toString(i), 0, true, szCompleteDate);
tempBean.setDinnerType(DiningTableBean.DinnerType.Evening);
tempBean.setTableType(DiningTableBean.TableType.Table);
tempBean.setIsSelected(false);
mCompleteTableInfoList.add(tempBean);
}
//DiningTableBean.saveBeanListToFile(getApplicationContext().getExternalFilesDir("temp") + "/1.json", mCompleteTableInfoList);
//ToastUtils.show("Test");
}
ArrayList<DiningTableBean> getOriginBookList() {
//ToastUtils.show(metOriginBookText.getText().toString());
ArrayList<DiningTableBean> listReturn = new ArrayList<DiningTableBean>();
String[] szOriginBookText = metOriginBookText.getText().toString().split("\\r?\\n");
for (String szTemp : szOriginBookText) {
String szTemp2 = szTemp.replace(" ", "");
DiningTableBean bean = format2DiningTableBean(szTemp2);
if (bean != null) {
listReturn.add(bean);
// 更新补全列表
updateCompleteTableInfoList(bean);
}
}
//ToastUtils.show("szOriginBookText : " + Integer.toString(szOriginBookText.length));
//ToastUtils.show("listReturn : " + Integer.toString(listReturn.size()));
return listReturn;
}
//
// 如果补全列表某一项时间和房号与指定项一致,
// 就把指定项信息更新至补全列表中。
//
void updateCompleteTableInfoList(DiningTableBean beanNew) {
for (DiningTableBean completeTableInfo : mCompleteTableInfoList) {
if (completeTableInfo.getDate().equals(beanNew.getDate())
&& completeTableInfo.getDinnerType() == beanNew.getDinnerType()
&& completeTableInfo.getTableNumber().equals(beanNew.getTableNumber())) {
completeTableInfo.setBookDesc(beanNew.getBookDesc());
completeTableInfo.setIsSelected(true);
}
}
}
//
// 检验每一个数据行的日期格式,
// 如果出现(x号/x月x号)以外的日期标注,
// 则提示有数据行日期标识不清晰的情况。
//
boolean checkLinesDateValidAndShow() {
boolean checkResult = true;
String[] szLines = metOriginBookText.getText().toString().split("\\r?\\n");
if (szLines.length < 1) {
ToastUtils.show("没有数据行。");
checkResult = false;
} else {
StringBuilder sb = new StringBuilder("日期标识不清晰,在");
for (int i = 0; i < szLines.length; i++) {
Pattern pattern = Pattern.compile("(\\d*[月]{0,1}\\d+号)\\s*([晚|午]{0,1}).*");
Matcher matcher = pattern.matcher(szLines[i]);
if (!matcher.find()) {
sb.append("(");
sb.append(Integer.toString(i + 1));
sb.append(")");
checkResult = false;
}
}
sb.append("行。");
if (checkResult == false) {
ToastUtils.show(sb.toString());
}
}
return checkResult;
}
//
// 获取指定日期的第一条数据的位置
// 返回 -1 表示所有数据都没有该日期
//
int getFirstIndexOfDate(ArrayList<DiningTableBean> originBookList, String szDate) {
int nResult = -1;
//ToastUtils.show("szDate " + szDate);
for (int i = originBookList.size() - 1 ; i > 0; i--) {
//LogUtils.d(TAG, "originBookList.get(i).getDate() " + originBookList.get(i).getDate());
if (originBookList.get(i).isDiningTableBean()) {
if (originBookList.get(i).getDate().equals(szDate)) {
nResult = i;
continue;
} else {
return nResult;
}
} else {
nResult = i;
continue;
}
}
return nResult;
}
DiningTableBean format2DiningTableBean(String szBookInfo) {
if (!szBookInfo.trim().equals("")) {
//String sz = "29号晚 201房 123456";
// 定义正则表达式模式
Pattern pattern = Pattern.compile("(\\d*[月]{0,1}\\d+号)\\s*([晚|午]{0,1})\\s*(\\d+)([房|桌])[-\\s]*(.*)");
Matcher matcher = pattern.matcher(szBookInfo);
if (matcher.find()) {
String date = matcher.group(1);
String szDinnerType = matcher.group(2);
String tableNumber = matcher.group(3);
String szTableType = matcher.group(4);
String bookDesc = matcher.group(5);
DiningTableBean.DinnerType dinnerType = DiningTableBean.DinnerType.Evening;
if (szDinnerType.equals("")) {
dinnerType = DiningTableBean.DinnerType.Noon;
}
DiningTableBean.TableType tableType = DiningTableBean.TableType.Room;
if (szTableType.equals("")) {
tableType = DiningTableBean.TableType.Table;
}
return new DiningTableBean(tableNumber, 0, true, dinnerType, bookDesc, date, tableType);
} else {
return new DiningTableBean(true, false, szBookInfo);
}
}
return null;
}
void checkAndAddCompleteTableInfoList(ArrayList<DiningTableBean> originBookList) {
boolean isAddCompleteTableInfoList = false;
for (int i = 0; i < originBookList.size(); i++) {
DiningTableBean item = originBookList.get(i);
// 循环读取原始 Bean 列表,
// 如果遇到第一个匹配指定日期号,
// 就不显示 item 项,并且添加预制好的补全表(一次性添加,后续再遇到同类 item 项则不添加补全表。)。
if (isInCompleteRule(item)) {
// 设置不显示 item 项
item.setIsSelected(false);
// 添加补全列表(一次性添加,后续再遇到同类 item 项则不添加补全表。)
if (isAddCompleteTableInfoList == false) {
originBookList.addAll(i, mCompleteTableInfoList);
// 设置后续遇到同类项时,不再添加补全列表
isAddCompleteTableInfoList = true;
i = i + mCompleteTableInfoList.size();
}
}
}
// 如果遍历完原始 Bean 表后还未添加补全表,
// 就添加一次补全表
if (isAddCompleteTableInfoList == false) {
int nInsertPost = getFirstIndexOfDate(originBookList, metCompleteDate.getText().toString() + "");
//ToastUtils.show("nInsertPost " + Integer.toString(nInsertPost));
if (nInsertPost == -1) {
originBookList.addAll(mCompleteTableInfoList);
} else {
originBookList.addAll(nInsertPost, mCompleteTableInfoList);
}
// 设置后续遇到同类项时,不再添加补全列表
isAddCompleteTableInfoList = true;
}
}
public void onCopyResultText(View view) {
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager) getApplicationContext().getSystemService(Context.CLIPBOARD_SERVICE);
// Creates a new text clip to put on the clipboard
ClipData clip = ClipData.newPlainText("simple text", metPreBookList.getText());
// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);
Toast.makeText(getApplicationContext(), "Copy to clipboard.", Toast.LENGTH_SHORT).show();
}
public void onCleanResultText(View view) {
metPreBookList.setText("");
}
public void onCreatePreBookList(View view) {
if(metCompleteDate.getText().toString().trim().equals("")) {
ToastUtils.show("没有填写补全日期");
return;
}
//checkLinesDateValidAndShow();
createNullCompleteTableInfoList();
ArrayList<DiningTableBean> originBookList = getOriginBookList();
checkAndAddCompleteTableInfoList(originBookList);
StringBuilder sbPreBookList = new StringBuilder();
for (int i = 0; i < originBookList.size(); i++) {
DiningTableBean item = originBookList.get(i);
if (item.isDiningTableBean()) {
if (item.getIsSelected()) {
String szDinnerTyp = "";
if (item.getDinnerType() == DiningTableBean.DinnerType.Evening) {
szDinnerTyp = "";
}
if (item.getDinnerType() == DiningTableBean.DinnerType.Noon) {
szDinnerTyp = "";
}
String szTableType = "";
if (item.getTableType() == DiningTableBean.TableType.Room) {
szTableType = "";
}
if (item.getTableType() == DiningTableBean.TableType.Table) {
szTableType = "";
}
sbPreBookList.append(item.getDate());
//sbPreBookList.append("号");
sbPreBookList.append(szDinnerTyp);
//sbPreBookList.append(" ");
sbPreBookList.append(item.getTableNumber());
sbPreBookList.append(szTableType);
sbPreBookList.append("-");
sbPreBookList.append(item.getBookDesc());
sbPreBookList.append("\n");
}
} else {
sbPreBookList.append(item.getOriginDesc());
sbPreBookList.append("\n");
}
// 不同日期间隔行排版
if (i + 1 != originBookList.size()) {
if (!originBookList.get(i + 1).getDate().equals(originBookList.get(i).getDate())) {
sbPreBookList.append("\n");
}
}
}
//metPreBookList.append("\n");
metPreBookList.append(sbPreBookList.toString());
mScrollView.post(new Runnable() {
@Override
public void run() {
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
}
@@ -1,226 +0,0 @@
package cc.winboll.studio.stallhelper.activities;
import android.graphics.Color;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.BackgroundColorSpan;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.stallhelper.R;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @CreateTime 2026-03-31 00:20:00
* @ModifyTime 2026-03-31 14:47:00
* @Describe 订单笔录辅助窗口,支持房号、桌号、多房间号(含"号"字)、手机号尾4位及'-'后文本高亮,原文完整输出
*/
public class NoteRecordHelperActivity extends AppCompatActivity {
public static final String TAG = "NoteRecordHelperActivity";
// 高亮颜色数组
private final int[] highlightColors = {
Color.parseColor("#4CAF50"),
Color.parseColor("#FF9800"),
Color.parseColor("#E91E63"),
Color.parseColor("#F44336"),
Color.parseColor("#FFC107"),
Color.parseColor("#2196F3")
};
// 正则匹配
// 匹配多房间号(包含后面的"号"字):11-16-17号
private final Pattern PATTERN_MULTI_ROOM = Pattern.compile("(\\d+(?:-\\d+)+\\s*号)");
// 匹配手机号(连续11位或带分隔符)
private final Pattern PATTERN_PHONE = Pattern.compile("(?:\\d{11}|(?:\\d{3,4}[\\s-]?){3,4})");
// 控件
private EditText etContent;
private Button btnHighlight;
private TextView tvHighlightResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_noterecordhelper);
LogUtils.i(TAG, "onCreate: 页面初始化");
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
setTitle("订单笔录辅助");
initViews();
}
/**
* 初始化控件
*/
private void initViews() {
etContent = findViewById(R.id.et_content);
btnHighlight = findViewById(R.id.btn_highlight);
tvHighlightResult = findViewById(R.id.tv_highlight_result);
btnHighlight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.i(TAG, "onClick: 点击高亮按钮");
highlightContent();
}
});
}
/**
* 解析并高亮内容
*/
private void highlightContent() {
LogUtils.i(TAG, "highlightContent: 开始处理高亮");
String raw = etContent.getText().toString();
if (raw.isEmpty()) {
LogUtils.i(TAG, "highlightContent: 输入内容为空,直接返回");
return;
}
String[] lines = raw.split("\\n");
SpannableStringBuilder ssb = new SpannableStringBuilder();
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
// 空行也完整保留
if (line.isEmpty()) {
ssb.append("\n");
continue;
}
int colorIndex = i % highlightColors.length;
int color = highlightColors[colorIndex];
LogUtils.d(TAG, "highlightContent: 处理第" + (i + 1) + "行,颜色索引=" + colorIndex);
processLine(line, ssb, color);
if (i != lines.length - 1) {
ssb.append("\n");
}
}
tvHighlightResult.setText(ssb);
LogUtils.i(TAG, "highlightContent: 高亮处理完成");
}
/**
* 处理单行高亮(原文完整保留)
* @param line 单行文本
* @param ssb 富文本构建器
* @param color 高亮颜色
*/
private void processLine(String line, SpannableStringBuilder ssb, int color) {
LogUtils.d(TAG, "processLine: line=" + line + ", color=" + Integer.toHexString(color));
// 1. 先把原文完整追加到 ssb,不会丢失任何字符
int start = ssb.length();
ssb.append(line);
// 2. 高亮多房间号(包含"号"字):11-16-17号
Matcher mMulti = PATTERN_MULTI_ROOM.matcher(line);
if (mMulti.find()) {
int s = start + mMulti.start(1);
int e = start + mMulti.end(1);
ssb.setSpan(new BackgroundColorSpan(color), s, e, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
// 3. 高亮房号 / 桌号
int roomIdx = line.indexOf("");
int tableIdx = line.indexOf("号桌");
if (roomIdx != -1) {
int s = findNumberStart(line, roomIdx);
int e = roomIdx + 1;
ssb.setSpan(new BackgroundColorSpan(color), start + s, start + e, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (tableIdx != -1) {
int s = findNumberStart(line, tableIdx);
int e = tableIdx + 2;
ssb.setSpan(new BackgroundColorSpan(color), start + s, start + e, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
// 4. 处理'-'后的内容:优先高亮手机号尾4位,否则高亮全部文本
int dashIndex = line.indexOf("-");
if (dashIndex != -1) {
String afterDash = line.substring(dashIndex + 1);
Matcher phoneMatcher = PATTERN_PHONE.matcher(afterDash);
boolean foundPhone = false;
// 尝试匹配手机号并高亮尾4位
while (phoneMatcher.find()) {
String phone = phoneMatcher.group();
if (phone != null) {
foundPhone = true;
// 提取纯数字
String pureNumber = phone.replaceAll("[^0-9]", "");
if (pureNumber.length() >= 4) {
String last4 = pureNumber.substring(pureNumber.length() - 4);
// 在原行中定位尾4位(从 dashIndex+1 开始查找,避免匹配前面的数字)
int tailStart = line.indexOf(last4, dashIndex + 1);
if (tailStart != -1) {
int s = start + tailStart;
int e = s + 4;
ssb.setSpan(new BackgroundColorSpan(color), s, e, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
// 没有手机号,则高亮'-'后的全部文本
if (!foundPhone) {
int s = start + dashIndex + 1;
int e = start + line.length();
ssb.setSpan(new BackgroundColorSpan(color), s, e, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
// 无'-'的行,尝试匹配并高亮手机号尾4位
Matcher phoneMatcher = PATTERN_PHONE.matcher(line);
while (phoneMatcher.find()) {
String phone = phoneMatcher.group();
if (phone != null) {
String pureNumber = phone.replaceAll("[^0-9]", "");
if (pureNumber.length() >= 4) {
String last4 = pureNumber.substring(pureNumber.length() - 4);
int tailStart = line.indexOf(last4);
if (tailStart != -1) {
int s = start + tailStart;
int e = s + 4;
ssb.setSpan(new BackgroundColorSpan(color), s, e, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
}
}
/**
* 查找数字起始位置
* @param line 文本行
* @param endIdx 结束位置
* @return 数字起始下标
*/
private int findNumberStart(String line, int endIdx) {
LogUtils.d(TAG, "findNumberStart: endIdx=" + endIdx);
for (int i = endIdx - 1; i >= 0; i--) {
char c = line.charAt(i);
if (!Character.isDigit(c) && c != ' ') {
return i + 1;
}
}
return 0;
}
}
@@ -1,264 +0,0 @@
package cc.winboll.studio.stallhelper.activities;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/10/16 05:13:42
* @Describe 餐桌空白预定表生成窗口
*/
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.stallhelper.R;
import cc.winboll.studio.stallhelper.beans.DiningTableBean;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
public class PreNullDiningTableActivity extends WinBoLLActivity {
public static final String TAG = "DiningTableActivity";
ArrayList<DiningTableBean> mListTableInfo;
DiningTableBeanListAdapter mDiningTableBeanListAdapter;
EditText metPreBookList;
ScrollView mScrollView;
RadioButton mrbDinnerTypeNoon;
RadioButton mrbDinnerTypeEvening;
@Override
public String getTag() {
return TAG;
}
// @Override
// protected boolean isEnableDisplayHomeAsUp() {
// return true;
// }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_prenulldiningtable);
setTitle("餐桌空白预定表制作");
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
StringBuilder sbDateNow = new StringBuilder("当前日期:");
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timeString = sdf.format(now);
sbDateNow.append(timeString);
TextView tvDateNow = findViewById(R.id.activityprenulldiningtableTextView2);
tvDateNow.setText(sbDateNow.toString());
LocalDate currentDate = LocalDate.now();
int dayOfMonth = currentDate.getDayOfMonth();
EditText etDateNow = findViewById(R.id.activityprenulldiningtableEditText1);
etDateNow.setText(Integer.toString(dayOfMonth));
mListTableInfo = new ArrayList<DiningTableBean>();
addTable();
RecyclerView recyclerView = findViewById(R.id.activityprenulldiningtableRecyclerView1);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext(), LinearLayoutManager.HORIZONTAL, false));
mDiningTableBeanListAdapter = new DiningTableBeanListAdapter(mListTableInfo);
// 设置 RecyclerView 的适配器
recyclerView.setAdapter(mDiningTableBeanListAdapter);
metPreBookList = findViewById(R.id.activityprenulldiningtableEditText2);
mScrollView = findViewById(R.id.activityprenulldiningtableScrollView1);
mrbDinnerTypeNoon = findViewById(R.id.activityprenulldiningtableRadioButton1);
mrbDinnerTypeEvening = findViewById(R.id.activityprenulldiningtableRadioButton2);
mrbDinnerTypeEvening.setChecked(true);
}
//
// @Override
// protected Toolbar initToolBar() {
// return findViewById(R.id.activityprenulldiningtableToolbar1);
// }
//
// @Override
// protected boolean isAddWinBoLLToolBar() {
// return false;
// }
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
WinBoLLActivityManager.getInstance().finish(this);
}
return super.onOptionsItemSelected(item);
}
void addTable() {
mListTableInfo.add(new DiningTableBean("201", 18, false));
mListTableInfo.add(new DiningTableBean("202", 8, false));
mListTableInfo.add(new DiningTableBean("203", 8, false));
mListTableInfo.add(new DiningTableBean("205", 10, false));
mListTableInfo.add(new DiningTableBean("206", 0, false));
mListTableInfo.add(new DiningTableBean("207", 0, false));
mListTableInfo.add(new DiningTableBean("208", 8, false));
mListTableInfo.add(new DiningTableBean("209", 10, false));
mListTableInfo.add(new DiningTableBean("210", 8, false));
mListTableInfo.add(new DiningTableBean("211", 12, false));
mListTableInfo.add(new DiningTableBean("212", 8, false));
mListTableInfo.add(new DiningTableBean("213", 9, false));
mListTableInfo.add(new DiningTableBean("666", 14, false));
mListTableInfo.add(new DiningTableBean("888", 20, false));
}
public void onDinnerType(View view) {
RadioButton rb = (RadioButton)view;
if (rb.getId() == mrbDinnerTypeNoon.getId()) {
ToastUtils.show("午餐");
mrbDinnerTypeEvening.setChecked(false);
} else {
ToastUtils.show("晚餐");
mrbDinnerTypeNoon.setChecked(false);
}
}
public void onCopyResultText(View view) {
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager) getApplicationContext().getSystemService(Context.CLIPBOARD_SERVICE);
// Creates a new text clip to put on the clipboard
ClipData clip = ClipData.newPlainText("simple text", metPreBookList.getText());
// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);
Toast.makeText(getApplicationContext(), "Copy to clipboard.", Toast.LENGTH_SHORT).show();
}
public void onCleanResultText(View view) {
metPreBookList.setText("");
}
public void onCreatePreBookList(View view) {
String szDinnerTyp = mrbDinnerTypeEvening.isChecked() ?"": "";
EditText etDate = findViewById(R.id.activityprenulldiningtableEditText1);
StringBuilder sbPreBookList = new StringBuilder();
for (DiningTableBean item : mListTableInfo) {
if (item.getIsSelected()) {
/*sbPreBookList.append(etDate.getText());
sbPreBookList.append("号 ");
sbPreBookList.append(szDinnerTyp);
sbPreBookList.append(" ");
sbPreBookList.append(item.getTableNumber());
sbPreBookList.append("房-\n");*/
String szTableType = "";
if (item.getTableType() == DiningTableBean.TableType.Room) {
szTableType = "";
}
if (item.getTableType() == DiningTableBean.TableType.Table) {
szTableType = "";
}
sbPreBookList.append(etDate.getText());
sbPreBookList.append("");
sbPreBookList.append(szDinnerTyp);
sbPreBookList.append(" ");
sbPreBookList.append(item.getTableNumber());
sbPreBookList.append(szTableType);
sbPreBookList.append("-");
sbPreBookList.append(item.getBookDesc());
sbPreBookList.append("\n");
}
}
//metPreBookList.append("\n");
metPreBookList.append(sbPreBookList.toString());
mScrollView.post(new Runnable() {
@Override
public void run() {
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
public void onSelectALLTable(View view) {
boolean isSelectAll =((CheckBox)findViewById(R.id.activityprenulldiningtableCheckBox1)).isChecked();
for (DiningTableBean item : mListTableInfo) {
item.setIsSelected(isSelectAll);
}
//ToastUtils.show(Boolean.toString(isSelectAll));
mDiningTableBeanListAdapter.notifyDataSetChanged();
}
class DiningTableBeanListAdapter extends RecyclerView.Adapter {
ArrayList<DiningTableBean> mDataList;
public DiningTableBeanListAdapter(ArrayList<DiningTableBean> listInfo) {
mDataList = listInfo;
}
@Override
public int getItemCount() {
return mDataList.size();
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
final DiningTableBean item = mDataList.get(position);
if (holder.getItemViewType() == 0) {
final SimpleViewHolder viewHolder = (SimpleViewHolder) holder;
viewHolder.mcbTable.setText(item.getTableNumber() + "房(" + Integer.toString(item.getMaxPersonCount()) + "人)");
viewHolder.mcbTable.setChecked(item.getIsSelected());
viewHolder.mcbTable.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
item.setIsSelected(viewHolder.mcbTable.isChecked());
}
});
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 0) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listview_tableinfo, parent, false);
return new SimpleViewHolder(view);
}
return null;
}
class SimpleViewHolder extends RecyclerView.ViewHolder {
CheckBox mcbTable;
SimpleViewHolder(View itemView) {
super(itemView);
mcbTable = itemView.findViewById(R.id.listviewtableinfoCheckBox1);
}
}
}
}
@@ -1,432 +0,0 @@
package cc.winboll.studio.stallhelper.activities;
import android.app.Dialog;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.stallhelper.R;
import cc.winboll.studio.stallhelper.beans.TaskSchedulerBean;
import java.util.ArrayList;
/**
* @Author ZhanGSKen
* @Date 2026/05/18
* @Describe 任务排档窗口
*/
public class TaskSchedulerActivity extends WinBoLLActivity {
public static final String TAG = "TaskSchedulerActivity";
// 时间转换常量
private static final int MINUTES_PER_HOUR = 60;
private static final int HOURS_PER_DAY = 24;
private static final int DAYS_PER_MONTH = 30; // 默认按30天计算
ArrayList<TaskSchedulerBean> mTaskList;
TaskSchedulerAdapter mAdapter;
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(0, 1001, 0, "编辑任务内容");
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (item.getItemId() == 1001) {
View triggerView = item.getActionView();
if (triggerView != null && triggerView.getTag() instanceof Integer) {
int position = (Integer) triggerView.getTag();
showEditTaskDialog(position);
}
return true;
}
return super.onContextItemSelected(item);
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_task_scheduler);
LogUtils.i(TAG, "onCreate: 任务排档页面初始化");
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
setTitle("任务排档");
// 初始化任务列表
mTaskList = new ArrayList<TaskSchedulerBean>();
initSampleData();
// 设置 RecyclerView
RecyclerView recyclerView = findViewById(R.id.task_scheduler_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext(), LinearLayoutManager.VERTICAL, false));
mAdapter = new TaskSchedulerAdapter(mTaskList);
recyclerView.setAdapter(mAdapter);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
WinBoLLActivityManager.getInstance().finish(this);
}
return super.onOptionsItemSelected(item);
}
/**
* 格式化时间戳为年月日和小时
* @param timestamp 时间戳
* @return 格式化的时间字符串,如 "2026-05-18 09"
*/
private String formatTimestamp(long timestamp) {
if (timestamp == 0) {
return "";
}
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH");
java.util.Date date = new java.util.Date(timestamp);
return sdf.format(date);
}
/**
* 解析时间字符串为时间戳
* @param timeStr 时间字符串,如 "2026-05-18 09"
* @return 时间戳
*/
private long parseTimeString(String timeStr) {
if (timeStr == null || timeStr.trim().isEmpty()) {
return 0;
}
try {
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH");
java.util.Date date = sdf.parse(timeStr);
return date.getTime();
} catch (Exception e) {
LogUtils.e(TAG, "parseTimeString: 时间解析错误", e);
return 0;
}
}
/**
* 初始化示例数据
*/
private void initSampleData() {
// 创建今天的日期作为基础
java.util.Calendar calendar = java.util.Calendar.getInstance();
calendar.set(java.util.Calendar.MINUTE, 0);
calendar.set(java.util.Calendar.SECOND, 0);
calendar.set(java.util.Calendar.MILLISECOND, 0);
// 设置不同的小时
calendar.set(java.util.Calendar.HOUR_OF_DAY, 9);
long time9 = calendar.getTimeInMillis();
calendar.set(java.util.Calendar.HOUR_OF_DAY, 10);
long time10 = calendar.getTimeInMillis();
calendar.set(java.util.Calendar.HOUR_OF_DAY, 11);
calendar.set(java.util.Calendar.MINUTE, 30);
long time1130 = calendar.getTimeInMillis();
calendar.set(java.util.Calendar.HOUR_OF_DAY, 12);
calendar.set(java.util.Calendar.MINUTE, 30);
long time1230 = calendar.getTimeInMillis();
calendar.set(java.util.Calendar.HOUR_OF_DAY, 16);
calendar.set(java.util.Calendar.MINUTE, 0);
long time16 = calendar.getTimeInMillis();
calendar.set(java.util.Calendar.HOUR_OF_DAY, 17);
long time17 = calendar.getTimeInMillis();
mTaskList.add(new TaskSchedulerBean("准备食材", "购买新鲜蔬菜和肉类", time9, 60));
mTaskList.add(new TaskSchedulerBean("烹饪午餐", "按照食谱准备午餐", time10, 90));
mTaskList.add(new TaskSchedulerBean("用餐休息", "享受美食并休息", time1130, 60));
mTaskList.add(new TaskSchedulerBean("清洁厨房", "清洗餐具和整理厨房", time1230, 45));
mTaskList.add(new TaskSchedulerBean("准备晚餐", "提前准备晚餐食材", time16, 60));
mTaskList.add(new TaskSchedulerBean("烹饪晚餐", "制作丰盛的晚餐", time17, 90));
}
/**
* 将月、天、小时转换为总分钟数
* @param month 月份
* @param day 天数
* @param hour 小时
* @return 总分钟数
*/
private int convertToTotalMinutes(int month, int day, int hour) {
return month * DAYS_PER_MONTH * HOURS_PER_DAY * MINUTES_PER_HOUR +
day * HOURS_PER_DAY * MINUTES_PER_HOUR +
hour * MINUTES_PER_HOUR;
}
/**
* 将总分钟数转换为月、天、小时的格式字符串
* @param totalMinutes 总分钟数
* @return 格式化的字符串,如 "0月0天1小时"
*/
private String formatDurationToString(int totalMinutes) {
int totalHours = totalMinutes / MINUTES_PER_HOUR;
int totalDays = totalHours / HOURS_PER_DAY;
int month = totalDays / DAYS_PER_MONTH;
int day = totalDays % DAYS_PER_MONTH;
int hour = totalHours % HOURS_PER_DAY;
StringBuilder sb = new StringBuilder();
if (month > 0) {
sb.append(month).append("");
}
if (day > 0) {
sb.append(day).append("");
}
if (hour > 0) {
sb.append(hour).append("小时");
}
if (sb.length() == 0) {
sb.append("0小时");
}
return sb.toString();
}
/**
* 显示编辑任务对话框
* @param position 要编辑的任务位置
*/
private void showEditTaskDialog(final int position) {
if (position < 0 || position >= mTaskList.size()) {
return;
}
final TaskSchedulerBean task = mTaskList.get(position);
final Dialog dialog = new Dialog(this);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.dialog_task_edit);
// 初始化对话框控件
final EditText etTaskName = dialog.findViewById(R.id.dialog_et_task_name);
final EditText etCurrentScheduleTime = dialog.findViewById(R.id.dialog_et_start_time);
final EditText etMonth = dialog.findViewById(R.id.dialog_et_month);
final EditText etDay = dialog.findViewById(R.id.dialog_et_day);
final EditText etHour = dialog.findViewById(R.id.dialog_et_hour);
final EditText etRemark = dialog.findViewById(R.id.dialog_et_remark);
Button btnCancel = dialog.findViewById(R.id.dialog_btn_cancel);
Button btnSave = dialog.findViewById(R.id.dialog_btn_save);
// 填充现有数据 - 将分钟转换为月、天、小时
etTaskName.setText(task.getTaskName());
etCurrentScheduleTime.setText(formatTimestamp(task.getCurrentScheduleTime()));
etRemark.setText(task.getRemark());
// 反向转换:将分钟转换为月、天、小时
int totalMinutes = task.getDurationMinutes();
int totalHours = totalMinutes / MINUTES_PER_HOUR;
int totalDays = totalHours / HOURS_PER_DAY;
int month = totalDays / DAYS_PER_MONTH;
int day = totalDays % DAYS_PER_MONTH;
int hour = totalHours % HOURS_PER_DAY;
if (month > 0) {
etMonth.setText(Integer.toString(month));
}
if (day > 0) {
etDay.setText(Integer.toString(day));
}
if (hour > 0) {
etHour.setText(Integer.toString(hour));
}
// 取消按钮
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
// 保存按钮
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 获取输入的数据
String taskName = etTaskName.getText().toString().trim();
String currentScheduleTime = etCurrentScheduleTime.getText().toString().trim();
String monthStr = etMonth.getText().toString().trim();
String dayStr = etDay.getText().toString().trim();
String hourStr = etHour.getText().toString().trim();
String remark = etRemark.getText().toString().trim();
// 验证输入
if (taskName.isEmpty()) {
etTaskName.setError("请输入任务名称");
return;
}
if (currentScheduleTime.isEmpty()) {
etCurrentScheduleTime.setError("请输入当前排档时间");
return;
}
// 解析时间字符串为时间戳
long timestamp = parseTimeString(currentScheduleTime);
if (timestamp == 0) {
etCurrentScheduleTime.setError("时间格式不正确,请使用 yyyy-MM-dd HH 格式");
return;
}
// 解析排档幅度 - 月、天、小时
int month = 0;
int day = 0;
int hour = 0;
try {
if (!monthStr.isEmpty()) {
month = Integer.parseInt(monthStr);
}
if (!dayStr.isEmpty()) {
day = Integer.parseInt(dayStr);
}
if (!hourStr.isEmpty()) {
hour = Integer.parseInt(hourStr);
}
} catch (NumberFormatException e) {
LogUtils.e(TAG, "showEditTaskDialog: 数字解析错误", e);
return;
}
// 转换为总分钟数
int durationMinutes = convertToTotalMinutes(month, day, hour);
// 更新任务数据
task.setTaskName(taskName);
task.setCurrentScheduleTime(timestamp);
task.setDurationMinutes(durationMinutes);
task.setRemark(remark);
// 刷新列表
mAdapter.notifyItemChanged(position);
LogUtils.i(TAG, "showEditTaskDialog: 任务 [" + taskName + "] 数据已更新,排档幅度=" + durationMinutes + "分钟");
dialog.dismiss();
}
});
dialog.show();
}
/**
* 执行排档操作
* @param position 点击排档按钮的任务位置
*/
private void performSchedule(int position) {
if (position < 0 || position >= mTaskList.size()) {
return;
}
TaskSchedulerBean currentTask = mTaskList.get(position);
long currentTime = currentTask.getCurrentScheduleTime();
int duration = currentTask.getDurationMinutes();
// 计算新的时间(时间戳加上分钟数)
long newTime = currentTime + duration * 60 * 1000L;
LogUtils.i(TAG, "performSchedule: 当前时间 " + formatTimestamp(currentTime) + " + " + duration + "分钟 = " + formatTimestamp(newTime));
// 更新下一个任务的时间(如果有)
if (position + 1 < mTaskList.size()) {
TaskSchedulerBean nextTask = mTaskList.get(position + 1);
nextTask.setCurrentScheduleTime(newTime);
mAdapter.notifyItemChanged(position + 1);
LogUtils.i(TAG, "performSchedule: 更新任务 [" + nextTask.getTaskName() + "] 时间更新为 " + formatTimestamp(newTime));
}
}
/**
* RecyclerView 适配器
*/
class TaskSchedulerAdapter extends RecyclerView.Adapter {
ArrayList<TaskSchedulerBean> mDataList;
public TaskSchedulerAdapter(ArrayList<TaskSchedulerBean> dataList) {
mDataList = dataList;
}
@Override
public int getItemCount() {
return mDataList.size();
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
final TaskSchedulerBean item = mDataList.get(position);
final TaskViewHolder viewHolder = (TaskViewHolder) holder;
viewHolder.mtvTaskName.setText(item.getTaskName());
viewHolder.mtvCurrentScheduleTime.setText(formatTimestamp(item.getCurrentScheduleTime()));
// 格式化显示排档幅度
viewHolder.mtvDuration.setText(formatDurationToString(item.getDurationMinutes()));
viewHolder.mtvRemark.setText(item.getRemark());
// mllMain长按存入当前条目独立位置Tag,互不干扰
viewHolder.mllMain.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
v.setTag(position);
registerForContextMenu(v);
openContextMenu(v);
return true;
}
});
// 设置排档按钮点击事件
viewHolder.mbtnSchedule.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
performSchedule(position);
}
});
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_task_scheduler, parent, false);
return new TaskViewHolder(view);
}
class TaskViewHolder extends RecyclerView.ViewHolder {
TextView mtvTaskName;
TextView mtvCurrentScheduleTime;
TextView mtvDuration;
TextView mtvRemark;
LinearLayout mllMain;
Button mbtnSchedule;
TaskViewHolder(View itemView) {
super(itemView);
mtvTaskName = itemView.findViewById(R.id.item_task_name);
mtvCurrentScheduleTime = itemView.findViewById(R.id.item_start_time);
mtvDuration = itemView.findViewById(R.id.item_duration);
mtvRemark = itemView.findViewById(R.id.item_remark);
mllMain = itemView.findViewById(R.id.itemtaskscheduler_llmain);
mbtnSchedule = itemView.findViewById(R.id.item_btn_schedule);
}
}
}
}
@@ -1,82 +0,0 @@
package cc.winboll.studio.stallhelper.activities;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/28 15:34:16
* @Describe 应用活动窗口基类
*/
import android.app.Activity;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.stallhelper.App;
import cc.winboll.studio.stallhelper.R;
public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
public static final String TAG = "WinBoLLActivityBase";
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
WinBoLLActivityManager getWinBoLLActivityManager() {
return WinBoLLActivityManager.getInstance();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWinBoLLActivityManager().add(this);
}
@Override
public void onPostCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onPostCreate(savedInstanceState, persistentState);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// if (item.getItemId() == cc.winboll.studio.appbase.R.id.item_log) {
// GlobalApplication.getWinBoLLActivityManager().startLogActivity(this);
// return true;
// } else if(item.getItemId() == cc.winboll.studio.appbase.R.id.item_minimal) {
// //moveTaskToBack(true);
// exit();
// }
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
void exit() {
YesNoAlertDialog.show(this, "Exit " + getString(R.string.app_name), "Close all activity and exit?", new YesNoAlertDialog.OnDialogResultListener(){
@Override
public void onYes() {
WinBoLLActivityManager.getInstance().finishAll();
}
@Override
public void onNo() {
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
getWinBoLLActivityManager().registeRemove(this);
}
}
@@ -1,216 +0,0 @@
package cc.winboll.studio.stallhelper.beans;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/10/16 06:09:54
* @Describe 餐桌
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class DiningTableBean extends BaseBean {
public static final String TAG = "AuthenticationBean";
public enum DinnerType { Noon, Evening };
public enum TableType { Room, Table };
// 餐桌号
String tableNumber;
// 最大就餐人数
int maxPersonCount;
// 是否选取了这张桌号
boolean isSelected;
// 预定时间类型
DinnerType dinnerType;
// 预定信息
String bookDesc;
// 预定时间
String date;
// 餐桌类型
TableType tableType;
// 是否被识别为 DiningTableBean
boolean isDiningTableBean;
// 或者是无法识别为 DiningTableBean 的原始信息。
String originDesc;
public DiningTableBean(String tableNumber, int maxPersonCount, boolean isSelected) {
this.tableNumber = tableNumber;
this.maxPersonCount = maxPersonCount;
this.isSelected = isSelected;
this.dinnerType = DinnerType.Evening;
this.bookDesc = "";
this.date = "";
this.tableType = TableType.Room;
this.isDiningTableBean = true;
this.originDesc = "";
}
public DiningTableBean(String tableNumber, int maxPersonCount, boolean isSelected, String date) {
this.tableNumber = tableNumber;
this.maxPersonCount = maxPersonCount;
this.isSelected = isSelected;
this.dinnerType = DinnerType.Evening;
this.bookDesc = "";
this.date = date;
this.tableType = TableType.Room;
this.isDiningTableBean = true;
this.originDesc = "";
}
public DiningTableBean(String tableNumber, int maxPersonCount, boolean isSelected, DinnerType dinnerType, String bookDesc, String date, TableType tableType) {
this.tableNumber = tableNumber;
this.maxPersonCount = maxPersonCount;
this.isSelected = isSelected;
this.dinnerType = dinnerType;
this.bookDesc = bookDesc;
this.date = date;
this.tableType = tableType;
this.isDiningTableBean = true;
this.originDesc = "";
}
public DiningTableBean(boolean isSelected, boolean isDiningTableBean, String originDesc) {
this.tableNumber = "";
this.maxPersonCount = 0;
this.isSelected = isSelected;
this.dinnerType = DinnerType.Evening;
this.bookDesc = "";
this.date = "";
this.tableType = TableType.Room;
this.isDiningTableBean = isDiningTableBean;
this.originDesc = originDesc;
}
public void setOriginDesc(String originDesc) {
this.originDesc = originDesc;
}
public String getOriginDesc() {
return originDesc;
}
public void setIsDiningTableBean(boolean isDiningTableBean) {
this.isDiningTableBean = isDiningTableBean;
}
public boolean isDiningTableBean() {
return isDiningTableBean;
}
public void setTableType(TableType tableType) {
this.tableType = tableType;
}
public TableType getTableType() {
return tableType;
}
public void setDate(String date) {
this.date = date;
}
public String getDate() {
return date;
}
public void setDinnerType(DinnerType dinnerType) {
this.dinnerType = dinnerType;
}
public DinnerType getDinnerType() {
return dinnerType;
}
public void setBookDesc(String bookDesc) {
this.bookDesc = bookDesc;
}
public String getBookDesc() {
return bookDesc;
}
public void setIsSelected(boolean isSelected) {
this.isSelected = isSelected;
}
public boolean getIsSelected() {
return isSelected;
}
public void setTableNumber(String tableNumber) {
this.tableNumber = tableNumber;
}
public String getTableNumber() {
return tableNumber;
}
public void setMaxPersonCount(int maxPersonCount) {
this.maxPersonCount = maxPersonCount;
}
public int getMaxPersonCount() {
return maxPersonCount;
}
@Override
public String getName() {
return DiningTableBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
DiningTableBean bean = this;
jsonWriter.name("tableNumber").value(bean.getTableNumber());
jsonWriter.name("maxPersonCount").value(bean.getMaxPersonCount());
jsonWriter.name("isSelected").value(bean.getTableNumber());
jsonWriter.name("dinnerType").value(bean.getDinnerType().ordinal());
jsonWriter.name("bookDesc").value(bean.getBookDesc());
jsonWriter.name("date").value(bean.getDate());
jsonWriter.name("tableType").value(bean.getTableType().ordinal());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("tableNumber")) {
setTableNumber(jsonReader.nextString());
} else if (name.equals("maxPersonCount")) {
setMaxPersonCount(jsonReader.nextInt());
} else if (name.equals("isSelected")) {
setIsSelected(jsonReader.nextBoolean());
} else if (name.equals("dinnerType")) {
setDinnerType(DinnerType.values()[jsonReader.nextInt()]);
} else if (name.equals("bookDesc")) {
setBookDesc(jsonReader.nextString());
} else if (name.equals("date")) {
setDate(jsonReader.nextString());
} else if (name.equals("tableType")) {
setTableType(TableType.values()[jsonReader.nextInt()]);
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}
@@ -1,117 +0,0 @@
package cc.winboll.studio.stallhelper.beans;
/**
* @Author ZhanGSKen
* @Date 2026/05/18
* @Describe 任务排档数据模型
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class TaskSchedulerBean extends BaseBean {
public static final String TAG = "TaskSchedulerBean";
// 任务排档名称
String taskName;
// 备注
String remark;
// 当前排档时间(时间戳)
long currentScheduleTime;
// 排档时间幅度(分钟)- 用于计算下一个任务的起始时间
int durationMinutes;
public TaskSchedulerBean() {
this.taskName = "";
this.remark = "";
this.currentScheduleTime = 0;
this.durationMinutes = 0;
}
public TaskSchedulerBean(String taskName, String remark, long currentScheduleTime, int durationMinutes) {
this.taskName = taskName;
this.remark = remark;
this.currentScheduleTime = currentScheduleTime;
this.durationMinutes = durationMinutes;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public long getCurrentScheduleTime() {
return currentScheduleTime;
}
public void setCurrentScheduleTime(long currentScheduleTime) {
this.currentScheduleTime = currentScheduleTime;
}
public int getDurationMinutes() {
return durationMinutes;
}
public void setDurationMinutes(int durationMinutes) {
this.durationMinutes = durationMinutes;
}
@Override
public String getName() {
return TaskSchedulerBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
TaskSchedulerBean bean = this;
jsonWriter.name("taskName").value(bean.getTaskName());
jsonWriter.name("remark").value(bean.getRemark());
jsonWriter.name("startTime").value(bean.getCurrentScheduleTime());
jsonWriter.name("durationMinutes").value(bean.getDurationMinutes());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("taskName")) {
setTaskName(jsonReader.nextString());
} else if (name.equals("remark")) {
setRemark(jsonReader.nextString());
} else if (name.equals("startTime")) {
setCurrentScheduleTime(jsonReader.nextLong());
} else if (name.equals("durationMinutes")) {
setDurationMinutes(jsonReader.nextInt());
} else {
return false;
}
}
return true;
}
@Override
public TaskSchedulerBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
jsonReader.endObject();
return this;
}
}
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 阴影部分 -->
<!-- 个人觉得更形象的表达:top代表下边的阴影高度,left代表右边的阴影宽度。其实也就是相对应的offset,solid中的颜色是阴影的颜色,也可以设置角度等等 -->
<item
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#0F000000"
android:startColor="#0F000000" />
<corners
android:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</shape>
</item>
<!-- 背景部分 -->
<!-- 形象的表达:bottom代表背景部分在上边缘超出阴影的高度,right代表背景部分在左边超出阴影的宽度(相对应的offset) -->
<item
android:left="3dp"
android:top="3dp"
android:right="3dp"
android:bottom="5dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#0FFFFFFF"
android:startColor="#FFFFFFFF" />
<corners
android:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</shape>
</item>
</layer-list>
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 阴影部分 -->
<!-- 个人觉得更形象的表达:top代表下边的阴影高度,left代表右边的阴影宽度。其实也就是相对应的offset,solid中的颜色是阴影的颜色,也可以设置角度等等 -->
<item
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#0F000000"
android:startColor="#0F000000" />
<corners
android:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</shape>
</item>
<!-- 背景部分 -->
<!-- 形象的表达:bottom代表背景部分在上边缘超出阴影的高度,right代表背景部分在左边超出阴影的宽度(相对应的offset) -->
<item
android:left="3dp"
android:top="3dp"
android:right="3dp"
android:bottom="5dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#AF000000"
android:startColor="#AF000000" />
<corners
android:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</shape>
</item>
</layer-list>
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 阴影部分 -->
<!-- 个人觉得更形象的表达:top代表下边的阴影高度,left代表右边的阴影宽度。其实也就是相对应的offset,solid中的颜色是阴影的颜色,也可以设置角度等等 -->
<item
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#0FFFFFFF"
android:startColor="#0FFFFFFF" />
<corners
android:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</shape>
</item>
<!-- 背景部分 -->
<!-- 形象的表达:bottom代表背景部分在上边缘超出阴影的高度,right代表背景部分在左边超出阴影的宽度(相对应的offset) -->
<item
android:left="3dp"
android:top="3dp"
android:right="3dp"
android:bottom="5dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#CFFFFFFF"
android:startColor="#CFFFFFFF" />
<corners
android:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</shape>
</item>
</layer-list>
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 阴影部分 -->
<!-- 个人觉得更形象的表达:top代表下边的阴影高度,left代表右边的阴影宽度。其实也就是相对应的offset,solid中的颜色是阴影的颜色,也可以设置角度等等 -->
<item
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#0F000000"
android:startColor="#0F000000" />
<corners
android:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</shape>
</item>
<!-- 背景部分 -->
<!-- 形象的表达:bottom代表背景部分在上边缘超出阴影的高度,right代表背景部分在左边超出阴影的宽度(相对应的offset) -->
<item
android:left="3dp"
android:top="3dp"
android:right="3dp"
android:bottom="5dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="@color/colorAccent"
android:startColor="@color/colorAccent" />
<corners
android:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</shape>
</item>
</layer-list>
@@ -1,69 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="256"
android:viewportHeight="256">
<path
android:fillColor="#FF977A1F"
android:strokeColor="#FF977A1F"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M67.56 132.48C67.56 132.48 196.61 132.48 196.61 132.48 196.61 132.48 196.61 140.79 196.61 140.79 196.61 140.79 67.56 140.79 67.56 140.79 67.56 140.79 67.56 132.48 67.56 132.48"/>
<path
android:fillColor="#FF977A1F"
android:strokeColor="#FF977A1F"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M100.51 140.79C100.51 140.79 108.38 140.79 108.38 140.79 108.38 140.79 108.38 211.22 108.38 211.22 108.38 211.22 100.51 211.22 100.51 211.22 100.51 211.22 100.51 140.79 100.51 140.79"/>
<path
android:fillColor="#FF977A1F"
android:strokeColor="#FF977A1F"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M157.4 140.79C157.4 140.79 165.72 140.79 165.72 140.79 165.72 140.79 165.72 211.22 165.72 211.22 165.72 211.22 157.4 211.22 157.4 211.22 157.4 211.22 157.4 140.79 157.4 140.79"/>
<path
android:fillColor="#FF977A1F"
android:strokeColor="#FF977A1F"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M32.05 85.46C32.05 85.46 38.61 85.46 38.61 85.46 38.61 85.46 38.61 211.22 38.61 211.22 38.61 211.22 32.05 211.22 32.05 211.22 32.05 211.22 32.05 85.46 32.05 85.46"/>
<path
android:fillColor="#FF977A1F"
android:strokeColor="#FF977A1F"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M38.61 160.26C38.61 160.26 77.24 160.26 77.24 160.26 77.24 160.26 77.24 165.51 77.24 165.51 77.24 165.51 38.61 165.51 38.61 165.51 38.61 165.51 38.61 160.26 38.61 160.26"/>
<path
android:fillColor="#FF977A1F"
android:strokeColor="#FF977A1F"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M73.52 160.26C73.52 160.26 80.96 160.26 80.96 160.26 80.96 160.26 80.96 211.22 80.96 211.22 80.96 211.22 73.52 211.22 73.52 211.22 73.52 211.22 73.52 160.26 73.52 160.26"/>
<path
android:fillColor="#FF977A1F"
android:strokeColor="#FF977A1F"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M226.71 85.46C226.71 85.46 233.27 85.46 233.27 85.46 233.27 85.46 233.27 211.22 233.27 211.22 233.27 211.22 226.71 211.22 226.71 211.22 226.71 211.22 226.71 85.46 226.71 85.46"/>
<path
android:fillColor="#FF977A1F"
android:strokeColor="#FF977A1F"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M188.08 157.64C188.08 157.64 226.71 157.64 226.71 157.64 226.71 157.64 226.71 162.89 226.71 162.89 226.71 162.89 188.08 162.89 188.08 162.89 188.08 162.89 188.08 157.64 188.08 157.64"/>
<path
android:fillColor="#FF977A1F"
android:strokeColor="#FF977A1F"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M188.08 157.64C188.08 157.64 195.52 157.64 195.52 157.64 195.52 157.64 195.52 208.59 195.52 208.59 195.52 208.59 188.08 208.59 188.08 208.59 188.08 208.59 188.08 157.64 188.08 157.64"/>
</vector>
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:clickable="true">
<item android:drawable="@drawable/ic_launcher_background"/>
<item
android:left="15dp"
android:top="15dp"
android:right="15dp"
android:bottom="15dp"
android:drawable="@drawable/ic_launcher_foreground"/>
</layer-list>
@@ -1,170 +0,0 @@
<?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="@color/colorPrimary"
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>
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M16.61,15.15C16.15,15.15 15.77,14.78 15.77,14.32S16.15,13.5 16.61,13.5H16.61C17.07,13.5 17.45,13.86 17.45,14.32C17.45,14.78 17.07,15.15 16.61,15.15M7.41,15.15C6.95,15.15 6.57,14.78 6.57,14.32C6.57,13.86 6.95,13.5 7.41,13.5H7.41C7.87,13.5 8.24,13.86 8.24,14.32C8.24,14.78 7.87,15.15 7.41,15.15M16.91,10.14L18.58,7.26C18.67,7.09 18.61,6.88 18.45,6.79C18.28,6.69 18.07,6.75 18,6.92L16.29,9.83C14.95,9.22 13.5,8.9 12,8.91C10.47,8.91 9,9.24 7.73,9.82L6.04,6.91C5.95,6.74 5.74,6.68 5.57,6.78C5.4,6.87 5.35,7.08 5.44,7.25L7.1,10.13C4.25,11.69 2.29,14.58 2,18H22C21.72,14.59 19.77,11.7 16.91,10.14H16.91Z"/>
</vector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

@@ -1,62 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="256"
android:viewportHeight="256">
<!-- 背景圆形 -->
<path
android:fillColor="#FF4CAF50"
android:strokeColor="#FF4CAF50"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M128,44 C81.4,44 44,81.4 44,128 C44,174.6 81.4,212 128,212 C174.6,212 212,174.6 212,128 C212,81.4 174.6,44 128,44 Z"/>
<!-- 文档/列表背景 -->
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="2.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M85,80 L171,80 L171,180 L85,180 L85,80 Z"/>
<!-- 列表线1 -->
<path
android:fillColor="#FF333333"
android:strokeColor="#FF333333"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M100,100 L156,100"/>
<!-- 列表线2 -->
<path
android:fillColor="#FF333333"
android:strokeColor="#FF333333"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M100,120 L156,120"/>
<!-- 列表线3 -->
<path
android:fillColor="#FF333333"
android:strokeColor="#FF333333"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M100,140 L156,140"/>
<!-- 勾选标记 -->
<path
android:fillColor="#FF4CAF50"
android:strokeColor="#FF4CAF50"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M95,98 L101,104 L115,90"/>
<!-- 时钟指针暗示 -->
<path
android:fillColor="#FF4CAF50"
android:strokeColor="#FF4CAF50"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M128,150 L128,168 L144,168"/>
</vector>
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="180"
android:endColor="#FFFFFFFF"
android:startColor="#FFFFFFFF"
android:type="linear" />
<corners android:radius="10dp" />
</shape>
@@ -1,23 +0,0 @@
<?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:background="?attr/activityBackgroundColor">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<cc.winboll.studio.libappbase.views.AboutView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:id="@+id/aboutview"/>
</LinearLayout>
@@ -1,138 +0,0 @@
<?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:background="?attr/activityBackgroundColor">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前日期:"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/activitycompletediningtableTextView2"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="补全设置:"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<EditText
android:layout_width="wrap_content"
android:ems="10"
android:hint="输入(N或N月N)号"
android:layout_height="wrap_content"
android:id="@+id/activitycompletediningtableEditText3"
android:layout_weight="1.0"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="号晚"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</LinearLayout>
<RelativeLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="清空"
android:layout_toLeftOf="@id/activitycompletediningtableButton2"
android:id="@+id/activitycompletediningtableButton3"
android:onClick="onCleanResultText"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="复制"
android:layout_toLeftOf="@id/activitycompletediningtableButton1"
android:id="@+id/activitycompletediningtableButton2"
android:onClick="onCopyResultText"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="补全订餐列表"
android:id="@+id/activitycompletediningtableButton1"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:onClick="onCreatePreBookList"/>
</RelativeLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/activitycompletediningtableScrollView2"
android:layout_weight="1.0">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top"
android:id="@+id/activitycompletediningtableEditText1"/>
</LinearLayout>
</ScrollView>
<View
android:layout_width="match_parent"
android:layout_height="5dp"
android:background="#ccc"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/activitycompletediningtableScrollView1"
android:layout_weight="1.0">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top"
android:id="@+id/activitycompletediningtableEditText2"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
@@ -1,119 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/activityBackgroundColor">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<LinearLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="@drawable/bg_frame"
android:onClick="onPreNullDiningTable">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPreNullBackgroung"
android:src="@drawable/diningtable"
android:layout_margin="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="餐桌空白预定表制作"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="@drawable/bg_frame"
android:onClick="onCompleteDiningTable">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorCompleteBackgroung"
android:src="@drawable/diningtable"
android:layout_margin="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="订餐列表补全"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="@drawable/bg_frame"
android:onClick="onNoteRecordHelper">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorCompleteBackgroung"
android:src="@drawable/ic_noterecordhelper"
android:layout_margin="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="订单笔录辅助"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="@drawable/bg_frame"
android:onClick="onTaskScheduler">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPreNullBackgroung"
android:src="@drawable/ic_task_scheduler"
android:layout_margin="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="任务排档"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
@@ -1,83 +0,0 @@
<?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="match_parent"
android:orientation="vertical"
android:background="?attr/activityBackgroundColor">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="订单笔录辅助"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center"
android:paddingBottom="16dp"/>
<!-- 输入区域 -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:fillViewport="true">
<EditText
android:id="@+id/et_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="请输入订单内容,每行一条记录"
android:gravity="top|start"
android:background="@android:color/white"
android:textColor="@android:color/black"
android:textSize="18sp"
android:padding="12dp"
android:inputType="textMultiLine|textNoSuggestions"
android:minLines="10"
android:scrollbars="vertical"/>
</ScrollView>
<Button
android:id="@+id/btn_highlight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="一键高亮标记"
android:layout_marginTop="12dp"/>
<!-- 结果区域:关键是 fillViewport -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:layout_marginTop="12dp"
android:fillViewport="true">
<TextView
android:id="@+id/tv_highlight_result"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:padding="12dp"
android:textSize="18sp"
android:gravity="top|start"
android:scrollbars="vertical"/>
</ScrollView>
</LinearLayout>
</LinearLayout>
@@ -1,160 +0,0 @@
<?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:background="?attr/activityBackgroundColor">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前日期:"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/activityprenulldiningtableTextView2"/>
</LinearLayout>
<RelativeLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="订餐日期:"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_alignParentLeft="true"
android:id="@+id/activityprenulldiningtableTextView1"
android:layout_centerVertical="true"/>
<EditText
android:layout_width="50dp"
android:layout_height="wrap_content"
android:inputType="number"
android:ems="10"
android:layout_toRightOf="@+id/activityprenulldiningtableTextView1"
android:id="@+id/activityprenulldiningtableEditText1"
android:layout_centerVertical="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="号"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_toRightOf="@id/activityprenulldiningtableEditText1"
android:layout_centerVertical="true"/>
</RelativeLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="午餐"
android:id="@+id/activityprenulldiningtableRadioButton1"
android:onClick="onDinnerType"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="晚餐"
android:id="@+id/activityprenulldiningtableRadioButton2"
android:onClick="onDinnerType"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/activityprenulldiningtableCheckBox1"
android:text="全选"
android:layout_marginRight="10dp"
android:onClick="onSelectALLTable"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_shadow"
android:padding="10dp"
android:id="@+id/activityprenulldiningtableRecyclerView1"/>
</LinearLayout>
<RelativeLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="清空"
android:layout_toLeftOf="@id/activityprenulldiningtableButton2"
android:id="@+id/activityprenulldiningtableButton3"
android:onClick="onCleanResultText"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="复制"
android:layout_toLeftOf="@id/activityprenulldiningtableButton1"
android:id="@+id/activityprenulldiningtableButton2"
android:onClick="onCopyResultText"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="制作订餐列表"
android:id="@+id/activityprenulldiningtableButton1"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:onClick="onCreatePreBookList"/>
</RelativeLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/activityprenulldiningtableScrollView1"
android:layout_weight="1.0">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:id="@+id/activityprenulldiningtableEditText2"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
@@ -1,158 +0,0 @@
<?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"
android:padding="20dp"
android:background="?android:attr/windowBackground">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="编辑任务排档"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:paddingBottom="15dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="任务名称:"
android:textSize="14sp"
android:paddingBottom="5dp"/>
<EditText
android:id="@+id/dialog_et_task_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入任务名称"
android:inputType="text"
android:padding="10dp"
android:background="@android:drawable/edit_text"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="当前排档时间(yyyy-MM-dd HH):"
android:textSize="14sp"
android:paddingTop="15dp"
android:paddingBottom="5dp"/>
<EditText
android:id="@+id/dialog_et_start_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="例如:09:00"
android:inputType="time"
android:padding="10dp"
android:background="@android:drawable/edit_text"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="排档幅度:"
android:textSize="14sp"
android:paddingTop="15dp"
android:paddingBottom="5dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<EditText
android:id="@+id/dialog_et_month"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="月份"
android:inputType="number"
android:padding="10dp"
android:background="@android:drawable/edit_text"
android:layout_marginEnd="5dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="月"/>
<EditText
android:id="@+id/dialog_et_day"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="天数"
android:inputType="number"
android:padding="10dp"
android:background="@android:drawable/edit_text"
android:layout_marginStart="10dp"
android:layout_marginEnd="5dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="天"/>
<EditText
android:id="@+id/dialog_et_hour"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="小时"
android:inputType="number"
android:padding="10dp"
android:background="@android:drawable/edit_text"
android:layout_marginStart="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小时"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="备注:"
android:textSize="14sp"
android:paddingTop="15dp"
android:paddingBottom="5dp"/>
<EditText
android:id="@+id/dialog_et_remark"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入备注信息"
android:inputType="textMultiLine"
android:minLines="3"
android:gravity="top|start"
android:padding="10dp"
android:background="@android:drawable/edit_text"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="20dp"
android:gravity="end">
<Button
android:id="@+id/dialog_btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消"
android:layout_marginEnd="10dp"/>
<Button
android:id="@+id/dialog_btn_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="保存"/>
</LinearLayout>
</LinearLayout>

Some files were not shown because too many files have changed in this diff Show More