Compare commits
	
		
			115 Commits
		
	
	
		
			f93b6047a8
			...
			73285c8779
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 73285c8779 | ||
|   | fa338ec8c7 | ||
|   | 7a3a1f4bcd | ||
|   | ad991e3da2 | ||
|   | c0ff228845 | ||
|   | a0fe8f17a8 | ||
|   | 16bd40fc59 | ||
|   | ffaf683c54 | ||
|   | 26f5f8d3db | ||
|   | 917e25cdc8 | ||
|   | 707bed52c7 | ||
|   | b7f8b76ace | ||
|   | 38fe941a8b | ||
|   | e80d7e7b03 | ||
|   | 6e0a833fde | ||
|   | c596ee5fa4 | ||
|   | 5e99f1278e | ||
|   | b7158d1ebd | ||
|   | 9b51250ebf | ||
|   | 23920a7ff1 | ||
|   | 17c373c490 | ||
|   | 5f7c94b349 | ||
|   | c2b739d345 | ||
|   | 67a05cd457 | ||
|   | 554ab758bf | ||
|   | 20e118cd34 | ||
|   | f370ae8ffb | ||
|   | c92c874ea1 | ||
|   | 90a6116c0a | ||
|   | 45208ecbb1 | ||
|   | c28d655fe3 | ||
|   | 4b5905f74e | ||
|   | 6bd01780ec | ||
|   | a6699262f8 | ||
|   | ea2d38defc | ||
|   | e430b7abe4 | ||
|   | 945eadb617 | ||
|   | c5bffc5eef | ||
|   | 88597fe407 | ||
|   | 53f985533a | ||
|   | a3950f13ad | ||
|   | c878e9dc02 | ||
|   | f2f7cab330 | ||
|   | 6c8b0dcfa5 | ||
|   | 7de8a4f084 | ||
|   | 219c6614be | ||
|   | 0f5bb020b9 | ||
|   | 7794ff80ec | ||
|   | 7463ad3352 | ||
|   | 69187e3ed0 | ||
|   | 753032efed | ||
|   | 2b4c43c9af | ||
|   | 711c98d556 | ||
|   | 202205588a | ||
|   | 42c4978b44 | ||
|   | 1a2b7b862d | ||
|   | eb253b374f | ||
|   | c4e88e9593 | ||
|   | 08d9d92ae4 | ||
|   | 74841c08dc | ||
|   | 945bacb825 | ||
|   | 0e464495fd | ||
|   | e8682ce410 | ||
|   | 2e4003dae0 | ||
|   | 198b0975ce | ||
|   | 24a578a9d2 | ||
|   | 46de24447f | ||
|   | ac1c008035 | ||
|   | b124487cb1 | ||
|   | 9621d35f79 | ||
|   | 17de0832a6 | ||
|   | 1320984829 | ||
|   | abf1e5ba42 | ||
|   | 1cd2f88038 | ||
|   | 3f6e583d68 | ||
|   | 271456bfcd | ||
|   | ee5458d82c | ||
|   | 3a83367f71 | ||
|   | 74b9350a6a | ||
|   | d2858f23f7 | ||
|   | 40a5b9c339 | ||
|   | fd79113572 | ||
|   | 9b911b583c | ||
|   | 37817c3e8c | ||
|   | 0b5402f5f3 | ||
|   | bea22e3853 | ||
|   | 7e2ad0c01d | ||
|   | 476ce02fc8 | ||
|   | bc697279ad | ||
|   | dee01f1179 | ||
|   | a500decc7a | ||
|   | 5099d00050 | ||
|   | 515d14e896 | ||
|   | f630e27ed8 | ||
|   | cd7ed01216 | ||
|   | bb24bbfbd1 | ||
|   | 2ba2f88510 | ||
|   | db3a3644a8 | ||
|   | 556bfa7024 | ||
|   | 4842a1ec30 | ||
|   | 89dac91cc6 | ||
|   | 3809c1bcab | ||
|   | b0388a2972 | ||
|   | bd5a1f18ce | ||
|   | 99798b4816 | ||
|   | 9a0ee889ba | ||
|   | 6cce9c4d3f | ||
|   | df18c34976 | ||
|   | 22ca83b5b7 | ||
|   | 98233ce148 | ||
|   | b61c63c426 | ||
|   | f02dc215ca | ||
|   | 1c27d0ccdc | ||
|   | 803745d12e | ||
|   | a66be9cd37 | 
| @@ -113,10 +113,10 @@ if [[ $? -eq 0 ]]; then | ||||
|     # 如果Git已经提交了所有代码就执行标签和应用发布操作 | ||||
|  | ||||
|     # 预先询问是否添加工作流标签 | ||||
|     echo "Add Github Workflows Tag? (yes/No)" | ||||
| 	result=$(askAddWorkflowsTag) | ||||
| 	nAskAddWorkflowsTag=$? | ||||
| 	echo $result | ||||
|     #echo "Add Github Workflows Tag? (yes/No)" | ||||
| 	#result=$(askAddWorkflowsTag) | ||||
| 	#nAskAddWorkflowsTag=$? | ||||
| 	#echo $result | ||||
|  | ||||
|     # 发布应用 | ||||
| 	echo "Publishing WinBoLL APK ..." | ||||
| @@ -138,17 +138,17 @@ if [[ $? -eq 0 ]]; then | ||||
| 	fi | ||||
|      | ||||
|     # 添加 GitHub 工作流标签 | ||||
| 	if [[ $nAskAddWorkflowsTag -eq 1 ]]; then | ||||
| 	#if [[ $nAskAddWorkflowsTag -eq 1 ]]; then | ||||
| 	    # 如果用户选择添加工作流标签 | ||||
|     	result=$(addWorkflowsTag $1) | ||||
| 		if [[ $? -eq 0 ]]; then | ||||
| 		    echo $result | ||||
|     	#result=$(addWorkflowsTag $1) | ||||
| 		#if [[ $? -eq 0 ]]; then | ||||
| 		#    echo $result | ||||
| 		    # 工作流标签添加成功 | ||||
| 		else | ||||
| 			echo -e "${0}: addWorkflowsTag $1\n${result}\nAdd workflows tag cancel." | ||||
| 			exit 1 # addWorkflowsTag 异常 | ||||
| 		fi | ||||
| 	fi | ||||
| 		#else | ||||
| 			#echo -e "${0}: addWorkflowsTag $1\n${result}\nAdd workflows tag cancel." | ||||
| 			#exit 1 # addWorkflowsTag 异常 | ||||
| 		#fi | ||||
| 	#fi | ||||
| 	 | ||||
| 	## 清理更新描述文件内容 | ||||
| 	echo "" > $1/app_update_description.txt | ||||
|   | ||||
| @@ -114,9 +114,12 @@ | ||||
|  | ||||
| # 本项目要实际运用需要注意以下几个步骤: | ||||
| # 在项目根目录下: | ||||
| ## 1. 项目模块编译环境设置(必须),settings.gradle-demo 要复制为 settings.gradle,并取消相应项目模块的注释。 | ||||
| ## 2. 项目 Android SDK 编译环境设置(可选),local.properties-demo 要复制为 local.properties,并按需要设置 Android SDK 目录。 | ||||
| ## 3. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoLL Maven 库登录用户信息。 | ||||
| ## ★. 项目模块编译环境设置(必须),settings.gradle-demo 要复制为 settings.gradle,并取消相应项目模块的注释。 | ||||
| ## ★. 项目模块编译环境设置(必须) 在根目录拷贝 gradle.properties-androidx-demo 或者 gradle.properties-android-demo 文件为 gradle.properties。 | ||||
| ## ★. 项目 Android SDK 编译环境设置(可选),local.properties-demo 要复制为 local.properties,并按需要设置 Android SDK 目录。 | ||||
| ## ★. 应用签名密钥 keystore 设置问题。一般调试编译只需用【Termux】cd 进 GenKeyStore 目录执行 $ bash gen_debug_keystore.sh 命令即可完成设置。 | ||||
| ## ☆. 应用 WiBoLL 签名密钥配置问题<非必须考虑>。设置时需要 clone 【keystore】模块源码并拷贝模块目录的 appkey.jks 与 appkey.keystore 到项目根目录即可。 | ||||
| ## ☆. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoLL Maven 库登录用户信息。 | ||||
|  | ||||
|  | ||||
| # ☆类库型项目编译方法 | ||||
|   | ||||
| @@ -29,7 +29,7 @@ android { | ||||
|         // versionName 更新后需要手动设置  | ||||
|         // 项目模块目录的 build.gradle 文件的 stageCount=0 | ||||
|         // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" | ||||
|         versionName "15.8"  | ||||
|         versionName "15.9"  | ||||
|         if(true) { | ||||
|             versionName = genVersionName("${versionName}") | ||||
|         } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue Jun 03 19:17:05 HKT 2025 | ||||
| stageCount=2 | ||||
| #Sat Jun 28 12:59:51 HKT 2025 | ||||
| stageCount=3 | ||||
| libraryProject=libaes | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.1 | ||||
| baseVersion=15.9 | ||||
| publishVersion=15.9.2 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.8.2 | ||||
| baseBetaVersion=15.9.3 | ||||
|   | ||||
| @@ -83,7 +83,7 @@ public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity | ||||
|         appInfo.setAppGitOwner("Studio"); | ||||
|         appInfo.setAppGitAPPBranch(szBranchName); | ||||
|         appInfo.setAppGitAPPSubProjectFolder(szBranchName); | ||||
|         appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=2&fromuid=1"); | ||||
|         appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=3&extra=page%3D1"); | ||||
|         appInfo.setAppAPKName("AES"); | ||||
|         appInfo.setAppAPKFolderName("AES"); | ||||
|         //appInfo.setIsAddDebugTools(false); | ||||
|   | ||||
| @@ -67,7 +67,7 @@ dependencies { | ||||
|     //api 'androidx.vectordrawable:vectordrawable-animated:1.1.0' | ||||
|     //api 'androidx.fragment:fragment:1.1.0' | ||||
|      | ||||
|     api 'cc.winboll.studio:libaes:15.8.0' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.1' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.1' | ||||
|     api 'cc.winboll.studio:libaes:15.9.2' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.4' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.4' | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Mon May 19 21:43:40 GMT 2025 | ||||
| #Sat Jun 28 05:02:54 GMT 2025 | ||||
| stageCount=0 | ||||
| libraryProject= | ||||
| baseVersion=15.0 | ||||
| publishVersion=15.0.0 | ||||
| buildCount=22 | ||||
| buildCount=27 | ||||
| baseBetaVersion=15.0.1 | ||||
|   | ||||
| @@ -30,7 +30,7 @@ android { | ||||
|         // versionName 更新后需要手动设置  | ||||
|         // .winboll/winbollBuildProps.properties 文件的 stageCount=0 | ||||
|         // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" | ||||
|         versionName "15.8" | ||||
|         versionName "15.9" | ||||
|         if(true) { | ||||
|             versionName = genVersionName("${versionName}") | ||||
|         } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue Jun 03 13:40:08 HKT 2025 | ||||
| stageCount=5 | ||||
| #Sun Aug 31 04:33:09 CST 2025 | ||||
| stageCount=6 | ||||
| libraryProject=libappbase | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.4 | ||||
| baseVersion=15.9 | ||||
| publishVersion=15.9.5 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.8.5 | ||||
| baseBetaVersion=15.9.6 | ||||
|   | ||||
| @@ -9,7 +9,8 @@ | ||||
|         android:label="@string/app_name" | ||||
|         android:theme="@style/MyAPPBaseTheme" | ||||
|         android:resizeableActivity="true" | ||||
|         android:process=":App"> | ||||
|         android:process=":App" | ||||
|         android:networkSecurityConfig="@xml/network_security_config"> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
| @@ -39,7 +40,8 @@ | ||||
|             android:resizeableActivity="true" | ||||
|             android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/> | ||||
|  | ||||
|         <activity android:name=".activities.New2Activity" | ||||
|         <activity | ||||
|             android:name=".activities.New2Activity" | ||||
|             android:label="New2Activity" | ||||
|             android:exported="true" | ||||
|             android:resizeableActivity="true" | ||||
| @@ -74,7 +76,8 @@ | ||||
|  | ||||
|         <service android:name=".services.AssistantService"/> | ||||
|  | ||||
|         <receiver android:name="cc.winboll.studio.appbase.receivers.MainReceiver" | ||||
|         <receiver | ||||
|             android:name="cc.winboll.studio.appbase.receivers.MainReceiver" | ||||
|             android:exported="true"> | ||||
|  | ||||
|             <intent-filter> | ||||
| @@ -105,7 +108,8 @@ | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
|         <receiver android:name=".receivers.APPNewsWidgetClickListener" | ||||
|         <receiver | ||||
|             android:name=".receivers.APPNewsWidgetClickListener" | ||||
|             android:exported="true"> | ||||
|  | ||||
|             <intent-filter> | ||||
| @@ -122,7 +126,6 @@ | ||||
|             android:name="android.max_aspect" | ||||
|             android:value="4.0"/> | ||||
|  | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
|   | ||||
| @@ -62,6 +62,11 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity { | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         if(item.getItemId() == R.id.item_yun) { | ||||
|             GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.YunActivity.class); | ||||
|         } else if(item.getItemId() == R.id.item_logon) { | ||||
|             GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.LogonActivity.class); | ||||
|         } | ||||
|         // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。 | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
|   | ||||
| @@ -5,6 +5,14 @@ | ||||
|         android:id="@+id/item_home" | ||||
|         android:title="HOME" | ||||
|         android:icon="@drawable/ic_winboll"/> | ||||
|     <item | ||||
|         android:id="@+id/item_yun" | ||||
|         android:title="YUN" | ||||
|         android:icon="@drawable/ic_winboll"/> | ||||
|     <item | ||||
|         android:id="@+id/item_logon" | ||||
|         android:title="Logon" | ||||
|         android:icon="@drawable/ic_winboll"/> | ||||
|     <item | ||||
|         android:id="@+id/item_log" | ||||
|         android:title="LOG" | ||||
|   | ||||
							
								
								
									
										12
									
								
								appbase/src/main/res/xml/network_security_config.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <network-security-config> | ||||
|     <!-- 允许访问 winboll.cc 及其子域名(原配置) --> | ||||
|     <domain-config cleartextTrafficPermitted="true"> | ||||
|         <domain includeSubdomains="true">winboll.cc</domain> | ||||
|     </domain-config> | ||||
|  | ||||
|     <!-- **新增:允许访问 IP 地址 10.8.0.250** --> | ||||
|     <domain-config cleartextTrafficPermitted="true"> | ||||
|         <domain includeSubdomains="false">10.8.0.250</domain> <!-- 不包含子域名 --> | ||||
|     </domain-config> | ||||
| </network-security-config> | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue Jun 03 15:05:48 HKT 2025 | ||||
| stageCount=5 | ||||
| #Mon Sep 01 07:56:33 HKT 2025 | ||||
| stageCount=7 | ||||
| libraryProject=libapputils | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.4 | ||||
| publishVersion=15.8.6 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.8.5 | ||||
| baseBetaVersion=15.8.7 | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Sun May 04 05:32:00 GMT 2025 | ||||
| stageCount=1 | ||||
| #Tue Jun 24 09:54:47 HKT 2025 | ||||
| stageCount=3 | ||||
| libraryProject= | ||||
| baseVersion=15.2 | ||||
| publishVersion=15.2.0 | ||||
| buildCount=74 | ||||
| baseBetaVersion=15.2.1 | ||||
| publishVersion=15.2.2 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.2.3 | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| https://github.com/aJIEw/PhoneCallApp.git | ||||
|  | ||||
| #### 介绍 | ||||
| 通讯录与拨号 | ||||
| 这是可以根据正则表达式匹配拦截骚扰电话的手机拨号应用。 | ||||
|  | ||||
| #### 软件架构 | ||||
| 适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。 | ||||
|   | ||||
| @@ -45,9 +45,9 @@ android { | ||||
|  | ||||
| dependencies { | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
|     api 'cc.winboll.studio:libaes:15.8.0' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.1' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.1' | ||||
|     api 'cc.winboll.studio:libaes:15.9.2' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.4' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.4' | ||||
|      | ||||
|     // 权限请求框架:https://github.com/getActivity/XXPermissions | ||||
|     api 'com.github.getActivity:XXPermissions:18.63' | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue May 20 13:02:18 HKT 2025 | ||||
| stageCount=3 | ||||
| #Thu Jul 17 09:57:24 HKT 2025 | ||||
| stageCount=12 | ||||
| libraryProject= | ||||
| baseVersion=15.3 | ||||
| publishVersion=15.3.2 | ||||
| publishVersion=15.3.11 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.3.3 | ||||
| baseBetaVersion=15.3.12 | ||||
|   | ||||
| @@ -79,12 +79,12 @@ public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity | ||||
|         APPInfo appInfo = new APPInfo(); | ||||
|         appInfo.setAppName("Contacts"); | ||||
|         appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll); | ||||
|         appInfo.setAppDescription("通讯录与拨号"); | ||||
|         appInfo.setAppGitName("APP"); | ||||
|         appInfo.setAppDescription("这是可以根据正则表达式匹配拦截骚扰电话的手机拨号应用。"); | ||||
|         appInfo.setAppGitName("APPBase"); | ||||
|         appInfo.setAppGitOwner("Studio"); | ||||
|         appInfo.setAppGitAPPBranch(szBranchName); | ||||
|         appInfo.setAppGitAPPSubProjectFolder(szBranchName); | ||||
|         appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=Contacts"); | ||||
|         appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=4&extra=page%3D1"); | ||||
|         appInfo.setAppAPKName("Contacts"); | ||||
|         appInfo.setAppAPKFolderName("Contacts"); | ||||
|         return new AboutView(mContext, appInfo); | ||||
|   | ||||
| @@ -198,6 +198,9 @@ public class SettingsActivity extends AppCompatActivity implements IWinBoLLActiv | ||||
|             settingsModel.setDunTotalCount(Integer.parseInt(etDunTotalCount.getText().toString())); | ||||
|             settingsModel.setDunResumeSecondCount(Integer.parseInt(etDunResumeSecondCount.getText().toString())); | ||||
|             settingsModel.setDunResumeCount(Integer.parseInt(etDunResumeCount.getText().toString())); | ||||
| 			 | ||||
| 			// 应用效果提示 | ||||
| 			ToastUtils.show((settingsModel.getDunTotalCount() == 1)?"电话骚扰防御力几乎为0。":String.format("以下设置将在连拨%d次后接通电话。", settingsModel.getDunTotalCount())); | ||||
|         } | ||||
|         settingsModel.setIsEnableDun(isEnableDun); | ||||
|         Rules.getInstance(this).saveDun(); | ||||
| @@ -207,6 +210,7 @@ public class SettingsActivity extends AppCompatActivity implements IWinBoLLActiv | ||||
|         etDunTotalCount.setText(Integer.toString(settingsModel.getDunTotalCount())); | ||||
|         etDunResumeSecondCount.setText(Integer.toString(settingsModel.getDunResumeSecondCount())); | ||||
|         etDunResumeCount.setText(Integer.toString(settingsModel.getDunResumeCount())); | ||||
| 		 | ||||
|     } | ||||
|  | ||||
|     void updateStreamVolumeTextView() { | ||||
| @@ -243,6 +247,9 @@ public class SettingsActivity extends AppCompatActivity implements IWinBoLLActiv | ||||
|         Rules.getInstance(this).resetDefaultBoBullToonURL(); | ||||
|         EditText etBoBullToonURL = findViewById(R.id.bobulltoonurl_et); | ||||
|         etBoBullToonURL.setText(Rules.getInstance(this).getBoBullToonURL()); | ||||
| 		 | ||||
| 		final TomCat tomCat = TomCat.getInstance(this); | ||||
| 		tomCat.cleanBoBullToon(); | ||||
|     } | ||||
|  | ||||
|     public void onDownloadBoBullToon(View view) { | ||||
| @@ -330,4 +337,8 @@ public class SettingsActivity extends AppCompatActivity implements IWinBoLLActiv | ||||
|     public void onAbout(View view) { | ||||
|         App.getWinBoLLActivityManager().startWinBoLLActivity(this, AboutActivity.class); | ||||
|     } | ||||
| 	 | ||||
| 	public void onLogView(View view) { | ||||
|         App.getWinBoLLActivityManager().startLogActivity(this); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,13 +5,18 @@ package cc.winboll.studio.contacts.adapters; | ||||
|  * @Date 2025/02/26 13:09:32 | ||||
|  * @Describe CallLogAdapter | ||||
|  */ | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.Button; | ||||
| import android.widget.PopupMenu; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| import cc.winboll.studio.contacts.R; | ||||
| @@ -47,6 +52,38 @@ public class CallLogAdapter extends RecyclerView.Adapter<CallLogAdapter.CallLogV | ||||
|     public void onBindViewHolder(@NonNull CallLogViewHolder holder, int position) { | ||||
|         final CallLogModel callLog = callLogList.get(position); | ||||
|         holder.phoneNumber.setText(callLog.getPhoneNumber() + "☎" + mContactUtils.getContactsName(callLog.getPhoneNumber())); | ||||
| 		holder.phoneNumber.setOnLongClickListener(new View.OnLongClickListener() { | ||||
| 				@Override | ||||
| 				public boolean onLongClick(View p1) { | ||||
| 					// 弹出复制菜单 | ||||
| 					PopupMenu menu = new PopupMenu(mContext, holder.phoneNumber); | ||||
| 					//加载菜单资源 | ||||
| 					menu.getMenuInflater().inflate(R.menu.toolbar_calllog_phonenumber, menu.getMenu()); | ||||
| 					//设置点击事件的响应 | ||||
| 					menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { | ||||
| 							@Override | ||||
| 							public boolean onMenuItemClick(MenuItem menuItem) { | ||||
| 								int nItemId = menuItem.getItemId(); | ||||
| 								if (nItemId == R.id.item_calllog_phonenumber_copy) { | ||||
| 									// Gets a handle to the clipboard service. | ||||
| 									ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); | ||||
| 									// Creates a new text clip to put on the clipboard | ||||
| 									ClipData clip = ClipData.newPlainText("simple text", callLog.getPhoneNumber()); | ||||
| 									// Set the clipboard's primary clip. | ||||
| 									clipboard.setPrimaryClip(clip); | ||||
| 									Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show(); | ||||
| 								} | ||||
|  | ||||
| 								return true; | ||||
| 							} | ||||
| 						}); | ||||
| 					//一定要调用show()来显示弹出式菜单 | ||||
| 					menu.show(); | ||||
|  | ||||
| 					return true; | ||||
| 				} | ||||
| 			}); | ||||
| 		 | ||||
|         holder.callStatus.setText(callLog.getCallStatus()); | ||||
|         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); | ||||
|         holder.callDate.setText(dateFormat.format(callLog.getCallDate())); | ||||
|   | ||||
| @@ -5,19 +5,25 @@ package cc.winboll.studio.contacts.adapters; | ||||
|  * @Date 2025/02/26 13:35:44 | ||||
|  * @Describe ContactAdapter | ||||
|  */ | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.Button; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.PopupMenu; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| import cc.winboll.studio.contacts.R; | ||||
| import cc.winboll.studio.contacts.beans.ContactModel; | ||||
| import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.util.List; | ||||
| import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; | ||||
|  | ||||
| public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactViewHolder> { | ||||
|  | ||||
| @@ -26,8 +32,10 @@ public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactV | ||||
|     private static final int REQUEST_CALL_PHONE = 1; | ||||
|  | ||||
|     private List<ContactModel> contactList; | ||||
| 	Context mContext; | ||||
|  | ||||
|     public ContactAdapter(List<ContactModel> contactList) { | ||||
|     public ContactAdapter(Context context, List<ContactModel> contactList) { | ||||
| 		mContext = context; | ||||
|         this.contactList = contactList; | ||||
|     } | ||||
|  | ||||
| @@ -41,6 +49,37 @@ public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactV | ||||
|     @Override | ||||
|     public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) { | ||||
|         final ContactModel contact = contactList.get(position); | ||||
| 		holder.llPhoneNumberMain.setOnLongClickListener(new View.OnLongClickListener() { | ||||
| 				@Override | ||||
| 				public boolean onLongClick(View p1) { | ||||
| 					// 弹出复制菜单 | ||||
| 					PopupMenu menu = new PopupMenu(mContext, holder.llPhoneNumberMain); | ||||
| 					//加载菜单资源 | ||||
| 					menu.getMenuInflater().inflate(R.menu.toolbar_contact_phonenumber, menu.getMenu()); | ||||
| 					//设置点击事件的响应 | ||||
| 					menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { | ||||
| 							@Override | ||||
| 							public boolean onMenuItemClick(MenuItem menuItem) { | ||||
| 								int nItemId = menuItem.getItemId(); | ||||
| 								if (nItemId == R.id.item_contact_phonenumber_copy) { | ||||
| 									// Gets a handle to the clipboard service. | ||||
| 									ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); | ||||
| 									// Creates a new text clip to put on the clipboard | ||||
| 									ClipData clip = ClipData.newPlainText("simple text", contact.getNumber()); | ||||
| 									// Set the clipboard's primary clip. | ||||
| 									clipboard.setPrimaryClip(clip); | ||||
| 									Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show(); | ||||
| 								} | ||||
|  | ||||
| 								return true; | ||||
| 							} | ||||
| 						}); | ||||
| 					//一定要调用show()来显示弹出式菜单 | ||||
| 					menu.show(); | ||||
|  | ||||
| 					return true; | ||||
| 				} | ||||
| 			}); | ||||
|         holder.contactName.setText(contact.getName()); | ||||
|         holder.contactNumber.setText(contact.getNumber()); | ||||
|  | ||||
| @@ -69,12 +108,14 @@ public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactV | ||||
|     } | ||||
|  | ||||
|     public class ContactViewHolder extends RecyclerView.ViewHolder { | ||||
| 		LinearLayout llPhoneNumberMain; | ||||
|         TextView contactName; | ||||
|         TextView contactNumber; | ||||
|         AOHPCTCSeekBar dialAOHPCTCSeekBar; | ||||
|          | ||||
|         public ContactViewHolder(@NonNull View itemView) { | ||||
|             super(itemView); | ||||
| 			llPhoneNumberMain = itemView.findViewById(R.id.itemcontactLinearLayout1); | ||||
|             contactName = itemView.findViewById(R.id.contact_name); | ||||
|             contactNumber = itemView.findViewById(R.id.contact_number); | ||||
|             dialAOHPCTCSeekBar = itemView.findViewById(R.id.aohpctcseekbar_dial); | ||||
|   | ||||
| @@ -7,6 +7,7 @@ package cc.winboll.studio.contacts.adapters; | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.MotionEvent; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.Button; | ||||
| @@ -20,6 +21,7 @@ import cc.winboll.studio.contacts.R; | ||||
| import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel; | ||||
| import cc.winboll.studio.contacts.dun.Rules; | ||||
| import cc.winboll.studio.contacts.views.LeftScrollView; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.util.ArrayList; | ||||
| @@ -60,6 +62,10 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V | ||||
|             final SimpleViewHolder simpleViewHolder = (SimpleViewHolder) holder; | ||||
|             String szView = model.getRuleText().trim().equals("") ?"[NULL]": model.getRuleText(); | ||||
|             simpleViewHolder.tvRuleText.setText(szView); | ||||
|             simpleViewHolder.checkBoxAllow.setChecked(model.isAllowConnection()); | ||||
| 			simpleViewHolder.checkBoxAllow.setEnabled(false); | ||||
|             simpleViewHolder.checkBoxEnable.setChecked(model.isEnable()); | ||||
| 			simpleViewHolder.checkBoxEnable.setEnabled(false); | ||||
|             simpleViewHolder.scrollView.setOnActionListener(new LeftScrollView.OnActionListener(){ | ||||
|  | ||||
|                     @Override | ||||
| @@ -215,16 +221,22 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V | ||||
|  | ||||
|         private final LeftScrollView scrollView; | ||||
|         private final TextView tvRuleText; | ||||
| 		CheckBox checkBoxAllow; | ||||
|         CheckBox checkBoxEnable; | ||||
|  | ||||
|  | ||||
|         public SimpleViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) { | ||||
|             super(itemView); | ||||
|             scrollView = itemView.findViewById(R.id.scrollView); | ||||
|             //tvRuleText = itemView.findViewById(R.id.ruletext_tv); | ||||
|             tvRuleText = new TextView(itemView.getContext()); | ||||
| 			LayoutInflater inflater = LayoutInflater.from(itemView.getContext()); | ||||
| 		    View viewContent = inflater.inflate(R.layout.view_phone_connect_rule_simple_content, parent, false); | ||||
|             tvRuleText = viewContent.findViewById(R.id.ruletext_tv); | ||||
|             checkBoxAllow = viewContent.findViewById(R.id.checkbox_allow); | ||||
|             checkBoxEnable = viewContent.findViewById(R.id.checkbox_enable); | ||||
|             //tvRuleText = new TextView(itemView.getContext()); | ||||
|             scrollView.setContentWidth(parent.getWidth()); | ||||
|             //scrollView.setContentWidth(600); | ||||
|             scrollView.addContentLayout(tvRuleText); | ||||
|             scrollView.addContentLayout(viewContent); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| @@ -243,5 +255,9 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V | ||||
|             buttonConfirm = itemView.findViewById(R.id.button_confirm); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 	private void setCheckBoxTouchListener(CheckBox checkBox) { | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -44,7 +44,7 @@ public class TomCat { | ||||
|         } | ||||
|         return _TomCat; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     public String getDefaultBobulltoonUrl() { | ||||
|         return mContext.getString(R.string.default_bobulltoon_url); | ||||
|     } | ||||
| @@ -123,7 +123,7 @@ public class TomCat { | ||||
|             } | ||||
|  | ||||
|             // 更新新文件 | ||||
|             if(downloadAndExtractZip(zipUrl, destinationFolder)) { | ||||
|             if (downloadAndExtractZip(zipUrl, destinationFolder)) { | ||||
|                 LogUtils.d(TAG, "ZIP 文件下载并解压成功。"); | ||||
|                 return true; | ||||
|             } | ||||
| @@ -155,6 +155,19 @@ public class TomCat { | ||||
|         return mContext.getExternalFilesDir(TAG); | ||||
|     } | ||||
|  | ||||
| 	public void cleanBoBullToon() { | ||||
| 		String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径 | ||||
| 		// 删除旧文件 | ||||
| 		File fOldFolder = new File(destinationFolder); | ||||
| 		if (fOldFolder.exists()) { | ||||
| 			deleteFolderRecursive(fOldFolder); | ||||
| 			fOldFolder.mkdirs(); | ||||
| 		} | ||||
|  | ||||
| 		ToastUtils.show("已清空 BoBullToon 数据!"); | ||||
| 		LogUtils.d(TAG, "已清空 BoBullToon 数据"); | ||||
| 	} | ||||
|  | ||||
|     public boolean loadPhoneBoBullToon() { | ||||
|         listPhoneBoBullToon.clear(); | ||||
|         File fBoBullToon = new File(getWorkingFolder(), "bobulltoon"); | ||||
|   | ||||
| @@ -145,6 +145,14 @@ public class Rules { | ||||
|             LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); | ||||
|         } | ||||
|  | ||||
|         // 检验拨不通号码群 | ||||
|         if (!isDefend && MainService.isPhoneInBoBullToon(phoneNumber)) { | ||||
|             LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber)); | ||||
|             isDefend = true; | ||||
|             isConnect = false; | ||||
|             LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); | ||||
|         } | ||||
|  | ||||
|         // 查询通讯录是否有该联系人 | ||||
|         boolean isPhoneInContacts = ContactUtils.getInstance(mContext).isPhoneInContacts(mContext, phoneNumber); | ||||
|         if (!isDefend) { | ||||
| @@ -158,14 +166,6 @@ public class Rules { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 检验拨不通号码群 | ||||
|         if (!isDefend && MainService.isPhoneInBoBullToon(phoneNumber)) { | ||||
|             LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber)); | ||||
|             isDefend = true; | ||||
|             isConnect = false; | ||||
|             LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); | ||||
|         } | ||||
|  | ||||
|         // 正则匹配规则名单校验 | ||||
|         if (!isDefend) { | ||||
|             for (int i = 0; i < _PhoneConnectRuleModelList.size(); i++) { | ||||
|   | ||||
| @@ -73,7 +73,7 @@ public class ContactsFragment extends Fragment { | ||||
|         super.onViewCreated(view, savedInstanceState); | ||||
|         recyclerView = view.findViewById(R.id.contacts_recycler_view); | ||||
|         recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); | ||||
|         contactAdapter = new ContactAdapter(contactList); | ||||
|         contactAdapter = new ContactAdapter(getContext(), contactList); | ||||
|         recyclerView.setAdapter(contactAdapter); | ||||
|  | ||||
|         searchEditText = view.findViewById(R.id.search_edit_text); | ||||
|   | ||||
| @@ -47,8 +47,8 @@ public class LeftScrollView extends HorizontalScrollView { | ||||
|         init(); | ||||
|     } | ||||
|  | ||||
|     public void addContentLayout(TextView textView) { | ||||
|         contentLayout.addView(textView, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); | ||||
|     public void addContentLayout(View viewContent) { | ||||
|         contentLayout.addView(viewContent, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); | ||||
|     } | ||||
|  | ||||
|     public void setContentWidth(int contentWidth) { | ||||
|   | ||||
| @@ -269,6 +269,19 @@ | ||||
|  | ||||
| 			</LinearLayout> | ||||
|  | ||||
| 			<LinearLayout | ||||
| 				android:orientation="horizontal" | ||||
| 				android:layout_width="match_parent" | ||||
| 				android:layout_height="wrap_content" | ||||
| 				android:gravity="center_horizontal"> | ||||
|  | ||||
| 				<TextView | ||||
| 					android:layout_width="wrap_content" | ||||
| 					android:layout_height="wrap_content" | ||||
| 					android:text="<<==向左拉动列表项可编辑内容"/> | ||||
|  | ||||
| 			</LinearLayout> | ||||
|  | ||||
| 			<androidx.recyclerview.widget.RecyclerView | ||||
| 				android:id="@+id/recycler_view" | ||||
| 				android:layout_width="match_parent" | ||||
| @@ -287,6 +300,12 @@ | ||||
| 				android:layout_height="wrap_content" | ||||
| 				android:gravity="right"> | ||||
|  | ||||
| 				<Button | ||||
| 					android:layout_width="wrap_content" | ||||
| 					android:layout_height="wrap_content" | ||||
| 					android:text="LogView" | ||||
| 					android:onClick="onLogView"/> | ||||
|  | ||||
| 				<Button | ||||
| 					android:layout_width="wrap_content" | ||||
| 					android:layout_height="wrap_content" | ||||
|   | ||||
| @@ -15,8 +15,10 @@ | ||||
| 		<Button | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="Test Main" | ||||
| 			android:onClick="onTestMain"/> | ||||
| 			android:text="Add Demo Rules(While size is 0) and Test" | ||||
| 			android:onClick="onTestMain" | ||||
| 			android:textSize="10sp" | ||||
| 			android:textAllCaps="false"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| @@ -43,7 +45,8 @@ | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="Test Phone" | ||||
| 			android:onClick="onTestPhone"/> | ||||
| 			android:onClick="onTestPhone" | ||||
| 			android:textAllCaps="false"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,8 @@ | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content"> | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:id="@+id/itemcontactLinearLayout1"> | ||||
|  | ||||
| 		<TextView | ||||
| 			android:id="@+id/contact_number" | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
| 			android:id="@+id/checkbox_allow" | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="允许连接"/> | ||||
| 			android:text="连接"/> | ||||
|  | ||||
|         <CheckBox | ||||
|             android:id="@+id/checkbox_enable" | ||||
|   | ||||
| @@ -1,55 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <HorizontalScrollView | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 	android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:scrollbars="none" | ||||
|     android:id="@+id/scrollView"> | ||||
|  | ||||
|     <LinearLayout | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:orientation="horizontal"> | ||||
|  | ||||
|         <!-- 内容区域 --> | ||||
|         <LinearLayout | ||||
|             android:id="@+id/content_layout" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:padding="16dp" | ||||
|             android:background="@color/white"> | ||||
|             <!-- 这里放置你的列表项内容 --> | ||||
|  | ||||
|             <TextView | ||||
|                 android:id="@+id/text_view" | ||||
|                 android:layout_width="0dp" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_weight="1" | ||||
|                 android:textSize="16sp"/> | ||||
|         </LinearLayout> | ||||
|  | ||||
|         <!-- 操作按钮 --> | ||||
|         <LinearLayout | ||||
|             android:id="@+id/action_layout" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="match_parent" | ||||
|             android:orientation="horizontal" | ||||
|             android:background="@color/lightgray"> | ||||
|  | ||||
|             <Button | ||||
|                 android:id="@+id/edit_btn" | ||||
|                 android:layout_width="80dp" | ||||
|                 android:layout_height="match_parent" | ||||
|                 android:text="编辑" | ||||
|                 android:background="@color/blue" /> | ||||
|  | ||||
|             <Button | ||||
|                 android:id="@+id/delete_btn" | ||||
|                 android:layout_width="80dp" | ||||
|                 android:layout_height="match_parent" | ||||
|                 android:text="删除" | ||||
|                 android:background="@color/red" /> | ||||
|         </LinearLayout> | ||||
|     </LinearLayout> | ||||
| </HorizontalScrollView> | ||||
| @@ -0,0 +1,35 @@ | ||||
| <?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="horizontal" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="wrap_content" | ||||
| 	android:layout_gravity="center_vertical" | ||||
| 	android:gravity="center_vertical"> | ||||
|  | ||||
| 	<TextView | ||||
| 		android:layout_width="wrap_content" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="Text" | ||||
| 		android:layout_weight="1.0" | ||||
| 		android:id="@+id/ruletext_tv"/> | ||||
|  | ||||
| 	<CheckBox | ||||
| 		android:id="@+id/checkbox_allow" | ||||
| 		android:layout_width="wrap_content" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="连接" | ||||
| 		android:clickable="false" | ||||
| 		android:focusable="false"/> | ||||
|  | ||||
| 	<CheckBox | ||||
| 		android:id="@+id/checkbox_enable" | ||||
| 		android:layout_width="wrap_content" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="启用" | ||||
| 		android:clickable="false" | ||||
| 		android:focusable="false"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
| @@ -0,0 +1,9 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|      | ||||
|     <item | ||||
|         android:id="@+id/item_calllog_phonenumber_copy" | ||||
|         android:title="Copy"/> | ||||
|      | ||||
| </menu> | ||||
| @@ -0,0 +1,9 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|  | ||||
|     <item | ||||
|         android:id="@+id/item_contact_phonenumber_copy" | ||||
|         android:title="Copy"/> | ||||
|      | ||||
| </menu> | ||||
| @@ -2,6 +2,6 @@ | ||||
| <resources> | ||||
|  | ||||
|     <string name="app_name">Contacts</string> | ||||
|     <string name="default_bobulltoon_url">http://10.8.0.12:3000/Studio/BoBullToon/archive/main.zip</string> | ||||
|     <string name="default_bobulltoon_url">https://gitea.winboll.cc/Studio/BoBullToon/archive/main.zip</string> | ||||
|  | ||||
| </resources> | ||||
|   | ||||
| @@ -42,23 +42,24 @@ android { | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     compileOptions { | ||||
|     /*compileOptions { | ||||
|         sourceCompatibility JavaVersion.VERSION_11 | ||||
|         targetCompatibility JavaVersion.VERSION_11 | ||||
|     } | ||||
|     }*/ | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
| 	api project(':libjc') | ||||
| 	api 'cc.winboll.studio:libaes:15.9.1' | ||||
| 	api 'cc.winboll.studio:libapputils:15.8.4' | ||||
| 	api 'cc.winboll.studio:libappbase:15.8.4' | ||||
| 	 | ||||
|     // https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on | ||||
|     //implementation 'org.bouncycastle:bcprov-jdk15on:1.70' | ||||
|     implementation 'org.bouncycastle:bcprov-jdk15to18:1.69' | ||||
|     implementation 'org.bouncycastle:bcpkix-jdk15to18:1.69' | ||||
|  | ||||
| 	api project(':libjc') | ||||
|     api 'androidx.appcompat:appcompat:1.0.0' | ||||
| 	api 'com.google.android.material:material:1.0.0' | ||||
|      | ||||
|     api 'cc.winboll.studio:libapputils:9.1.0' | ||||
|     api 'cc.winboll.studio:libappbase:1.0.3' | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Fri Jan 10 22:03:57 GMT 2025 | ||||
| #Tue Jun 24 11:17:30 GMT 2025 | ||||
| stageCount=0 | ||||
| libraryProject=libjc | ||||
| baseVersion=1.0 | ||||
| publishVersion=1.0.0 | ||||
| buildCount=133 | ||||
| buildCount=135 | ||||
| baseBetaVersion=1.0.1 | ||||
|   | ||||
| @@ -15,10 +15,10 @@ import android.widget.LinearLayout; | ||||
| import android.widget.ScrollView; | ||||
| import android.widget.TextView; | ||||
| import cc.winboll.studio.jc.R; | ||||
| import cc.winboll.studio.libapputils.log.LogUtils; | ||||
| import cc.winboll.studio.libjc.JAR_RUNNING_MODE; | ||||
| import cc.winboll.studio.libjc.JCMainThread; | ||||
| import cc.winboll.studio.libjc.net.JCSocketClient; | ||||
| import cc.winboll.studio.libjc.util.LogUtils; | ||||
| import cc.winboll.studio.libjc.Main; | ||||
|  | ||||
| final public class MainActivity extends Activity implements JCMainThread.OnMessageListener { | ||||
|  | ||||
| @@ -77,7 +77,7 @@ final public class MainActivity extends Activity implements JCMainThread.OnMessa | ||||
|         // 启动主线程 | ||||
|         _JCMainThread = JCMainThread.getInstance(getPackageName()); | ||||
|         _JCMainThread.setOnLogListener(this); | ||||
|         _JCMainThread.setRunningMode(JAR_RUNNING_MODE.JC); | ||||
|         //_JCMainThread.setRunningMode(Main.JAR_RUNNING_MODE.JC); | ||||
|         _JCMainThread.start(); | ||||
|  | ||||
|         // 设置 WinBoll 应用 UI 类型 | ||||
|   | ||||
| @@ -21,8 +21,8 @@ android { | ||||
|  | ||||
| dependencies { | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
|     api 'cc.winboll.studio:libapputils:15.8.2' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.2' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.4' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.4' | ||||
|      | ||||
|     // 吐司类库 | ||||
|     api 'com.github.getActivity:ToastUtils:10.5' | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue Jun 03 19:16:41 HKT 2025 | ||||
| stageCount=2 | ||||
| #Sat Jun 28 12:59:30 HKT 2025 | ||||
| stageCount=3 | ||||
| libraryProject=libaes | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.1 | ||||
| baseVersion=15.9 | ||||
| publishVersion=15.9.2 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.8.2 | ||||
| baseBetaVersion=15.9.3 | ||||
|   | ||||
| @@ -107,7 +107,7 @@ public class AboutView extends LinearLayout { | ||||
|         mszAppDescription = mAPPInfo.getAppDescription(); | ||||
|         mnAppIcon = mAPPInfo.getAppIcon(); | ||||
|  | ||||
|         mszWinBoLLServerHost = GlobalApplication.isDebuging() ?  "https://dev.winboll.cc": "https://www.winboll.cc"; | ||||
|         mszWinBoLLServerHost = GlobalApplication.isDebuging() ?  "https://yun-preivew.winboll.cc": "https://yun.winboll.cc"; | ||||
|  | ||||
|         try { | ||||
|             mszAppVersionName = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName; | ||||
|   | ||||
| @@ -22,4 +22,9 @@ android { | ||||
|  | ||||
| dependencies { | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
|     // 网络连接类库 | ||||
|     api 'com.squareup.okhttp3:okhttp:4.4.1' | ||||
|     // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind | ||||
|      | ||||
|     api 'com.google.code.gson:gson:2.10.1' | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue Jun 03 13:40:01 HKT 2025 | ||||
| stageCount=5 | ||||
| #Mon Aug 18 03:56:26 HKT 2025 | ||||
| stageCount=6 | ||||
| libraryProject=libappbase | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.4 | ||||
| baseVersion=15.9 | ||||
| publishVersion=15.9.5 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.8.5 | ||||
| baseBetaVersion=15.9.6 | ||||
|   | ||||
| @@ -103,6 +103,10 @@ | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
|         <activity android:name="cc.winboll.studio.libappbase.activities.YunActivity"/> | ||||
|  | ||||
|         <activity android:name="cc.winboll.studio.libappbase.activities.LogonActivity"/> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
|   | ||||
| @@ -0,0 +1,150 @@ | ||||
| package cc.winboll.studio.libappbase.activities; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.os.Bundle; | ||||
| import android.view.View; | ||||
| import android.widget.RadioButton; | ||||
| import cc.winboll.studio.libappbase.BuildConfig; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.LogView; | ||||
| import cc.winboll.studio.libappbase.R; | ||||
| import cc.winboll.studio.libappbase.models.UserInfoModel; | ||||
| import cc.winboll.studio.libappbase.utils.RSAUtils; | ||||
| import cc.winboll.studio.libappbase.utils.YunUtils; | ||||
| import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
| import java.security.KeyPair; | ||||
| import java.security.PrivateKey; | ||||
| import java.security.PublicKey; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/06/04 13:29 | ||||
|  * @Describe 用户登录框 | ||||
|  */ | ||||
| public class LogonActivity extends Activity implements IWinBoLLActivity { | ||||
|  | ||||
|     public static final String TAG = "LogonActivity"; | ||||
|  | ||||
|     public static final String DEBUG_HOST = "http://10.8.0.250:456"; | ||||
|     public static final String YUN_HOST = "https://yun.winboll.cc"; | ||||
|      | ||||
|      | ||||
|     String mHost = ""; | ||||
|     RadioButton mrbYunHost; | ||||
|     RadioButton mrbDebugHost; | ||||
|     LogView mLogView; | ||||
|      | ||||
|     @Override | ||||
|     public Activity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_logon); | ||||
|         mLogView = findViewById(R.id.logview); | ||||
|         mLogView.start(); | ||||
|  | ||||
|         mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST; | ||||
|         if (BuildConfig.DEBUG) { | ||||
|             mrbYunHost = findViewById(R.id.rb_yunhost); | ||||
|             mrbDebugHost = findViewById(R.id.rb_debughost); | ||||
|             mrbYunHost.setChecked(!BuildConfig.DEBUG); | ||||
|             mrbDebugHost.setChecked(BuildConfig.DEBUG); | ||||
|         } else { | ||||
|             findViewById(R.id.ll_hostbar).setVisibility(View.GONE); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public void onSwitchHost(View view) { | ||||
|         if (view.getId() == R.id.rb_yunhost) { | ||||
|             mrbDebugHost.setChecked(false); | ||||
|             mHost = YUN_HOST; | ||||
|         } else if (view.getId() == R.id.rb_debughost) { | ||||
|             mrbYunHost.setChecked(false); | ||||
|             mHost = DEBUG_HOST; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         mLogView.start(); | ||||
|     } | ||||
|  | ||||
|     public void onTestLogin(View view) { | ||||
|         LogUtils.d(TAG, "onTestLogin"); | ||||
|         final YunUtils yunUtils = YunUtils.getInstance(this); | ||||
|          | ||||
|         UserInfoModel userInfoModel = new UserInfoModel(); | ||||
|         userInfoModel.setUsername("jian"); | ||||
|         userInfoModel.setPassword("kkiio"); | ||||
|         userInfoModel.setToken("aaa111"); | ||||
|         yunUtils.login(mHost, userInfoModel); | ||||
|     } | ||||
|  | ||||
|     public void onTestRSA(View view) { | ||||
|         LogUtils.d(TAG, "onTestRSA"); | ||||
|         RSAUtils utils = RSAUtils.getInstance(this); | ||||
|          | ||||
|         try { | ||||
|             // 测试 1:首次生成密钥对 | ||||
|             LogUtils.d(TAG, "==== 首次生成密钥对 ===="); | ||||
|             if (utils.keysExist()) { | ||||
|                 LogUtils.d(TAG, "密钥对已生成"); | ||||
|             } else { | ||||
|                 utils.generateAndSaveKeys(); | ||||
|                 LogUtils.d(TAG, "密钥对生成成功。"); | ||||
|             } | ||||
|  | ||||
|             // 测试 2:获取密钥对(自动读取已生成的文件) | ||||
|             KeyPair keyPair = utils.getOrGenerateKeys(); | ||||
|             PublicKey publicKey = keyPair.getPublic(); | ||||
|             PrivateKey privateKey = keyPair.getPrivate(); | ||||
|  | ||||
|             // 打印密钥信息 | ||||
|             LogUtils.d(TAG, "\n==== 密钥信息 ===="); | ||||
|             LogUtils.d(TAG, "公钥算法:" + publicKey.getAlgorithm()); | ||||
|             LogUtils.d(TAG, "公钥编码长度:" + publicKey.getEncoded().length + "字节"); | ||||
|             LogUtils.d(TAG, "私钥算法:" + privateKey.getAlgorithm()); | ||||
|             LogUtils.d(TAG, "私钥编码长度:" + privateKey.getEncoded().length + "字节"); | ||||
|  | ||||
|             // 测试 3:重复调用时检查是否复用文件 | ||||
|             LogUtils.d(TAG, "\n==== 二次调用 ===="); | ||||
|             KeyPair reusedPair = utils.getOrGenerateKeys(); | ||||
|             LogUtils.d(TAG, "是否为同一公钥:" + (publicKey.equals(reusedPair.getPublic()))); // true(单例引用) | ||||
|             LogUtils.d(TAG, "操作完成"); | ||||
|  | ||||
|             String testMessage = "Hello, RSA Encryption!"; | ||||
|  | ||||
|             // 1. 获取或生成密钥对 | ||||
|             PublicKey publicKeyReused = reusedPair.getPublic(); | ||||
|             PrivateKey privateKeyReused = reusedPair.getPrivate(); | ||||
|  | ||||
|             // 2. 公钥加密 | ||||
|             byte[] encryptedData = utils.encryptWithPublicKey(testMessage, publicKeyReused); | ||||
|             LogUtils.d(TAG, "加密后数据(字节长度):" + encryptedData.length); | ||||
|  | ||||
|             // 3. 私钥解密 | ||||
|             String decryptedMessage = utils.decryptWithPrivateKey(encryptedData, privateKeyReused); | ||||
|             LogUtils.d(TAG, "解密结果: " + decryptedMessage); | ||||
|              | ||||
|             // 4. 验证解密是否成功 | ||||
|             if (testMessage.equals(decryptedMessage)) { | ||||
|                 LogUtils.d(TAG, "加密解密测试通过!"); | ||||
|             } else { | ||||
|                 LogUtils.d(TAG, "测试失败:内容不一致"); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,126 @@ | ||||
| package cc.winboll.studio.libappbase.activities; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.os.Bundle; | ||||
| import android.view.View; | ||||
| import cc.winboll.studio.libappbase.BuildConfig; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.R; | ||||
| import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
| import java.io.IOException; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.Request; | ||||
| import okhttp3.Response; | ||||
| import android.widget.RadioButton; | ||||
| import cc.winboll.studio.libappbase.LogView; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/06/04 11:06 | ||||
|  * @Describe 云宝云 | ||||
|  */ | ||||
| public class YunActivity extends Activity implements IWinBoLLActivity { | ||||
|  | ||||
|     public static final String TAG = "YunActivity"; | ||||
|  | ||||
|     public static final String DEBUG_HOST = "http://10.8.0.250:456"; | ||||
|     public static final String YUN_HOST = "https://yun.winboll.cc"; | ||||
|  | ||||
|     String mHost = ""; | ||||
|     RadioButton mrbYunHost; | ||||
|     RadioButton mrbDebugHost; | ||||
|     LogView mLogView; | ||||
|  | ||||
|     @Override | ||||
|     public Activity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_yun); | ||||
|         mLogView = findViewById(R.id.logview); | ||||
|         mLogView.start(); | ||||
|  | ||||
|         mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST; | ||||
|         if (BuildConfig.DEBUG) { | ||||
|             mrbYunHost = findViewById(R.id.rb_yunhost); | ||||
|             mrbDebugHost = findViewById(R.id.rb_debughost); | ||||
|             mrbYunHost.setChecked(!BuildConfig.DEBUG); | ||||
|             mrbDebugHost.setChecked(BuildConfig.DEBUG); | ||||
|         } else { | ||||
|             findViewById(R.id.ll_hostbar).setVisibility(View.GONE); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void onSwitchHost(View view) { | ||||
|         if (view.getId() == R.id.rb_yunhost) { | ||||
|             mrbDebugHost.setChecked(false); | ||||
|             mHost = YUN_HOST; | ||||
|         } else if (view.getId() == R.id.rb_debughost) { | ||||
|             mrbYunHost.setChecked(false); | ||||
|             mHost = DEBUG_HOST; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         mLogView.start(); | ||||
|     } | ||||
|  | ||||
|     public void onTestYun(View view) { | ||||
|         LogUtils.d(TAG, "onTestYun"); | ||||
|         (new Thread(new Runnable(){ | ||||
|                 @Override | ||||
|                 public void run() { | ||||
|                     testYun(); | ||||
|                 } | ||||
|             })).start(); | ||||
|     } | ||||
|  | ||||
|     void testYun() { | ||||
|         OkHttpClient client = new OkHttpClient(); | ||||
|         Request request = new Request.Builder() | ||||
|             .url(mHost + "/backups/") | ||||
|             .build(); | ||||
|  | ||||
|         Response response = null; | ||||
|         try { | ||||
|             response = client.newCall(request).execute(); | ||||
|             if (response.isSuccessful()) { | ||||
|                 String responseBody = ""; | ||||
|                 if (response.body() != null) { | ||||
|                     responseBody = response.body().string(); | ||||
|                 } | ||||
|  | ||||
|                 // 正则匹配:任意主机名 -> Test OK(主机名部分匹配非空字符) | ||||
|                 boolean isMatch = responseBody.matches(".+? -> Test OK"); | ||||
|  | ||||
|                 if (isMatch) { | ||||
|                     LogUtils.d(TAG, responseBody); | ||||
|                 } else { | ||||
|                     LogUtils.d(TAG, "响应内容不匹配,内容:" + responseBody); | ||||
|                 } | ||||
|             } else { | ||||
|                 LogUtils.d(TAG, "请求失败,状态码:" + response.code()); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, "读取响应体失败:" + e.getMessage()); | ||||
|         } catch (Exception e) { | ||||
|             LogUtils.d(TAG, "异常:" + e.getMessage()); | ||||
|             e.printStackTrace(); // Java 7 需显式打印堆栈 | ||||
|         } finally { | ||||
|             // 手动关闭 Response(Java 7 不支持 try-with-resources) | ||||
|             if (response != null && response.body() != null) { | ||||
|                 response.body().close(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,53 @@ | ||||
| package cc.winboll.studio.libappbase.models; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/06/05 11:26 | ||||
|  */ | ||||
|  | ||||
| public class ResponseData { | ||||
|      | ||||
|     public static final String STATUS_SUCCESS = "success"; | ||||
|     public static final String STATUS_ERROR = "error"; | ||||
|      | ||||
|     private String status; | ||||
|     private String message; | ||||
|     private UserInfoModel data; | ||||
|      | ||||
|     public ResponseData() { | ||||
|         this.status = ""; | ||||
|         this.message = ""; | ||||
|         this.data = new UserInfoModel(); | ||||
|     } | ||||
|      | ||||
|     public ResponseData(String status, String message, UserInfoModel data) { | ||||
|         this.status = status; | ||||
|         this.message = message; | ||||
|         this.data = data; | ||||
|     } | ||||
|  | ||||
|     public void setStatus(String status) { | ||||
|         this.status = status; | ||||
|     } | ||||
|  | ||||
|     public String getStatus() { | ||||
|         return status; | ||||
|     } | ||||
|  | ||||
|     public void setMessage(String message) { | ||||
|         this.message = message; | ||||
|     } | ||||
|  | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     public void setData(UserInfoModel data) { | ||||
|         this.data = data; | ||||
|     } | ||||
|  | ||||
|     public UserInfoModel getData() { | ||||
|         return data; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,92 @@ | ||||
| package cc.winboll.studio.libappbase.models; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/06/04 19:14 | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class UserInfoModel extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "UserInfoModel"; | ||||
|  | ||||
|     String username; | ||||
|     String password; | ||||
|     String token; | ||||
|  | ||||
|     public UserInfoModel() { | ||||
|         this.username = ""; | ||||
|         this.password = ""; | ||||
|         this.token = ""; | ||||
|     } | ||||
|  | ||||
|     public void setUsername(String username) { | ||||
|         this.username = username; | ||||
|     } | ||||
|  | ||||
|     public String getUsername() { | ||||
|         return username; | ||||
|     } | ||||
|  | ||||
|     public void setPassword(String password) { | ||||
|         this.password = password; | ||||
|     } | ||||
|  | ||||
|     public String getPassword() { | ||||
|         return password; | ||||
|     } | ||||
|  | ||||
|     public void setToken(String token) { | ||||
|         this.token = token; | ||||
|     } | ||||
|  | ||||
|     public String getToken() { | ||||
|         return token; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return UserInfoModel.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("username").value(getUsername()); | ||||
|         jsonWriter.name("password").value(getPassword()); | ||||
|         jsonWriter.name("token").value(getToken()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("username")) { | ||||
|                 setUsername(jsonReader.nextString()); | ||||
|             } else if (name.equals("password")) { | ||||
|                 setPassword(jsonReader.nextString()); | ||||
|             } else if (name.equals("token")) { | ||||
|                 setToken(jsonReader.nextString()); | ||||
|             } 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; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,128 @@ | ||||
| package cc.winboll.studio.libappbase.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/06/04 20:15 | ||||
|  * @Describe 文件操作类 | ||||
|  */ | ||||
| import java.io.BufferedReader; | ||||
| import java.io.BufferedWriter; | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.FileReader; | ||||
| import java.io.FileWriter; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class FileUtils { | ||||
|     public static final String TAG = "FileUtils"; | ||||
|  | ||||
|     /** | ||||
|      * 读取文件为字节数组(Java 7 语法) | ||||
|      */ | ||||
|     public static byte[] readFileToByteArray(String filePath) { | ||||
|         FileInputStream fis = null; | ||||
|         ByteArrayOutputStream bos = null; | ||||
|         try { | ||||
|             fis = new FileInputStream(filePath); | ||||
|             bos = new ByteArrayOutputStream(); | ||||
|             byte[] buffer = new byte[4096]; | ||||
|             int bytesRead; | ||||
|             while ((bytesRead = fis.read(buffer)) != -1) { | ||||
|                 bos.write(buffer, 0, bytesRead); | ||||
|             } | ||||
|             return bos.toByteArray(); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return null; | ||||
|         } finally { | ||||
|             // 手动关闭流(Java 7 不支持 try-with-resources) | ||||
|             if (fis != null) { | ||||
|                 try { | ||||
|                     fis.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|             if (bos != null) { | ||||
|                 try { | ||||
|                     bos.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 写入字节数组到文件(Java 7 语法) | ||||
|      */ | ||||
|     public static boolean writeByteArrayToFile(byte[] data, String filePath) { | ||||
|         FileOutputStream fos = null; | ||||
|         try { | ||||
|             fos = new FileOutputStream(filePath); | ||||
|             fos.write(data); | ||||
|             return true; | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } finally { | ||||
|             if (fos != null) { | ||||
|                 try { | ||||
|                     fos.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 原字符串读写方法(适配 Java 7) | ||||
|     public static String readFileToString(String filePath) { | ||||
|         BufferedReader reader = null; | ||||
|         try { | ||||
|             reader = new BufferedReader(new FileReader(filePath)); | ||||
|             StringBuilder content = new StringBuilder(); | ||||
|             String line; | ||||
|             while ((line = reader.readLine()) != null) { | ||||
|                 content.append(line).append(System.getProperty("line.separator")); | ||||
|             } | ||||
|             // 去除最后一个换行符(可选) | ||||
|             if (content.length() > 0) { | ||||
|                 content.deleteCharAt(content.length() - 1); | ||||
|             } | ||||
|             return content.toString(); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return null; | ||||
|         } finally { | ||||
|             if (reader != null) { | ||||
|                 try { | ||||
|                     reader.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static boolean writeStringToFile(String content, String filePath, boolean append) { | ||||
|         BufferedWriter writer = null; | ||||
|         try { | ||||
|             writer = new BufferedWriter(new FileWriter(filePath, append)); | ||||
|             writer.write(content); | ||||
|             return true; | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } finally { | ||||
|             if (writer != null) { | ||||
|                 try { | ||||
|                     writer.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,222 @@ | ||||
| package cc.winboll.studio.libappbase.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/06/04 13:36 | ||||
|  * @Describe RSA加密工具 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.util.Base64; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.security.KeyFactory; | ||||
| import java.security.KeyPair; | ||||
| import java.security.KeyPairGenerator; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.security.PrivateKey; | ||||
| import java.security.PublicKey; | ||||
| import java.security.spec.InvalidKeySpecException; | ||||
| import java.security.spec.PKCS8EncodedKeySpec; | ||||
| import java.security.spec.X509EncodedKeySpec; | ||||
| import java.util.Objects; | ||||
| import javax.crypto.Cipher; | ||||
|  | ||||
| public class RSAUtils { | ||||
|     private static final String TAG = "RSAUtils"; | ||||
|     private static final int KEY_SIZE = 2048; | ||||
|     private static final String KEY_ALGORITHM = "RSA"; | ||||
|     private static final String PUBLIC_KEY_FILE = "public.key"; | ||||
|     private static final String PRIVATE_KEY_FILE = "private.key"; | ||||
|     private static final String CIPHER_ALGORITHM = KEY_ALGORITHM + "/ECB/PKCS1Padding"; // 保留原加密方式 | ||||
|  | ||||
|     private final String keyPath; | ||||
|     private static volatile RSAUtils INSTANCE; | ||||
|  | ||||
|     /** | ||||
|      * 构造方法:初始化密钥存储路径(内部存储) | ||||
|      */ | ||||
|     private RSAUtils(Context context) { | ||||
|         keyPath = context.getFilesDir() + File.separator + "keys" + File.separator; // 修正路径格式 | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取单例实例 | ||||
|      */ | ||||
|     public static synchronized RSAUtils getInstance(Context context) { | ||||
|         if (INSTANCE == null) { | ||||
|             INSTANCE = new RSAUtils(context); | ||||
|         } | ||||
|         return INSTANCE; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查密钥文件是否存在 | ||||
|      */ | ||||
|     public boolean keysExist() { | ||||
|         File publicKeyFile = new File(keyPath + PUBLIC_KEY_FILE); | ||||
|         File privateKeyFile = new File(keyPath + PRIVATE_KEY_FILE); | ||||
|         return publicKeyFile.exists() && privateKeyFile.exists(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 生成密钥对并保存到文件 | ||||
|      */ | ||||
|     public void generateAndSaveKeys() throws Exception { | ||||
|         LogUtils.d(TAG, "开始生成 RSA 密钥对(2048位)"); | ||||
|         KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM); | ||||
|         generator.initialize(KEY_SIZE); | ||||
|         KeyPair keyPair = generator.generateKeyPair(); | ||||
|  | ||||
|         saveKey(PUBLIC_KEY_FILE, keyPair.getPublic().getEncoded()); | ||||
|         saveKey(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded()); | ||||
|         LogUtils.d(TAG, "密钥对生成并保存成功"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取或生成密钥对(线程安全) | ||||
|      */ | ||||
|     public KeyPair getOrGenerateKeys() throws Exception { | ||||
|         if (!keysExist()) { | ||||
|             synchronized (RSAUtils.class) { // 双重检查锁,避免多线程重复生成 | ||||
|                 if (!keysExist()) { | ||||
|                     generateAndSaveKeys(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return readKeysFromFile(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 从文件读取密钥对 | ||||
|      */ | ||||
|     private KeyPair readKeysFromFile() throws Exception { | ||||
|         LogUtils.d(TAG, "读取密钥对文件"); | ||||
|         try { | ||||
|             byte[] publicKeyBytes = readFileToBytes(keyPath + PUBLIC_KEY_FILE); | ||||
|             byte[] privateKeyBytes = readFileToBytes(keyPath + PRIVATE_KEY_FILE); | ||||
|  | ||||
|             X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes); | ||||
|             PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes); | ||||
|  | ||||
|             KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); | ||||
|             PublicKey publicKey = factory.generatePublic(publicSpec); | ||||
|             PrivateKey privateKey = factory.generatePrivate(privateSpec); | ||||
|  | ||||
|             return new KeyPair(publicKey, privateKey); | ||||
|         } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { | ||||
|             LogUtils.e(TAG, "密钥文件读取失败:" + e.getMessage()); | ||||
|             throw new Exception("密钥文件损坏或格式错误", e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 保存密钥到文件(通用方法) | ||||
|      */ | ||||
|     private void saveKey(String fileName, byte[] keyBytes) throws IOException { | ||||
|         Objects.requireNonNull(keyBytes, "密钥字节数据不可为空"); | ||||
|         File dir = new File(keyPath); | ||||
|         if (!dir.exists() && !dir.mkdirs()) { | ||||
|             throw new IOException("创建密钥目录失败:" + keyPath); | ||||
|         } | ||||
|  | ||||
|         FileOutputStream fos = null; | ||||
|         try { | ||||
|             fos = new FileOutputStream(keyPath + fileName); | ||||
|             fos.write(keyBytes); | ||||
|         } finally { | ||||
|             if (fos != null) { | ||||
|                 try { | ||||
|                     fos.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 读取文件为字节数组(Java 7 兼容) | ||||
|      */ | ||||
|     private byte[] readFileToBytes(String filePath) throws IOException { | ||||
|         File file = new File(filePath); | ||||
|         if (!file.exists() || file.isDirectory()) { | ||||
|             throw new IOException("文件不存在或为目录:" + filePath); | ||||
|         } | ||||
|  | ||||
|         FileInputStream fis = null; | ||||
|         try { | ||||
|             fis = new FileInputStream(file); | ||||
|             byte[] data = new byte[(int) file.length()]; | ||||
|             int bytesRead = fis.read(data); | ||||
|             if (bytesRead != data.length) { | ||||
|                 throw new IOException("文件读取不完整"); | ||||
|             } | ||||
|             return data; | ||||
|         } finally { | ||||
|             if (fis != null) { | ||||
|                 try { | ||||
|                     fis.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 公钥加密(带参数校验) | ||||
|      */ | ||||
|     public byte[] encryptWithPublicKey(String plainText, PublicKey publicKey) throws Exception { | ||||
|         Objects.requireNonNull(plainText, "明文不可为空"); | ||||
|         Objects.requireNonNull(publicKey, "公钥不可为空"); | ||||
|  | ||||
|         Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); | ||||
|         cipher.init(Cipher.ENCRYPT_MODE, publicKey); | ||||
|  | ||||
|         // 检查数据长度是否超过 RSA 限制(2048位密钥最大明文为 214字节,PKCS1Padding) | ||||
|         int maxPlainTextSize = cipher.getBlockSize() - 11; // PKCS1Padding 固定填充长度 | ||||
|         if (plainText.getBytes("UTF-8").length > maxPlainTextSize) { | ||||
|             throw new IllegalArgumentException("明文过长,最大支持 " + maxPlainTextSize + " 字节"); | ||||
|         } | ||||
|  | ||||
|         return cipher.doFinal(plainText.getBytes("UTF-8")); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 私钥解密(带参数校验) | ||||
|      */ | ||||
|     public String decryptWithPrivateKey(byte[] encryptedData, PrivateKey privateKey) throws Exception { | ||||
|         Objects.requireNonNull(encryptedData, "密文不可为空"); | ||||
|         Objects.requireNonNull(privateKey, "私钥不可为空"); | ||||
|  | ||||
|         Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); | ||||
|         cipher.init(Cipher.DECRYPT_MODE, privateKey); | ||||
|         byte[] decryptedBytes = cipher.doFinal(encryptedData); | ||||
|         return new String(decryptedBytes, "UTF-8"); | ||||
|     } | ||||
|     /** | ||||
|      * 将 HTTP 传输的 Base64 字符串还原为加密字节数组(Java 7 兼容) | ||||
|      * @param httpString Base64 字符串(非 null) | ||||
|      * @return 加密字节数组 | ||||
|      * @throws IllegalArgumentException 解码失败时抛出 | ||||
|      */ | ||||
|     public byte[] httpStringToEncryptBytes(String httpString) { | ||||
|         Objects.requireNonNull(httpString, "HTTP 字符串不可为空"); | ||||
|  | ||||
|         // 计算缺失的填充符数量(Java 7 不支持 repeat(),手动拼接) | ||||
|         int pad = httpString.length() % 4; | ||||
|         StringBuilder paddedString = new StringBuilder(httpString); | ||||
|         if (pad != 0) { | ||||
|             for (int i = 0; i < pad; i++) { | ||||
|                 paddedString.append('='); // 补全 '=' | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 使用 Base64 解码(Android 原生 Base64 类兼容 Java 7) | ||||
|         return Base64.decode(paddedString.toString(), Base64.URL_SAFE); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,281 @@ | ||||
| package cc.winboll.studio.libappbase.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/06/04 17:21 | ||||
|  * @Describe 应用登录与接口工具 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.os.Handler; | ||||
| import android.os.Looper; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.models.ResponseData; | ||||
| import cc.winboll.studio.libappbase.models.UserInfoModel; | ||||
| import com.google.gson.Gson; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.net.URLDecoder; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.security.KeyPair; | ||||
| import java.security.PrivateKey; | ||||
| import java.security.PublicKey; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import okhttp3.Call; | ||||
| import okhttp3.Callback; | ||||
| import okhttp3.MediaType; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.Request; | ||||
| import okhttp3.RequestBody; | ||||
| import okhttp3.Response; | ||||
| import java.io.UnsupportedEncodingException; | ||||
|  | ||||
| public class YunUtils { | ||||
|     public static final String TAG = "YunUtils"; | ||||
|     // 私有静态实例,类加载时创建 | ||||
|     private static volatile YunUtils INSTANCE; | ||||
|     Context mContext; | ||||
|     UserInfoModel mUserInfoModel; | ||||
|     String token = ""; | ||||
|     String mDataFolderPath = ""; | ||||
|     String mUserInfoModelPath = ""; | ||||
|  | ||||
|     private static final int CONNECT_TIMEOUT = 15; // 连接超时时间(秒) | ||||
|     private static final int READ_TIMEOUT = 20;    // 读取超时时间(秒) | ||||
|     private static volatile YunUtils instance; | ||||
|     private OkHttpClient okHttpClient; | ||||
|     private Handler mainHandler; // 主线程 Handler | ||||
|  | ||||
|     // 私有构造方法,防止外部实例化 | ||||
|     private YunUtils(Context context) { | ||||
|         LogUtils.d(TAG, "YunUtils"); | ||||
|         mContext = context; | ||||
|         mDataFolderPath = mContext.getExternalFilesDir(TAG).toString(); | ||||
|         File fTest = new File(mDataFolderPath); | ||||
|         if (!fTest.exists()) { | ||||
|             fTest.mkdirs(); | ||||
|         } | ||||
|         mUserInfoModelPath = mDataFolderPath + File.separator + "UserInfoModel.rsajson"; | ||||
|  | ||||
|         okHttpClient = new OkHttpClient.Builder() | ||||
|             .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) | ||||
|             .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) | ||||
|             .build(); | ||||
|         mainHandler = new Handler(Looper.getMainLooper()); // 获取主线程 Looper | ||||
|     } | ||||
|  | ||||
|     // 公共静态方法,返回唯一实例 | ||||
|     public static synchronized YunUtils getInstance(Context context) { | ||||
|         LogUtils.d(TAG, "getInstance"); | ||||
|         if (INSTANCE == null) { | ||||
|             INSTANCE = new YunUtils(context); | ||||
|         } | ||||
|         return INSTANCE; | ||||
|     } | ||||
|  | ||||
|     public void checkLoginStatus() { | ||||
|         String token = getLocalToken(); | ||||
|         LogUtils.d(TAG, String.format("checkLoginStatus token is %s", token)); | ||||
|     } | ||||
|  | ||||
|     String getLocalToken() { | ||||
|         UserInfoModel userInfoModel = loadUserInfoModel(); | ||||
|         return (userInfoModel == null) ?"": userInfoModel.getToken(); | ||||
|     } | ||||
|  | ||||
|     public void login(String host, UserInfoModel userInfoModel) { | ||||
|         LogUtils.d(TAG, "login"); | ||||
|  | ||||
|         // 发送 POST 请求 | ||||
|         String apiUrl = host + "/login/index.php"; | ||||
|         // 序列化对象为JSON | ||||
|         Gson gson = new Gson(); | ||||
|         String jsonData = gson.toJson(userInfoModel); // 自动生成标准JSON | ||||
|         //String jsonData = userInfoModel.toString(); | ||||
|         LogUtils.d(TAG, "要发送的数据 : " + jsonData); | ||||
|  | ||||
|         sendPostRequest(apiUrl, jsonData, new OnResponseListener() { | ||||
|                 // 成功回调(主线程) | ||||
|                 @Override | ||||
|                 public void onSuccess(String responseBody) { | ||||
|                     LogUtils.d(TAG, "onSuccess"); | ||||
|                     LogUtils.d(TAG, String.format("responseBody %s", responseBody)); | ||||
|                     Gson gson = new Gson(); | ||||
|                     ResponseData result = gson.fromJson(responseBody, ResponseData.class); // 转为 Result 实例 | ||||
|                     if(result.getStatus().equals(ResponseData.STATUS_SUCCESS)) { | ||||
|                          | ||||
|                             UserInfoModel userInfoModel = result.getData(); | ||||
|                             if (userInfoModel != null) { | ||||
|                                 LogUtils.d(TAG, "收到网站 UserInfoModel"); | ||||
|                                 String token = userInfoModel.getToken(); | ||||
|                                 saveLocalToken(token); | ||||
|                                 checkLoginStatus(); | ||||
|                             } | ||||
|                         | ||||
|                     } else if(result.getStatus().equals(ResponseData.STATUS_ERROR)) { | ||||
|                         try { | ||||
|                             String decodedMessage = URLDecoder.decode(result.getMessage(), "UTF-8"); | ||||
|                             LogUtils.d(TAG, "服务器返回信息: " + decodedMessage); | ||||
|                         } catch (UnsupportedEncodingException e) { | ||||
|                             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // 失败回调(主线程) | ||||
|                 @Override | ||||
|                 public void onFailure(String errorMsg) { | ||||
|                     LogUtils.d(TAG, errorMsg); | ||||
|                     // 处理错误 | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     public void saveLocalToken(String token) { | ||||
|         UserInfoModel userInfoModel = new UserInfoModel(); | ||||
|         userInfoModel.setToken(token); | ||||
|         saveUserInfoModel(userInfoModel); | ||||
|     } | ||||
|  | ||||
|     UserInfoModel loadUserInfoModel() { | ||||
|         LogUtils.d(TAG, "loadUserInfoModel"); | ||||
|         if (new File(mUserInfoModelPath).exists()) { | ||||
|             try { | ||||
|                 // 加载加密后的模型数据 | ||||
|                 byte[] encryptedData = FileUtils.readFileToByteArray(mUserInfoModelPath); | ||||
|                 // 加载 RSA 工具 | ||||
|                 RSAUtils utils = RSAUtils.getInstance(mContext); | ||||
|                 KeyPair keyPair = utils.getOrGenerateKeys(); | ||||
|                 //PublicKey publicKey = keyPair.getPublic(); | ||||
|                 PrivateKey privateKey = keyPair.getPrivate(); | ||||
|                 // 私钥解密模型数据 | ||||
|                 String szInfo = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate()); | ||||
|                 LogUtils.d(TAG, String.format("szInfo %s", szInfo)); | ||||
|                 mUserInfoModel = UserInfoModel.parseStringToBean(szInfo, UserInfoModel.class); | ||||
|                 if (mUserInfoModel == null) { | ||||
|                     LogUtils.d(TAG, "模型数据解析为空数据。"); | ||||
|                 } | ||||
|                 LogUtils.d(TAG, "UserInfoModel 解密加载结束。"); | ||||
|             } catch (Exception e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|         } else { | ||||
|             LogUtils.d(TAG, "云服务登录信息不存在。"); | ||||
|             mUserInfoModel = null; | ||||
|         } | ||||
|         return mUserInfoModel; | ||||
|     } | ||||
|  | ||||
|     void saveUserInfoModel(UserInfoModel userInfoModel) { | ||||
|         LogUtils.d(TAG, "saveUserInfoModel"); | ||||
|         try { | ||||
|             String szInfo = userInfoModel.toString(); | ||||
|             LogUtils.d(TAG, "原始数据: " + szInfo); | ||||
|  | ||||
|             RSAUtils utils = RSAUtils.getInstance(mContext); | ||||
|             KeyPair keyPair = utils.getOrGenerateKeys(); | ||||
|             PublicKey publicKey = keyPair.getPublic(); | ||||
|  | ||||
|             // 公钥加密(传入字节数组,避免中间字符串转换) | ||||
|             byte[] encryptedData = utils.encryptWithPublicKey(szInfo, publicKey); | ||||
|  | ||||
|             // 保存加密字节数组到文件(直接操作字节,无需转字符串) | ||||
|             FileUtils.writeByteArrayToFile(encryptedData, mUserInfoModelPath); | ||||
|             LogUtils.d(TAG, "加密数据已保存"); | ||||
|  | ||||
|             // 测试解密(仅调试用) | ||||
|             String szInfo2 = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate()); | ||||
|             LogUtils.d(TAG, "解密结果: " + szInfo2); | ||||
|  | ||||
|             mUserInfoModel = UserInfoModel.parseStringToBean(szInfo2, UserInfoModel.class); | ||||
|             if (mUserInfoModel == null) { | ||||
|                 LogUtils.d(TAG, "模型解析失败"); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             LogUtils.d(TAG, "加密/解密失败: " + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 发送 POST 请求(JSON 数据) | ||||
|     public void sendPostRequest(String url, String data, OnResponseListener listener) { | ||||
|         RequestBody requestBody = RequestBody.create( | ||||
|             MediaType.parse("application/json; charset=utf-8"), // 关键头信息 | ||||
|             data.getBytes(StandardCharsets.UTF_8) | ||||
|         ); | ||||
|  | ||||
|         Request request = new Request.Builder() | ||||
|             .url(url) | ||||
|             .post(requestBody) | ||||
|             .addHeader("Content-Type", "application/json") // 显式添加头 | ||||
|             .build(); | ||||
|  | ||||
|         executeRequest(request, listener); | ||||
|     } | ||||
|  | ||||
|     // 发送 GET 请求 | ||||
|     public void sendGetRequest(String url, OnResponseListener listener) { | ||||
|         Request request = new Request.Builder() | ||||
|             .url(url) | ||||
|             .get() | ||||
|             .build(); | ||||
|         executeRequest(request, listener); | ||||
|     } | ||||
|  | ||||
|     // 执行请求(子线程处理) | ||||
|     private void executeRequest(final Request request, final OnResponseListener listener) { | ||||
|         okHttpClient.newCall(request).enqueue(new Callback() { | ||||
|                 // 响应成功(子线程) | ||||
|                 @Override | ||||
|                 public void onResponse(Call call, Response response) throws IOException { | ||||
|                     try { | ||||
|                         if (!response.isSuccessful()) { | ||||
|                             postFailure(listener, "响应码错误:" + response.code()); | ||||
|                             return; | ||||
|                         } | ||||
|                         String responseBody = response.body().string(); | ||||
|                         postSuccess(listener, responseBody); | ||||
|                     } catch (Exception e) { | ||||
|                         postFailure(listener, "解析失败:" + e.getMessage()); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // 响应失败(子线程) | ||||
|                 @Override | ||||
|                 public void onFailure(Call call, IOException e) { | ||||
|                     postFailure(listener, "网络失败:" + e.getMessage()); | ||||
|                 } | ||||
|  | ||||
|                 // 主线程回调(使用 Handler) | ||||
|                 private void postSuccess(final OnResponseListener listener, final String msg) { | ||||
|                     mainHandler.post(new Runnable() { | ||||
|                             @Override | ||||
|                             public void run() { | ||||
|                                 listener.onSuccess(msg); | ||||
|                             } | ||||
|                         }); | ||||
|                 } | ||||
|  | ||||
|                 private void postFailure(final OnResponseListener listener, final String msg) { | ||||
|                     mainHandler.post(new Runnable() { | ||||
|                             @Override | ||||
|                             public void run() { | ||||
|                                 listener.onFailure(msg); | ||||
|                             } | ||||
|                         }); | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     public interface OnResponseListener { | ||||
|         /** | ||||
|          * 成功响应(主线程回调) | ||||
|          * @param responseBody 响应体字符串 | ||||
|          */ | ||||
|         void onSuccess(String responseBody); | ||||
|  | ||||
|         /** | ||||
|          * 失败回调(包含错误信息) | ||||
|          * @param errorMsg 错误描述 | ||||
|          */ | ||||
|         void onFailure(String errorMsg); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										68
									
								
								libappbase/src/main/res/layout/activity_logon.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,68 @@ | ||||
| <?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"> | ||||
|  | ||||
|     <LinearLayout | ||||
|         android:orientation="horizontal" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_gravity="right" | ||||
|         android:gravity="right" | ||||
|         android:padding="10dp" | ||||
|         android:id="@+id/ll_hostbar"> | ||||
|  | ||||
|         <RadioButton | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="10.8.0.250:456" | ||||
|             android:id="@+id/rb_debughost" | ||||
|             android:onClick="onSwitchHost"/> | ||||
|  | ||||
|         <RadioButton | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="yun.winboll.cc" | ||||
|             android:id="@+id/rb_yunhost" | ||||
|             android:onClick="onSwitchHost"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|      | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:gravity="right"> | ||||
|  | ||||
| 		<Button | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="Test RSA" | ||||
|             android:onClick="onTestRSA"/> | ||||
|  | ||||
|         <Button | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="Test Login" | ||||
|             android:onClick="onTestLogin"/> | ||||
|          | ||||
| 	</LinearLayout> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="vertical" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:layout_weight="1.0"> | ||||
|  | ||||
| 		<cc.winboll.studio.libappbase.LogView | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="match_parent" | ||||
| 			android:id="@+id/logview"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										63
									
								
								libappbase/src/main/res/layout/activity_yun.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,63 @@ | ||||
| <?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"> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:layout_gravity="right" | ||||
| 		android:gravity="right" | ||||
| 		android:padding="10dp" | ||||
| 		android:id="@+id/ll_hostbar"> | ||||
|  | ||||
| 		<RadioButton | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="10.8.0.250:456" | ||||
| 			android:id="@+id/rb_debughost" | ||||
| 			android:onClick="onSwitchHost"/> | ||||
|  | ||||
| 		<RadioButton | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="yun.winboll.cc" | ||||
| 			android:id="@+id/rb_yunhost" | ||||
| 			android:onClick="onSwitchHost"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:layout_gravity="right" | ||||
| 		android:gravity="right"> | ||||
|  | ||||
| 		<Button | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="TestYun" | ||||
| 			android:onClick="onTestYun"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="vertical" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:layout_weight="1.0"> | ||||
|  | ||||
| 		<cc.winboll.studio.libappbase.LogView | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="match_parent" | ||||
| 			android:id="@+id/logview"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
| @@ -21,7 +21,7 @@ android { | ||||
|  | ||||
| dependencies { | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
|     api 'cc.winboll.studio:libappbase:15.8.2' | ||||
|     api 'cc.winboll.studio:libappbase:15.9.5' | ||||
|      | ||||
|     // 二维码类库 | ||||
|     api 'com.google.zxing:core:3.4.1' | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue Jun 03 15:05:42 HKT 2025 | ||||
| stageCount=5 | ||||
| #Mon Sep 01 07:56:11 HKT 2025 | ||||
| stageCount=7 | ||||
| libraryProject=libapputils | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.4 | ||||
| publishVersion=15.8.6 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.8.5 | ||||
| baseBetaVersion=15.8.7 | ||||
|   | ||||
| @@ -0,0 +1,32 @@ | ||||
| package cc.winboll.studio.libapputils.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@188.com> | ||||
|  * @Date 2025/09/01 07:49 | ||||
|  * @Describe .* 前置预防针 | ||||
|  regex pointer preventive injection | ||||
|  简称 RegexPPi | ||||
|  */ | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| public class RegexPPiUtils { | ||||
|  | ||||
| 	public static final String TAG = "RegexPPiUtils"; | ||||
|  | ||||
| 	// | ||||
| 	// 检验文本是否满足适合正则表达式模式计算 | ||||
| 	// | ||||
| 	public static boolean isPPiOK(String text) { | ||||
| 		//String text = "这里是一些任意的文本内容"; | ||||
| 		String regex = ".*"; | ||||
| 		Pattern pattern = Pattern.compile(regex); | ||||
| 		Matcher matcher = pattern.matcher(text); | ||||
| 		/*if (matcher.matches()) { | ||||
| 		 System.out.println("文本满足该正则表达式模式"); | ||||
| 		 } else { | ||||
| 		 System.out.println("文本不满足该正则表达式模式"); | ||||
| 		 }*/ | ||||
| 		return matcher.matches(); | ||||
| 	} | ||||
| } | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Fri Jan 10 22:03:57 GMT 2025 | ||||
| #Tue Jun 24 11:17:30 GMT 2025 | ||||
| stageCount=0 | ||||
| libraryProject=libjc | ||||
| baseVersion=1.0 | ||||
| publishVersion=1.0.0 | ||||
| buildCount=133 | ||||
| buildCount=135 | ||||
| baseBetaVersion=1.0.1 | ||||
|   | ||||
| @@ -21,7 +21,7 @@ public class Main { | ||||
|     public final static int JAR_RUNNING_MODE_JCNDK_DEBUG = 4; | ||||
|     public final static int JAR_RUNNING_MODE_JC = 5; | ||||
|     public final static int JAR_RUNNING_MODE_JC_DEBUG = 6; | ||||
|     public enum JAR_RUNNING_MODE { | ||||
|     public static enum JAR_RUNNING_MODE { | ||||
|         UNKNOWN(JAR_RUNNING_MODE_UNKNOWN), | ||||
|         CONSOLE(JAR_RUNNING_MODE_CONSOLE), | ||||
|         CONSOLE_DEBUG(JAR_RUNNING_MODE_CONSOLE_DEBUG), | ||||
|   | ||||
| @@ -29,7 +29,7 @@ android { | ||||
|         // versionName 更新后需要手动设置  | ||||
|         // .winboll/winbollBuildProps.properties 文件的 stageCount=0 | ||||
|         // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" | ||||
|         versionName "15.2"  | ||||
|         versionName "15.3"  | ||||
|         if(true) { | ||||
|             versionName = genVersionName("${versionName}") | ||||
|         } | ||||
| @@ -45,9 +45,9 @@ android { | ||||
|  | ||||
| dependencies { | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
|     api 'cc.winboll.studio:libaes:15.8.0' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.1' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.1' | ||||
|     api 'cc.winboll.studio:libaes:15.9.2' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.4' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.4' | ||||
|      | ||||
| 	api 'io.github.medyo:android-about-page:2.0.0' | ||||
|     api 'com.github.getActivity:ToastUtils:10.5' | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue May 20 20:39:06 HKT 2025 | ||||
| stageCount=6 | ||||
| #Thu Jul 03 13:50:15 HKT 2025 | ||||
| stageCount=2 | ||||
| libraryProject= | ||||
| baseVersion=15.2 | ||||
| publishVersion=15.2.5 | ||||
| baseVersion=15.3 | ||||
| publishVersion=15.3.1 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.2.6 | ||||
| baseBetaVersion=15.3.2 | ||||
|   | ||||
| @@ -17,7 +17,7 @@ import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
| import cc.winboll.studio.mymessagemanager.App; | ||||
| import cc.winboll.studio.mymessagemanager.R; | ||||
|  | ||||
| public class AboutActivity extends WinBollActivity implements IWinBoLLActivity { | ||||
| public class AboutActivity extends WinBoLLActivity implements IWinBoLLActivity { | ||||
|  | ||||
|     public static final String TAG = "AboutActivity"; | ||||
|  | ||||
| @@ -79,11 +79,11 @@ public class AboutActivity extends WinBollActivity implements IWinBoLLActivity { | ||||
|         appInfo.setAppName(getString(R.string.app_name)); | ||||
|         appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll); | ||||
|         appInfo.setAppDescription(getString(R.string.app_description)); | ||||
|         appInfo.setAppGitName("APP"); | ||||
|         appInfo.setAppGitName("APPBase"); | ||||
|         appInfo.setAppGitOwner("Studio"); | ||||
|         appInfo.setAppGitAPPBranch(szBranchName); | ||||
|         appInfo.setAppGitAPPSubProjectFolder(szBranchName); | ||||
|         appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=MyMessageManager"); | ||||
|         appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=5&extra=page%3D1"); | ||||
|         appInfo.setAppAPKName("MyMessageManager"); | ||||
|         appInfo.setAppAPKFolderName("MyMessageManager"); | ||||
|         return new AboutView(mContext, appInfo); | ||||
|   | ||||
| @@ -103,6 +103,9 @@ abstract public class BaseActivity extends AppCompatActivity { | ||||
|         } else if (R.id.item_defaulttheme == item.getItemId()) { | ||||
|             AESThemeUtil.saveThemeStyleID(this, R.style.MyAppTheme); | ||||
|             recreate(); | ||||
|         } else if (R.id.item_defaulttheme == item.getItemId()) { | ||||
|             AESThemeUtil.saveThemeStyleID(this, R.style.MyAppTheme); | ||||
|             recreate(); | ||||
|         } | ||||
|         //ToastUtils.show("nThemeStyleID " + Integer.toString(nThemeStyleID)); | ||||
|  | ||||
|   | ||||
| @@ -128,7 +128,7 @@ public class MainActivity extends BaseActivity { | ||||
|         mToolbar = findViewById(R.id.activitymainASupportToolbar1); | ||||
|         mToolbar.setSubtitle(getString(R.string.activity_name_main)); | ||||
|         setSupportActionBar(mToolbar); | ||||
|          | ||||
|  | ||||
|         boolean isEnableService = mAppConfigUtil.mAppConfigBean.isEnableService(); | ||||
|         msvEnableService = findViewById(R.id.activitymainSwitchView1); | ||||
|         msvEnableService.setChecked(isEnableService); | ||||
| @@ -269,17 +269,9 @@ public class MainActivity extends BaseActivity { | ||||
|     public boolean onCreateOptionsMenu(Menu menu) { | ||||
|         //return super.onCreateOptionsMenu(menu); | ||||
|         getMenuInflater().inflate(R.menu.toolbar_main, menu); | ||||
|  | ||||
|         /*ThemeUtil.BaseTheme baseTheme = ThemeUtil.getTheme(mAppConfigUtil.mAppConfigBean.getAppThemeID()); | ||||
|         if (baseTheme == ThemeUtil.BaseTheme.DEFAULT) { | ||||
|             menu.findItem(R.id.app_defaulttheme).setChecked(true); | ||||
|         } else if (baseTheme == ThemeUtil.BaseTheme.SKY) { | ||||
|             menu.findItem(R.id.app_skytheme).setChecked(true); | ||||
|         } else if (baseTheme == ThemeUtil.BaseTheme.GOLDEN) { | ||||
|             menu.findItem(R.id.app_goldentheme).setChecked(true); | ||||
|         }*/ | ||||
|  | ||||
|         return super.onCreateOptionsMenu(menu); | ||||
|         super.onCreateOptionsMenu(menu); | ||||
| 		getMenuInflater().inflate(R.menu.toolbar_main2, menu); | ||||
| 		return true; | ||||
|     } | ||||
|  | ||||
|     public static void reloadSMS() { | ||||
| @@ -306,7 +298,7 @@ public class MainActivity extends BaseActivity { | ||||
|             i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             startActivity(i); | ||||
| 		} else if (nItemId ==  R.id.app_log) { | ||||
|             //App.getWinBoLLActivityManager().startLogActivity(this); | ||||
|             App.getWinBoLLActivityManager().startLogActivity(this); | ||||
|         } else if (nItemId ==  R.id.app_unittest) { | ||||
|             Intent i = new Intent(MainActivity.this, UnitTestActivity.class); | ||||
|             i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
| @@ -324,7 +316,7 @@ public class MainActivity extends BaseActivity { | ||||
|             i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             startActivity(i); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -13,9 +13,9 @@ import cc.winboll.studio.libaes.beans.AESThemeBean; | ||||
| import cc.winboll.studio.libaes.utils.AESThemeUtil; | ||||
| import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
| 
 | ||||
| public class WinBollActivity extends AppCompatActivity implements IWinBoLLActivity { | ||||
| public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity { | ||||
| 
 | ||||
|     public static final String TAG = "WinBollActivity"; | ||||
|     public static final String TAG = "WinBoLLActivity"; | ||||
| 
 | ||||
|     protected volatile AESThemeBean.ThemeType mThemeType; | ||||
| 
 | ||||
| @@ -34,7 +34,7 @@ | ||||
|  | ||||
| 	</ScrollView> | ||||
|  | ||||
| 	<cc.winboll.studio.shared.log.LogView | ||||
| 	<cc.winboll.studio.libappbase.LogView | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:layout_weight="1.0" | ||||
|   | ||||
| @@ -17,26 +17,4 @@ | ||||
|     <item | ||||
|         android:id="@+id/app_smsrule" | ||||
|         android:title="@string/text_smsrule"/> | ||||
|     <item android:title="@string/app_developoptions"> | ||||
|         <menu> | ||||
|             <item | ||||
|                 android:id="@+id/app_log" | ||||
|                 android:title="@string/app_log"/> | ||||
|             <item | ||||
|                 android:id="@+id/app_unittest" | ||||
|                 android:title="@string/app_unittest"/> | ||||
|             <item | ||||
|                 android:id="@+id/app_crashtest" | ||||
|                 android:title="@string/app_crashtest"/> | ||||
|         </menu> | ||||
|     </item> | ||||
|     <item | ||||
|         android:id="@+id/app_appsettings" | ||||
|         android:title="@string/text_appsettings"/> | ||||
|     <item | ||||
|         android:id="@+id/app_about" | ||||
|         android:title="@string/app_about"/> | ||||
|     <item | ||||
|         android:id="@+id/app_smsrecycle" | ||||
|         android:title="@string/app_smsrecycle"/> | ||||
| </menu> | ||||
|   | ||||
							
								
								
									
										26
									
								
								mymessagemanager/src/main/res/menu/toolbar_main2.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,26 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
| 	<item android:title="@string/app_developoptions"> | ||||
|         <menu> | ||||
|             <item | ||||
|                 android:id="@+id/app_log" | ||||
|                 android:title="@string/app_log"/> | ||||
|             <item | ||||
|                 android:id="@+id/app_unittest" | ||||
|                 android:title="@string/app_unittest"/> | ||||
|             <item | ||||
|                 android:id="@+id/app_crashtest" | ||||
|                 android:title="@string/app_crashtest"/> | ||||
|         </menu> | ||||
|     </item> | ||||
|     <item | ||||
|         android:id="@+id/app_appsettings" | ||||
|         android:title="@string/text_appsettings"/> | ||||
|     <item | ||||
|         android:id="@+id/app_about" | ||||
|         android:title="@string/app_about"/> | ||||
|     <item | ||||
|         android:id="@+id/app_smsrecycle" | ||||
|         android:title="@string/app_smsrecycle"/> | ||||
| </menu> | ||||
							
								
								
									
										1
									
								
								numtable/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
| /build | ||||
							
								
								
									
										34
									
								
								numtable/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,34 @@ | ||||
| # NumTable | ||||
|  | ||||
| #### 介绍 | ||||
| 桌面图标多元应用。提供一个数字风格化的桌面标识图标,快捷的桌面标识创建途径。主要应用于桌面繁多时的页面环境辅助识别。 | ||||
|  | ||||
| #### 软件架构 | ||||
| 适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。 | ||||
| 也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。 | ||||
|  | ||||
|  | ||||
| #### Gradle 编译说明 | ||||
| 调试版编译命令 :gradle assembleBetaDebug | ||||
| 阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh numtable | ||||
|  | ||||
| #### 使用说明 | ||||
|  | ||||
| #### 参与贡献 | ||||
|  | ||||
| 1.  Fork 本仓库 | ||||
| 2.  新建 Feat_xxx 分支 | ||||
| 3.  提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>) | ||||
| 4.  新建 Pull Request | ||||
|  | ||||
|  | ||||
| #### 特技 | ||||
|  | ||||
| 1.  使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md | ||||
| 2.  Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) | ||||
| 3.  你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 | ||||
| 4.  [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 | ||||
| 5.  Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) | ||||
| 6.  Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) | ||||
|  | ||||
| #### 参考文档 | ||||
							
								
								
									
										1
									
								
								numtable/app_update_description.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
|  | ||||
							
								
								
									
										73
									
								
								numtable/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,73 @@ | ||||
| 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 32 | ||||
|     buildToolsVersion "32.0.0" | ||||
|  | ||||
|     defaultConfig { | ||||
|         applicationId "cc.winboll.studio.numtable" | ||||
|         minSdkVersion 24 | ||||
|         targetSdkVersion 30 | ||||
|         versionCode 1 | ||||
|         // versionName 更新后需要手动设置  | ||||
|         // .winboll/winbollBuildProps.properties 文件的 stageCount=0 | ||||
|         // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" | ||||
|         versionName "15.1" | ||||
|         if(true) { | ||||
|             versionName = genVersionName("${versionName}") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
|      | ||||
|     // 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.github.getActivity:ToastUtils:10.5' | ||||
|     // 网络连接类库 | ||||
|     api 'com.squareup.okhttp3:okhttp:4.4.1' | ||||
|     // AndroidX 类库 | ||||
|     api 'androidx.appcompat:appcompat:1.1.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' | ||||
|      | ||||
|     api 'cc.winboll.studio:libaes:15.8.0' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.2' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.2' | ||||
| } | ||||
							
								
								
									
										8
									
								
								numtable/build.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Sun Jun 08 21:21:11 HKT 2025 | ||||
| stageCount=1 | ||||
| libraryProject= | ||||
| baseVersion=15.1 | ||||
| publishVersion=15.1.0 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.1.1 | ||||
							
								
								
									
										21
									
								
								numtable/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | ||||
| # Add project specific ProGuard rules here. | ||||
| # You can control the set of applied configuration files using the | ||||
| # proguardFiles setting in build.gradle. | ||||
| # | ||||
| # For more details, see | ||||
| #   http://developer.android.com/guide/developing/tools/proguard.html | ||||
|  | ||||
| # If your project uses WebView with JS, uncomment the following | ||||
| # and specify the fully qualified class name to the JavaScript interface | ||||
| # class: | ||||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||
| #   public *; | ||||
| #} | ||||
|  | ||||
| # Uncomment this to preserve the line number information for | ||||
| # debugging stack traces. | ||||
| #-keepattributes SourceFile,LineNumberTable | ||||
|  | ||||
| # If you keep the line number information, uncomment this to | ||||
| # hide the original source file name. | ||||
| #-renamesourcefileattribute SourceFile | ||||
							
								
								
									
										14
									
								
								numtable/src/beta/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | ||||
| <?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_launcher_beta"> | ||||
|  | ||||
|         <!-- Put flavor specific code here --> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
|  | ||||
							
								
								
									
										6
									
								
								numtable/src/beta/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|  | ||||
|     <string name="app_name">NumTable +</string> | ||||
|  | ||||
| </resources> | ||||
							
								
								
									
										37
									
								
								numtable/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,37 @@ | ||||
| <?xml version='1.0' encoding='utf-8'?> | ||||
| <manifest | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="cc.winboll.studio.numtable"> | ||||
|  | ||||
|     <application | ||||
|         android:allowBackup="true" | ||||
|         android:icon="@drawable/ic_launcher" | ||||
|         android:roundIcon="@drawable/ic_launcher" | ||||
|         android:label="@string/app_name" | ||||
|         android:theme="@style/MyAppTheme" | ||||
|         android:resizeableActivity="true" | ||||
|         android:name=".App"> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
|             android:label="@string/app_name"> | ||||
|  | ||||
|             <intent-filter> | ||||
|  | ||||
|                 <action android:name="android.intent.action.MAIN"/> | ||||
|  | ||||
|                 <category android:name="android.intent.category.LAUNCHER"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|         </activity> | ||||
|  | ||||
|         <meta-data | ||||
|             android:name="android.max_aspect" | ||||
|             android:value="4.0"/> | ||||
|  | ||||
|         <activity android:name=".GlobalApplication$CrashActivity"/> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
							
								
								
									
										345
									
								
								numtable/src/main/java/cc/winboll/studio/numtable/App.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,345 @@ | ||||
| package cc.winboll.studio.numtable; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageInfo; | ||||
| import android.content.res.Resources; | ||||
| import android.graphics.Typeface; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.Handler; | ||||
| import android.os.Looper; | ||||
| import android.text.TextUtils; | ||||
| import android.util.Log; | ||||
| import android.view.Gravity; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.HorizontalScrollView; | ||||
| import android.widget.ScrollView; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| import cc.winboll.studio.libappbase.GlobalApplication; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import com.hjq.toast.style.WhiteToastStyle; | ||||
| import java.io.ByteArrayInputStream; | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.Closeable; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
| import java.lang.Thread.UncaughtExceptionHandler; | ||||
| import java.text.DateFormat; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Arrays; | ||||
| import java.util.Date; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | ||||
|  | ||||
| public class App extends GlobalApplication { | ||||
|  | ||||
|     private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper()); | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|          | ||||
|         // 初始化 Toast 框架 | ||||
|         ToastUtils.init(this); | ||||
|         // 设置 Toast 布局样式 | ||||
|         //ToastUtils.setView(R.layout.view_toast); | ||||
|         ToastUtils.setStyle(new WhiteToastStyle()); | ||||
|         ToastUtils.setGravity(Gravity.BOTTOM, 0, 200); | ||||
|          | ||||
|         //CrashHandler.getInstance().registerGlobal(this); | ||||
|         //CrashHandler.getInstance().registerPart(this); | ||||
|     } | ||||
|  | ||||
|     public static void write(InputStream input, OutputStream output) throws IOException { | ||||
|         byte[] buf = new byte[1024 * 8]; | ||||
|         int len; | ||||
|         while ((len = input.read(buf)) != -1) { | ||||
|             output.write(buf, 0, len); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void write(File file, byte[] data) throws IOException { | ||||
|         File parent = file.getParentFile(); | ||||
|         if (parent != null && !parent.exists()) parent.mkdirs(); | ||||
|  | ||||
|         ByteArrayInputStream input = new ByteArrayInputStream(data); | ||||
|         FileOutputStream output = new FileOutputStream(file); | ||||
|         try { | ||||
|             write(input, output); | ||||
|         } finally { | ||||
|             closeIO(input, output); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static String toString(InputStream input) throws IOException { | ||||
|         ByteArrayOutputStream output = new ByteArrayOutputStream(); | ||||
|         write(input, output); | ||||
|         try { | ||||
|             return output.toString("UTF-8"); | ||||
|         } finally { | ||||
|             closeIO(input, output); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void closeIO(Closeable... closeables) { | ||||
|         for (Closeable closeable : closeables) { | ||||
|             try { | ||||
|                 if (closeable != null) closeable.close(); | ||||
|             } catch (IOException ignored) {} | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static class CrashHandler { | ||||
|  | ||||
|         public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler(); | ||||
|  | ||||
|         private static CrashHandler sInstance; | ||||
|  | ||||
|         private PartCrashHandler mPartCrashHandler; | ||||
|  | ||||
|         public static CrashHandler getInstance() { | ||||
|             if (sInstance == null) { | ||||
|                 sInstance = new CrashHandler(); | ||||
|             } | ||||
|             return sInstance; | ||||
|         } | ||||
|  | ||||
|         public void registerGlobal(Context context) { | ||||
|             registerGlobal(context, null); | ||||
|         } | ||||
|  | ||||
|         public void registerGlobal(Context context, String crashDir) { | ||||
|             Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir)); | ||||
|         } | ||||
|  | ||||
|         public void unregister() { | ||||
|             Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER); | ||||
|         } | ||||
|  | ||||
|         public void registerPart(Context context) { | ||||
|             unregisterPart(context); | ||||
|             mPartCrashHandler = new PartCrashHandler(context.getApplicationContext()); | ||||
|             MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler); | ||||
|         } | ||||
|  | ||||
|         public void unregisterPart(Context context) { | ||||
|             if (mPartCrashHandler != null) { | ||||
|                 mPartCrashHandler.isRunning.set(false); | ||||
|                 mPartCrashHandler = null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private static class PartCrashHandler implements Runnable { | ||||
|  | ||||
|             private final Context mContext; | ||||
|  | ||||
|             public AtomicBoolean isRunning = new AtomicBoolean(true); | ||||
|  | ||||
|             public PartCrashHandler(Context context) { | ||||
|                 this.mContext = context; | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 while (isRunning.get()) { | ||||
|                     try { | ||||
|                         Looper.loop(); | ||||
|                     } catch (final Throwable e) { | ||||
|                         e.printStackTrace(); | ||||
|                         if (isRunning.get()) { | ||||
|                             MAIN_HANDLER.post(new Runnable(){ | ||||
|  | ||||
|                                     @Override | ||||
|                                     public void run() { | ||||
|                                         Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show(); | ||||
|                                     } | ||||
|                                 }); | ||||
|                         } else { | ||||
|                             if (e instanceof RuntimeException) { | ||||
|                                 throw (RuntimeException)e; | ||||
|                             } else { | ||||
|                                 throw new RuntimeException(e); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler { | ||||
|  | ||||
|             private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss"); | ||||
|  | ||||
|             private final Context mContext; | ||||
|  | ||||
|             private final File mCrashDir; | ||||
|  | ||||
|             public UncaughtExceptionHandlerImpl(Context context, String crashDir) { | ||||
|                 this.mContext = context; | ||||
|                 this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void uncaughtException(Thread thread, Throwable throwable) { | ||||
|                 try { | ||||
|  | ||||
|                     String log = buildLog(throwable); | ||||
|                     writeLog(log); | ||||
|  | ||||
|                     try { | ||||
|                         Intent intent = new Intent(mContext, CrashActivity.class); | ||||
|                         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|                         intent.putExtra(Intent.EXTRA_TEXT, log); | ||||
|                         mContext.startActivity(intent); | ||||
|                     } catch (Throwable e) { | ||||
|                         e.printStackTrace(); | ||||
|                         writeLog(e.toString()); | ||||
|                     } | ||||
|  | ||||
|                     throwable.printStackTrace(); | ||||
|                     android.os.Process.killProcess(android.os.Process.myPid()); | ||||
|                     System.exit(0); | ||||
|  | ||||
|                 } catch (Throwable e) { | ||||
|                     if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             private String buildLog(Throwable throwable) { | ||||
|                 String time = DATE_FORMAT.format(new Date()); | ||||
|  | ||||
|                 String versionName = "unknown"; | ||||
|                 long versionCode = 0; | ||||
|                 try { | ||||
|                     PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0); | ||||
|                     versionName = packageInfo.versionName; | ||||
|                     versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode; | ||||
|                 } catch (Throwable ignored) {} | ||||
|  | ||||
|                 LinkedHashMap<String, String> head = new LinkedHashMap<String, String>(); | ||||
|                 head.put("Time Of Crash", time); | ||||
|                 head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL)); | ||||
|                 head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT)); | ||||
|                 head.put("App Version", String.format("%s (%d)", versionName, versionCode)); | ||||
|                 head.put("Kernel", getKernel()); | ||||
|                 head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown"); | ||||
|                 head.put("Fingerprint", Build.FINGERPRINT); | ||||
|  | ||||
|                 StringBuilder builder = new StringBuilder(); | ||||
|  | ||||
|                 for (String key : head.keySet()) { | ||||
|                     if (builder.length() != 0) builder.append("\n"); | ||||
|                     builder.append(key); | ||||
|                     builder.append(" :    "); | ||||
|                     builder.append(head.get(key)); | ||||
|                 } | ||||
|  | ||||
|                 builder.append("\n\n"); | ||||
|                 builder.append(Log.getStackTraceString(throwable)); | ||||
|  | ||||
|                 return builder.toString();  | ||||
|             } | ||||
|  | ||||
|             private void writeLog(String log) { | ||||
|                 String time = DATE_FORMAT.format(new Date()); | ||||
|                 File file = new File(mCrashDir, "crash_" + time + ".txt"); | ||||
|                 try { | ||||
|                     write(file, log.getBytes("UTF-8")); | ||||
|                 } catch (Throwable e) { | ||||
|                     e.printStackTrace(); | ||||
|                 }  | ||||
|             } | ||||
|  | ||||
|             private static String getKernel() { | ||||
|                 try { | ||||
|                     return App.toString(new FileInputStream("/proc/version")).trim(); | ||||
|                 } catch (Throwable e) { | ||||
|                     return e.getMessage(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static final class CrashActivity extends Activity { | ||||
|  | ||||
|         private String mLog; | ||||
|  | ||||
|         @Override | ||||
|         protected void onCreate(Bundle savedInstanceState) { | ||||
|             super.onCreate(savedInstanceState); | ||||
|  | ||||
|             setTheme(android.R.style.Theme_DeviceDefault); | ||||
|             setTitle("App Crash"); | ||||
|  | ||||
|             mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT); | ||||
|  | ||||
|             ScrollView contentView = new ScrollView(this); | ||||
|             contentView.setFillViewport(true); | ||||
|  | ||||
|             HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this); | ||||
|  | ||||
|             TextView textView = new TextView(this); | ||||
|             int padding = dp2px(16); | ||||
|             textView.setPadding(padding, padding, padding, padding); | ||||
|             textView.setText(mLog); | ||||
|             textView.setTextIsSelectable(true); | ||||
|             textView.setTypeface(Typeface.DEFAULT); | ||||
|             textView.setLinksClickable(true); | ||||
|  | ||||
|             horizontalScrollView.addView(textView); | ||||
|             contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); | ||||
|  | ||||
|             setContentView(contentView); | ||||
|         } | ||||
|  | ||||
|         private void restart() { | ||||
|             Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName()); | ||||
|             if (intent != null) { | ||||
|                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|                 startActivity(intent); | ||||
|             } | ||||
|             finish(); | ||||
|             android.os.Process.killProcess(android.os.Process.myPid()); | ||||
|             System.exit(0); | ||||
|         } | ||||
|  | ||||
|         private static int dp2px(float dpValue) { | ||||
|             final float scale = Resources.getSystem().getDisplayMetrics().density; | ||||
|             return (int) (dpValue * scale + 0.5f); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public boolean onCreateOptionsMenu(Menu menu) { | ||||
|             menu.add(0, android.R.id.copy, 0, android.R.string.copy) | ||||
|                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); | ||||
|             return super.onCreateOptionsMenu(menu); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public boolean onOptionsItemSelected(MenuItem item) { | ||||
|             switch (item.getItemId()) { | ||||
|                 case android.R.id.copy: | ||||
|                     ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); | ||||
|                     cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog)); | ||||
|                     return true; | ||||
|             } | ||||
|             return super.onOptionsItemSelected(item); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onBackPressed() { | ||||
|             restart(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,31 @@ | ||||
| package cc.winboll.studio.numtable; | ||||
|  | ||||
| import android.os.Bundle; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import cc.winboll.studio.libappbase.LogView; | ||||
| import com.hjq.toast.ToastUtils; | ||||
|  | ||||
| public class MainActivity extends AppCompatActivity { | ||||
|  | ||||
|     LogView mLogView; | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_main); | ||||
|  | ||||
| 		Toolbar toolbar=(Toolbar)findViewById(R.id.toolbar); | ||||
| 		setSupportActionBar(toolbar); | ||||
|  | ||||
|         mLogView = findViewById(R.id.logview); | ||||
|          | ||||
|         ToastUtils.show("onCreate"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         mLogView.start(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,34 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:aapt="http://schemas.android.com/aapt" | ||||
|     android:width="108dp" | ||||
|     android:height="108dp" | ||||
|     android:viewportHeight="108" | ||||
|     android:viewportWidth="108"> | ||||
|     <path | ||||
|         android:fillType="evenOdd" | ||||
|         android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" | ||||
|         android:strokeColor="#00000000" | ||||
|         android:strokeWidth="1"> | ||||
|         <aapt:attr name="android:fillColor"> | ||||
|             <gradient | ||||
|                 android:endX="78.5885" | ||||
|                 android:endY="90.9159" | ||||
|                 android:startX="48.7653" | ||||
|                 android:startY="61.0927" | ||||
|                 android:type="linear"> | ||||
|                 <item | ||||
|                     android:color="#44000000" | ||||
|                     android:offset="0.0" /> | ||||
|                 <item | ||||
|                     android:color="#00000000" | ||||
|                     android:offset="1.0" /> | ||||
|             </gradient> | ||||
|         </aapt:attr> | ||||
|     </path> | ||||
|     <path | ||||
|         android:fillColor="#FFFFFF" | ||||
|         android:fillType="nonZero" | ||||
|         android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" | ||||
|         android:strokeColor="#00000000" | ||||
|         android:strokeWidth="1" /> | ||||
| </vector> | ||||
							
								
								
									
										13
									
								
								numtable/src/main/res/drawable/ic_launcher.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="512dp" | ||||
|     android:height="512dp" | ||||
|     android:viewportWidth="512" | ||||
|     android:viewportHeight="512"> | ||||
|     <path | ||||
|         android:fillColor="#FF3D8A1C" | ||||
|         android:strokeColor="#FFF5DD00" | ||||
|         android:strokeWidth="20.0" | ||||
|         android:strokeLineCap="round" | ||||
|         android:strokeMiterLimit="10" | ||||
|         android:pathData="M47.53 417.75C38.23 258.17 97.09 94.95 156.27 94.16 211.66 98.38 260.9 237.99 329.08 242.47 351.54 241.69 397.94 228.17 449.46 88.59 447.52 262.11 394.97 417.45 338.71 418.74 279.06 412.93 220.17 297.22 157.81 295.26 132.98 294.7 116.37 308.69 47.53 417.75Z"/> | ||||
| </vector> | ||||
							
								
								
									
										170
									
								
								numtable/src/main/res/drawable/ic_launcher_background.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,170 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="108dp" | ||||
|     android:height="108dp" | ||||
|     android:viewportHeight="108" | ||||
|     android:viewportWidth="108"> | ||||
|     <path | ||||
|         android:fillColor="#FFFBC41E" | ||||
|         android:pathData="M0,0h108v108h-108z" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M9,0L9,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,0L19,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M29,0L29,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M39,0L39,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M49,0L49,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M59,0L59,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M69,0L69,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M79,0L79,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M89,0L89,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M99,0L99,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,9L108,9" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,19L108,19" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,29L108,29" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,39L108,39" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,49L108,49" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,59L108,59" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,69L108,69" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,79L108,79" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,89L108,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,99L108,99" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,29L89,29" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,39L89,39" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,49L89,49" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,59L89,59" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,69L89,69" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,79L89,79" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M29,19L29,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M39,19L39,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M49,19L49,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M59,19L59,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M69,19L69,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M79,19L79,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
| </vector> | ||||
							
								
								
									
										11
									
								
								numtable/src/main/res/drawable/ic_launcher_beta.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | ||||
| <?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"/> | ||||
| </layer-list> | ||||
							
								
								
									
										51
									
								
								numtable/src/main/res/layout/activity_main.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,51 @@ | ||||
| <?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:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent" | ||||
| 	android:orientation="vertical"> | ||||
|  | ||||
| 	<com.google.android.material.appbar.AppBarLayout | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> | ||||
|  | ||||
| 		<androidx.appcompat.widget.Toolbar | ||||
| 			android:id="@+id/toolbar" | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="?attr/actionBarSize" | ||||
| 			app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> | ||||
|  | ||||
| 	</com.google.android.material.appbar.AppBarLayout> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="vertical" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:layout_weight="1.0" | ||||
| 		android:gravity="center_vertical|center_horizontal"> | ||||
|  | ||||
| 		<TextView | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="NumTable" | ||||
| 			android:textAppearance="?android:attr/textAppearanceLarge"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="vertical" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:layout_weight="1.0"> | ||||
|  | ||||
| 		<cc.winboll.studio.libappbase.LogView | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="match_parent" | ||||
| 			android:id="@+id/logview"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										5
									
								
								numtable/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <background android:drawable="@drawable/ic_launcher_background" /> | ||||
|     <foreground android:drawable="@drawable/ic_launcher_foreground" /> | ||||
| </adaptive-icon> | ||||
| @@ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <background android:drawable="@drawable/ic_launcher_background" /> | ||||
|     <foreground android:drawable="@drawable/ic_launcher_foreground" /> | ||||
| </adaptive-icon> | ||||
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-hdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-hdpi/ic_launcher_round.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-mdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-mdpi/ic_launcher_round.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xhdpi/ic_launcher_round.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 10 KiB | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xxxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										6
									
								
								numtable/src/main/res/values/colors.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <color name="colorPrimary">#009688</color> | ||||
|     <color name="colorPrimaryDark">#00796B</color> | ||||
|     <color name="colorAccent">#FF9800</color> | ||||
| </resources> | ||||
							
								
								
									
										4
									
								
								numtable/src/main/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| <resources> | ||||
|     <string name="app_name">NumTable</string> | ||||
|      | ||||
| </resources> | ||||
							
								
								
									
										11
									
								
								numtable/src/main/res/values/styles.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | ||||
| <resources> | ||||
|  | ||||
|     <!-- Base application theme. --> | ||||
|     <style name="MyAppTheme" parent="Theme.AppCompat.Light.NoActionBar"> | ||||
|         <!-- Customize your theme here. --> | ||||
|         <item name="colorPrimary">@color/colorPrimary</item> | ||||
|         <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | ||||
|         <item name="colorAccent">@color/colorAccent</item> | ||||
|     </style> | ||||
|  | ||||
| </resources> | ||||
							
								
								
									
										12
									
								
								numtable/src/stage/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | ||||
| <?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> | ||||
|  | ||||
|         <!-- Put flavor specific code here --> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
|  | ||||