From ff4170410169e200512198ba94de2127acde7c8a Mon Sep 17 00:00:00 2001 From: ZaneYork Date: Sat, 14 Mar 2020 16:52:52 +0800 Subject: [PATCH] Add comments, minor refactor --- .../smapiinstaller/constant/Constants.java | 25 +++- .../entity/ApkFilesManifest.java | 15 +++ .../entity/DownloadableContent.java | 21 ++++ .../entity/DownloadableContentList.java | 6 + .../zane/smapiinstaller/entity/HelpItem.java | 12 ++ .../smapiinstaller/entity/HelpItemList.java | 6 + .../smapiinstaller/entity/ManifestEntry.java | 18 +++ .../entity/ModManifestEntry.java | 30 +++++ .../smapiinstaller/entity/UpdatableList.java | 6 + .../zane/smapiinstaller/logic/ApkPatcher.java | 28 +++++ .../smapiinstaller/logic/CommonLogic.java | 99 +++++++--------- .../smapiinstaller/logic/GameLauncher.java | 13 ++- .../logic/ManifestTagVisitor.java | 3 + .../logic/ModAssetsManager.java | 44 ++++++- .../logic/UpdatableListManager.java | 17 +++ .../ui/config/ConfigEditFragment.java | 4 +- .../ui/config/ModManifestAdapter.java | 6 +- .../download/DownloadableContentAdapter.java | 16 +-- .../ui/install/InstallFragment.java | 29 ++--- .../smapiinstaller/utils/DialogUtils.java | 110 ++++++++++++++++++ .../zane/smapiinstaller/utils/FileUtils.java | 73 ++++++++++++ .../zane/smapiinstaller/utils/JSONUtil.java | 33 ++++++ .../smapiinstaller/utils/VersionUtil.java | 23 +++- 23 files changed, 540 insertions(+), 97 deletions(-) create mode 100644 app/src/main/java/com/zane/smapiinstaller/utils/DialogUtils.java diff --git a/app/src/main/java/com/zane/smapiinstaller/constant/Constants.java b/app/src/main/java/com/zane/smapiinstaller/constant/Constants.java index b5f8932..31bdc0a 100644 --- a/app/src/main/java/com/zane/smapiinstaller/constant/Constants.java +++ b/app/src/main/java/com/zane/smapiinstaller/constant/Constants.java @@ -1,14 +1,31 @@ package com.zane.smapiinstaller.constant; +/** + * 常量 + */ public class Constants { + /** + * Mod安装路径 + */ public static final String MOD_PATH = "StardewValley/Mods"; + /** + * 日志路径 + */ public static final String LOG_PATH = "StardewValley/ErrorLogs/SMAPI-latest.txt"; - + /** + * 安装包目标包名 + */ public static final String TARGET_PACKAGE_NAME = "com.zane.stardewvalley"; - + /** + * DLC下载路径 + */ public static final String DLC_LIST_UPDATE_URL = "http://dl.zaneyork.cn/smapi/downloadable_content_list.json"; - + /** + * 帮助内容下载路径 + */ public static final String HELP_LIST_UPDATE_URL = "http://dl.zaneyork.cn/smapi/help_item_list.json"; - + /** + * AppCenter秘钥 + */ public static final String APP_CENTER_SECRET = "cb44e94a-7b2f-431e-9ad9-48013ec8c208"; } diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/ApkFilesManifest.java b/app/src/main/java/com/zane/smapiinstaller/entity/ApkFilesManifest.java index 4e69175..ad28c76 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/ApkFilesManifest.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/ApkFilesManifest.java @@ -4,10 +4,25 @@ import java.util.List; import lombok.Data; +/** + * SMAPI所需处理的文件清单 + */ @Data public class ApkFilesManifest { + /** + * 最小兼容版本,包含 + */ private long minBuildCode; + /** + * 最大兼容版本,包含 + */ private Long maxBuildCode; + /** + * 兼容包基础文件路径 + */ private String basePath; + /** + * 文件清单 + */ private List manifestEntries; } diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContent.java b/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContent.java index 49adcea..2605b9e 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContent.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContent.java @@ -2,12 +2,33 @@ package com.zane.smapiinstaller.entity; import lombok.Data; +/** + * 可下载内容包 + */ @Data public class DownloadableContent { + /** + * 类型,COMPAT:兼容包/LOCALE:语言包 + */ private String type; + /** + * 名称 + */ private String name; + /** + * 资源存放位置 + */ private String assetPath; + /** + * 下载位置 + */ private String url; + /** + * 描述 + */ private String description; + /** + * 文件SHA3-256校验码 + */ private String hash; } diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContentList.java b/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContentList.java index 743e0ee..5a5edec 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContentList.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContentList.java @@ -5,8 +5,14 @@ import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; +/** + * 可下载内容列表 + */ @Data @EqualsAndHashCode(callSuper = true) public class DownloadableContentList extends UpdatableList { + /** + * 列表 + */ List contents; } diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/HelpItem.java b/app/src/main/java/com/zane/smapiinstaller/entity/HelpItem.java index 0bd4d44..2e34114 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/HelpItem.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/HelpItem.java @@ -2,9 +2,21 @@ package com.zane.smapiinstaller.entity; import lombok.Data; +/** + * 帮助信息 + */ @Data public class HelpItem { + /** + * 标题 + */ private String title; + /** + * 内容 + */ private String content; + /** + * 作者 + */ private String author; } diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/HelpItemList.java b/app/src/main/java/com/zane/smapiinstaller/entity/HelpItemList.java index fd049e6..6bd64be 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/HelpItemList.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/HelpItemList.java @@ -5,8 +5,14 @@ import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; +/** + * 帮助内容列表 + */ @Data @EqualsAndHashCode(callSuper = true) public class HelpItemList extends UpdatableList { + /** + * 列表 + */ private List items; } diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java b/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java index 49b63b5..d2493ec 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java @@ -2,11 +2,29 @@ package com.zane.smapiinstaller.entity; import lombok.Data; +/** + * 文件映射关系 + */ @Data public class ManifestEntry { + /** + * 目标路径 + */ private String targetPath; + /** + * 资源路径 + */ private String assetPath; + /** + * 压缩级别 + */ private int compression; + /** + * 来源位置,0:安装器自带/1:从APK中抽取 + */ private int origin; + /** + * 文件是否不属于兼容包中 + */ private boolean external; } diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/ModManifestEntry.java b/app/src/main/java/com/zane/smapiinstaller/entity/ModManifestEntry.java index 32e55b6..6dfecbb 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/ModManifestEntry.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/ModManifestEntry.java @@ -4,16 +4,46 @@ import java.util.Set; import lombok.Data; +/** + * Mod信息 + */ @Data public class ModManifestEntry { + /** + * 存放位置 + */ private String assetPath; + /** + * 名称 + */ private String Name; + /** + * 唯一ID + */ private String UniqueID; + /** + * 版本 + */ private String Version; + /** + * 描述 + */ private String Description; + /** + * 依赖 + */ private Set Dependencies; + /** + * 资源包类型 + */ private ModManifestEntry ContentPackFor; + /** + * 最小依赖版本 + */ private String MinimumVersion; + /** + * 是否必须依赖 + */ private Boolean IsRequired; } diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/UpdatableList.java b/app/src/main/java/com/zane/smapiinstaller/entity/UpdatableList.java index b9fb782..9c8b31e 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/UpdatableList.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/UpdatableList.java @@ -2,7 +2,13 @@ package com.zane.smapiinstaller.entity; import lombok.Data; +/** + * 可更新列表 + */ @Data public class UpdatableList { + /** + * 列表版本 + */ private int version; } diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java b/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java index 055c7fb..7703efc 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java @@ -57,6 +57,10 @@ public class ApkPatcher { this.context = context; } + /** + * 依次扫描package_names.json文件对应的包名,抽取找到的第一个游戏APK到SMAPI Installer路径 + * @return 抽取后的APK文件路径,如果抽取失败返回null + */ public String extract() { PackageManager packageManager = context.getPackageManager(); List packageNames = FileUtils.getAssetJson(context, "package_names.json", new TypeReference>() { }); @@ -91,6 +95,11 @@ public class ApkPatcher { return null; } + /** + * 将指定APK文件重新打包,添加SMAPI,修改AndroidManifest.xml,同时验证版本是否正确 + * @param apkPath APK文件路径 + * @return 是否成功打包 + */ public boolean patch(String apkPath) { if (apkPath == null) return false; @@ -130,6 +139,12 @@ public class ApkPatcher { return false; } + /** + * 扫描全部兼容包,寻找匹配的版本,修改AndroidManifest.xml文件 + * @param bytes AndroidManifest.xml的字节数组 + * @param manifests 兼容包列表 + * @return 修改后的AndroidManifest.xml的字节数组 + */ private byte[] modifyManifest(byte[] bytes, List manifests) { AtomicReference packageName = new AtomicReference<>(); Predicate processLogic = (attr) -> { @@ -184,6 +199,11 @@ public class ApkPatcher { } } + /** + * 重新签名安装包 + * @param apkPath APK文件路径 + * @return 签名后的安装包路径 + */ public String sign(String apkPath) { try { File externalFilesDir = Environment.getExternalStorageDirectory(); @@ -207,6 +227,10 @@ public class ApkPatcher { return null; } + /** + * 对指定安装包发起安装 + * @param apkPath 安装包路径 + */ public void install(String apkPath) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(fromFile(new File(apkPath)), "application/vnd.android.package-archive"); @@ -234,6 +258,10 @@ public class ApkPatcher { return Uri.fromFile(file); } + /** + * 获取报错内容 + * @return 报错内容 + */ public AtomicReference getErrorMessage() { return errorMessage; } diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java b/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java index d4f89a6..c64a959 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java @@ -37,18 +37,15 @@ import pxb.android.axml.AxmlVisitor; import pxb.android.axml.AxmlWriter; import pxb.android.axml.NodeVisitor; +/** + * 通用逻辑 + */ public class CommonLogic { - - public static void setProgressDialogState(View view, MaterialDialog dialog, int message, int progress) { - Activity activity = getActivityFromView(view); - if (activity != null && !activity.isFinishing() && !dialog.isCancelled()) { - activity.runOnUiThread(() -> { - dialog.incrementProgress(progress - dialog.getCurrentProgress()); - dialog.setContent(message); - }); - } - } - + /** + * 从View获取所属Activity + * @param view context容器 + * @return Activity + */ public static Activity getActivityFromView(View view) { if (null != view) { Context context = view.getContext(); @@ -62,51 +59,12 @@ public class CommonLogic { return null; } - public static void showAlertDialog(View view, int title, String message) { - Activity activity = getActivityFromView(view); - if (activity != null && !activity.isFinishing()) { - activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.ok).show()); - } - } - - public static void showAlertDialog(View view, int title, int message) { - Activity activity = getActivityFromView(view); - if (activity != null && !activity.isFinishing()) { - activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.ok).show()); - } - } - - public static void showConfirmDialog(View view, int title, int message, MaterialDialog.SingleButtonCallback callback) { - Activity activity = getActivityFromView(view); - if (activity != null && !activity.isFinishing()) { - activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.confirm).negativeText(R.string.cancel).onAny(callback).show()); - } - } - - public static void showConfirmDialog(View view, int title, String message, MaterialDialog.SingleButtonCallback callback) { - Activity activity = getActivityFromView(view); - if (activity != null && !activity.isFinishing()) { - activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.confirm).negativeText(R.string.cancel).onAny(callback).show()); - } - } - - public static AtomicReference showProgressDialog(View view, int title, String message) { - Activity activity = getActivityFromView(view); - AtomicReference reference = new AtomicReference<>(); - if (activity != null && !activity.isFinishing()) { - activity.runOnUiThread(() -> { - MaterialDialog dialog = new MaterialDialog.Builder(activity) - .title(title) - .content(message) - .progress(false, 100, true) - .cancelable(false) - .show(); - reference.set(dialog); - }); - } - return reference; - } + /** + * 打开指定URL + * @param context context + * @param url 目标URL + */ public static void openUrl(Context context, String url) { try { Intent intent = new Intent(); @@ -118,6 +76,12 @@ public class CommonLogic { } } + /** + * 复制文本到剪贴板 + * @param context 上下文 + * @param copyStr 文本 + * @return 是否复制成功 + */ public static boolean copyToClipboard(Context context, String copyStr) { try { ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); @@ -129,6 +93,11 @@ public class CommonLogic { } } + /** + * 扫描全部兼容包 + * @param context context + * @return 兼容包列表 + */ public static List findAllApkFileManifest(Context context) { ApkFilesManifest apkFilesManifest = com.zane.smapiinstaller.utils.FileUtils.getAssetJson(context, "apk_files_manifest.json", ApkFilesManifest.class); ArrayList apkFilesManifests = Lists.newArrayList(apkFilesManifest); @@ -148,7 +117,14 @@ public class CommonLogic { return apkFilesManifests; } - public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMod) { + /** + * 提取SMAPI环境文件到内部存储对应位置 + * @param context context + * @param apkPath 安装包路径 + * @param checkMode 是否为校验模式 + * @return 操作是否成功 + */ + public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMode) { List manifestEntries = com.zane.smapiinstaller.utils.FileUtils.getAssetJson(context, "smapi_files_manifest.json", new TypeReference>() { }); if (manifestEntries == null) return false; @@ -169,7 +145,7 @@ public class CommonLogic { File targetFile = new File(basePath, entry.getTargetPath()); switch (entry.getOrigin()) { case 0: - if (!checkMod || !targetFile.exists()) { + if (!checkMode || !targetFile.exists()) { try (InputStream inputStream = context.getAssets().open(entry.getAssetPath())) { if (!targetFile.getParentFile().exists()) { if (!targetFile.getParentFile().mkdirs()) { @@ -185,7 +161,7 @@ public class CommonLogic { } break; case 1: - if (!checkMod || !targetFile.exists()) { + if (!checkMode || !targetFile.exists()) { ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath(), targetFile); } break; @@ -194,6 +170,13 @@ public class CommonLogic { return true; } + /** + * 修改AndroidManifest.xml文件 + * @param bytes AndroidManifest.xml文件字符数组 + * @param processLogic 处理逻辑 + * @return 修改后的AndroidManifest.xml文件字符数组 + * @throws IOException 异常 + */ public static byte[] modifyManifest(byte[] bytes, Predicate processLogic) throws IOException { AxmlReader reader = new AxmlReader(bytes); AxmlWriter writer = new AxmlWriter(); diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/GameLauncher.java b/app/src/main/java/com/zane/smapiinstaller/logic/GameLauncher.java index 42a6801..4b0ef11 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/GameLauncher.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/GameLauncher.java @@ -9,7 +9,11 @@ import android.view.View; import com.microsoft.appcenter.crashes.Crashes; import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.constant.Constants; +import com.zane.smapiinstaller.utils.DialogUtils; +/** + * 游戏启动器 + */ public class GameLauncher { private final View root; @@ -18,13 +22,16 @@ public class GameLauncher { this.root = root; } + /** + * 启动逻辑 + */ public void launch() { Activity context = CommonLogic.getActivityFromView(root); PackageManager packageManager = context.getPackageManager(); try { PackageInfo packageInfo = packageManager.getPackageInfo(Constants.TARGET_PACKAGE_NAME, 0); if(!CommonLogic.unpackSmapiFiles(context, packageInfo.applicationInfo.publicSourceDir, true)) { - CommonLogic.showAlertDialog(root, R.string.error, R.string.error_failed_to_repair); + DialogUtils.showAlertDialog(root, R.string.error, R.string.error_failed_to_repair); return; } ModAssetsManager modAssetsManager = new ModAssetsManager(root); @@ -35,10 +42,10 @@ public class GameLauncher { } }); } catch (PackageManager.NameNotFoundException ignored) { - CommonLogic.showAlertDialog(root, R.string.error, R.string.error_smapi_not_installed); + DialogUtils.showAlertDialog(root, R.string.error, R.string.error_smapi_not_installed); } catch (Exception e) { Crashes.trackError(e); - CommonLogic.showAlertDialog(root, R.string.error, e.getLocalizedMessage()); + DialogUtils.showAlertDialog(root, R.string.error, e.getLocalizedMessage()); } } } diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/ManifestTagVisitor.java b/app/src/main/java/com/zane/smapiinstaller/logic/ManifestTagVisitor.java index b3782e4..eca3cd2 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/ManifestTagVisitor.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/ManifestTagVisitor.java @@ -4,6 +4,9 @@ import com.google.common.base.Predicate; import pxb.android.axml.NodeVisitor; +/** + * AndroidManifest文件节点访问器 + */ class ManifestTagVisitor extends NodeVisitor { private final Predicate attrProcessLogic; diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/ModAssetsManager.java b/app/src/main/java/com/zane/smapiinstaller/logic/ModAssetsManager.java index b4dd4cf..ced4fd7 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/ModAssetsManager.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/ModAssetsManager.java @@ -18,6 +18,7 @@ import com.google.common.collect.Queues; import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.constant.Constants; import com.zane.smapiinstaller.entity.ModManifestEntry; +import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.FileUtils; import com.zane.smapiinstaller.utils.VersionUtil; @@ -32,6 +33,9 @@ import java.util.concurrent.ConcurrentLinkedQueue; import androidx.core.util.Consumer; +/** + * Mod资源管理器 + */ public class ModAssetsManager { private final View root; @@ -42,6 +46,11 @@ public class ModAssetsManager { this.root = root; } + /** + * 查找第一个匹配的Mod + * @param filter 过滤规则 + * @return Mod信息 + */ public static ModManifestEntry findFirstModIf(Predicate filter) { ConcurrentLinkedQueue files = Queues.newConcurrentLinkedQueue(); files.add(new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH)); @@ -70,6 +79,10 @@ public class ModAssetsManager { return null; } + /** + * 查找全部已识别Mod + * @return Mod信息列表 + */ public static List findAllInstalledMods() { ConcurrentLinkedQueue files = Queues.newConcurrentLinkedQueue(); files.add(new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH)); @@ -103,6 +116,10 @@ public class ModAssetsManager { return mods; } + /** + * 安装默认Mod + * @return 是否安装成功 + */ public boolean installDefaultMods() { Activity context = CommonLogic.getActivityFromView(root); List modManifestEntries = FileUtils.getAssetJson(context, "mods_manifest.json", new TypeReference>() { }); @@ -114,7 +131,7 @@ public class ModAssetsManager { if (installedModMap.containsKey(mod.getUniqueID()) || installedModMap.containsKey(mod.getUniqueID().replace("ZaneYork.CustomLocalization", "SMAPI.CustomLocalization"))) { ImmutableList installedMods = installedModMap.get(mod.getUniqueID()); if (installedMods.size() > 1) { - CommonLogic.showAlertDialog(root, R.string.error, + DialogUtils.showAlertDialog(root, R.string.error, String.format(context.getString(R.string.duplicate_mod_found), Joiner.on(",").join(Lists.transform(installedMods, item -> FileUtils.toPrettyPath(item.getAssetPath()))))); return false; @@ -139,6 +156,10 @@ public class ModAssetsManager { return true; } + /** + * 检查Mod环境 + * @param returnCallback 回调函数 + */ public void checkModEnvironment(Consumer returnCallback) { ImmutableListMultimap installedModMap = Multimaps.index(findAllInstalledMods(), ModManifestEntry::getUniqueID); checkDuplicateMod(installedModMap, (isConfirm) -> { @@ -154,6 +175,11 @@ public class ModAssetsManager { }); } + /** + * 检查是否有重复Mod + * @param installedModMap 已安装Mod集合 + * @param returnCallback 回调函数 + */ private void checkDuplicateMod(ImmutableListMultimap installedModMap, Consumer returnCallback) { // Duplicate mod check ArrayList list = Lists.newArrayList(); @@ -164,7 +190,7 @@ public class ModAssetsManager { } } if (list.size() > 0) { - CommonLogic.showConfirmDialog(root, R.string.error, + DialogUtils.showConfirmDialog(root, R.string.error, root.getContext().getString(R.string.duplicate_mod_found, Joiner.on(";").join(list)), ((dialog, which) -> { if (which == DialogAction.POSITIVE) { @@ -178,6 +204,11 @@ public class ModAssetsManager { returnCallback.accept(true); } + /** + * 检查是否有依赖关系缺失 + * @param installedModMap 已安装Mod集合 + * @param returnCallback 回调函数 + */ private void checkUnsatisfiedDependencies(ImmutableListMultimap installedModMap, Consumer returnCallback) { Iterable dependencyErrors = Iterables.filter(Iterables.transform(installedModMap.values(), mod -> { if (mod.getDependencies() != null) { @@ -216,7 +247,7 @@ public class ModAssetsManager { return null; }), item -> item != null); if (dependencyErrors.iterator().hasNext()) { - CommonLogic.showConfirmDialog(root, R.string.error, + DialogUtils.showConfirmDialog(root, R.string.error, Joiner.on(";").join(dependencyErrors), ((dialog, which) -> { if (which == DialogAction.POSITIVE) { @@ -230,6 +261,11 @@ public class ModAssetsManager { returnCallback.accept(true); } + /** + * 检查是否有资源包依赖Mod没有安装 + * @param installedModMap 已安装Mod集合 + * @param returnCallback 回调函数 + */ private void checkContentpacks(ImmutableListMultimap installedModMap, Consumer returnCallback) { Iterable dependencyErrors = Iterables.filter(Iterables.transform(installedModMap.values(), mod -> { ModManifestEntry dependency = mod.getContentPackFor(); @@ -263,7 +299,7 @@ public class ModAssetsManager { return null; }), item -> item != null); if (dependencyErrors.iterator().hasNext()) { - CommonLogic.showConfirmDialog(root, R.string.error, + DialogUtils.showConfirmDialog(root, R.string.error, Joiner.on(";").join(dependencyErrors), ((dialog, which) -> { if (which == DialogAction.POSITIVE) { diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/UpdatableListManager.java b/app/src/main/java/com/zane/smapiinstaller/logic/UpdatableListManager.java index fd58259..d378159 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/UpdatableListManager.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/UpdatableListManager.java @@ -13,6 +13,10 @@ import com.zane.smapiinstaller.utils.JSONUtil; import java.util.ArrayList; import java.util.List; +/** + * 在线列表更新管理器 + * @param 列表类型 + */ public class UpdatableListManager { private static boolean updateChecked = false; @@ -20,6 +24,12 @@ public class UpdatableListManager { private List> onChangedListener = new ArrayList<>(); + /** + * @param root context容器 + * @param filename 本地文件名 + * @param tClass 目标类型 + * @param updateUrl 更新地址 + */ public UpdatableListManager(View root, String filename, Class tClass, String updateUrl) { updatableList = FileUtils.getAssetJson(root.getContext(), filename, tClass); if(!updateChecked) { @@ -40,10 +50,17 @@ public class UpdatableListManager { } } + /** + * @return 列表 + */ public T getList() { return (T) updatableList; } + /** + * 注册列表变化监听器 + * @param onChanged 回调 + */ public void registerListChangeListener(Predicate onChanged) { this.onChangedListener.add(onChanged); } diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigEditFragment.java b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigEditFragment.java index c15f467..fa12289 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigEditFragment.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigEditFragment.java @@ -8,7 +8,7 @@ import android.widget.Button; import android.widget.EditText; import com.zane.smapiinstaller.R; -import com.zane.smapiinstaller.logic.CommonLogic; +import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.FileUtils; import com.zane.smapiinstaller.utils.JSONUtil; @@ -62,7 +62,7 @@ public class ConfigEditFragment extends Fragment { } } catch (Exception e) { - CommonLogic.showAlertDialog(getView(), R.string.error, e.getLocalizedMessage()); + DialogUtils.showAlertDialog(getView(), R.string.error, e.getLocalizedMessage()); } } diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/config/ModManifestAdapter.java b/app/src/main/java/com/zane/smapiinstaller/ui/config/ModManifestAdapter.java index efab01f..d2ba42f 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/config/ModManifestAdapter.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/config/ModManifestAdapter.java @@ -9,7 +9,7 @@ import android.widget.TextView; import com.afollestad.materialdialogs.DialogAction; import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.entity.ModManifestEntry; -import com.zane.smapiinstaller.logic.CommonLogic; +import com.zane.smapiinstaller.utils.DialogUtils; import org.apache.commons.lang3.StringUtils; import org.zeroturnaround.zip.commons.FileUtils; @@ -76,7 +76,7 @@ public class ModManifestAdapter extends RecyclerView.Adapter{ + DialogUtils.showConfirmDialog(itemView, R.string.confirm, R.string.confirm_delete_content, (dialog, which)->{ if (which == DialogAction.POSITIVE) { File file = new File(modPath); if (file.exists()) { @@ -87,7 +87,7 @@ public class ModManifestAdapter extends RecyclerView.Adapter { + DialogUtils.showConfirmDialog(itemView, R.string.confirm, R.string.confirm_delete_content, (dialog, which) -> { if (which == DialogAction.POSITIVE) { try { FileUtils.forceDelete(contentFile); } catch (IOException e) { - CommonLogic.showAlertDialog(itemView, R.string.error, e.getLocalizedMessage()); + DialogUtils.showAlertDialog(itemView, R.string.error, e.getLocalizedMessage()); } } }); @@ -129,7 +129,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter StringUtils.equals(mod.getUniqueID(), "ZaneYork.CustomLocalization") || StringUtils.equals(mod.getUniqueID(), "SMAPI.CustomLocalization")); if (modManifestEntry == null) { - CommonLogic.showAlertDialog(itemView, R.string.error, String.format(context.getString(R.string.error_depends_on_mod), context.getString(R.string.locale_pack), "ZaneYork.CustomLocalization")); + DialogUtils.showAlertDialog(itemView, R.string.error, String.format(context.getString(R.string.error_depends_on_mod), context.getString(R.string.locale_pack), "ZaneYork.CustomLocalization")); return; } } @@ -146,7 +146,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter dialogRef = CommonLogic.showProgressDialog(itemView, R.string.progress, ""); + AtomicReference dialogRef = DialogUtils.showProgressDialog(itemView, R.string.progress, ""); OkGo.get(downloadableContent.getUrl()).execute(new FileCallback(file.getParentFile().getAbsolutePath(), file.getName()) { @Override public void onError(Response response) { @@ -156,7 +156,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter { + DialogUtils.showConfirmDialog(root, R.string.confirm, R.string.android_version_confirm, ((dialog, which) -> { if (which == DialogAction.POSITIVE) { installLogic(); } @@ -53,6 +53,9 @@ public class InstallFragment extends Fragment { } } + /** + * 安装逻辑 + */ private void installLogic() { new MaterialDialog.Builder(context).title(R.string.install_progress_title).content(R.string.extracting_package).contentGravity(GravityEnum.CENTER) .progress(false, 100, true).cancelable(false).cancelListener(dialog -> { @@ -67,39 +70,39 @@ public class InstallFragment extends Fragment { task = new Thread(() -> { try { ApkPatcher patcher = new ApkPatcher(context); - CommonLogic.setProgressDialogState(root, dialog, R.string.extracting_package, 0); + DialogUtils.setProgressDialogState(root, dialog, R.string.extracting_package, 0); String path = patcher.extract(); if (path == null) { - CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found))); + DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found))); return; } - CommonLogic.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 10); + DialogUtils.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 10); if (!CommonLogic.unpackSmapiFiles(context, path, false)) { - CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files))); + DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files))); return; } ModAssetsManager modAssetsManager = new ModAssetsManager(root); - CommonLogic.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 15); + DialogUtils.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 15); modAssetsManager.installDefaultMods(); - CommonLogic.setProgressDialogState(root, dialog, R.string.patching_package, 25); + DialogUtils.setProgressDialogState(root, dialog, R.string.patching_package, 25); if (!patcher.patch(path)) { - CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_patch_game))); + DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_patch_game))); return; } - CommonLogic.setProgressDialogState(root, dialog, R.string.signing_package, 55); + DialogUtils.setProgressDialogState(root, dialog, R.string.signing_package, 55); String signPath = patcher.sign(path); if (signPath == null) { - CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game))); + DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game))); return; } - CommonLogic.setProgressDialogState(root, dialog, R.string.installing_package, 99); + DialogUtils.setProgressDialogState(root, dialog, R.string.installing_package, 99); patcher.install(signPath); dialog.incrementProgress(1); } catch (Exception e) { Crashes.trackError(e); - CommonLogic.showAlertDialog(root, R.string.error, e.getLocalizedMessage()); + DialogUtils.showAlertDialog(root, R.string.error, e.getLocalizedMessage()); } finally { if (!dialog.isCancelled()) { diff --git a/app/src/main/java/com/zane/smapiinstaller/utils/DialogUtils.java b/app/src/main/java/com/zane/smapiinstaller/utils/DialogUtils.java new file mode 100644 index 0000000..f7e527a --- /dev/null +++ b/app/src/main/java/com/zane/smapiinstaller/utils/DialogUtils.java @@ -0,0 +1,110 @@ +package com.zane.smapiinstaller.utils; + +import android.app.Activity; +import android.view.View; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.zane.smapiinstaller.R; +import com.zane.smapiinstaller.logic.CommonLogic; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * 对话框相关工具类 + */ +public class DialogUtils { + /** + * 设置进度条状态 + * @param view context容器 + * @param dialog 对话框 + * @param message 消息 + * @param progress 进度 + */ + public static void setProgressDialogState(View view, MaterialDialog dialog, int message, int progress) { + Activity activity = CommonLogic.getActivityFromView(view); + if (activity != null && !activity.isFinishing() && !dialog.isCancelled()) { + activity.runOnUiThread(() -> { + dialog.setProgress(progress); + dialog.setContent(message); + }); + } + } + + /** + * 显示警告对话框 + * @param view context容器 + * @param title 标题 + * @param message 消息 + */ + public static void showAlertDialog(View view, int title, String message) { + Activity activity = CommonLogic.getActivityFromView(view); + if (activity != null && !activity.isFinishing()) { + activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.ok).show()); + } + } + + /** + * 显示警告对话框 + * @param view context容器 + * @param title 标题 + * @param message 消息 + */ + public static void showAlertDialog(View view, int title, int message) { + Activity activity = CommonLogic.getActivityFromView(view); + if (activity != null && !activity.isFinishing()) { + activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.ok).show()); + } + } + + /** + * 显示确认对话框 + * @param view context容器 + * @param title 标题 + * @param message 消息 + * @param callback 回调 + */ + public static void showConfirmDialog(View view, int title, int message, MaterialDialog.SingleButtonCallback callback) { + Activity activity = CommonLogic.getActivityFromView(view); + if (activity != null && !activity.isFinishing()) { + activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.confirm).negativeText(R.string.cancel).onAny(callback).show()); + } + } + + /** + * 显示确认对话框 + * @param view context容器 + * @param title 标题 + * @param message 消息 + * @param callback 回调 + */ + public static void showConfirmDialog(View view, int title, String message, MaterialDialog.SingleButtonCallback callback) { + Activity activity = CommonLogic.getActivityFromView(view); + if (activity != null && !activity.isFinishing()) { + activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.confirm).negativeText(R.string.cancel).onAny(callback).show()); + } + } + + /** + * 显示进度条 + * @param view context容器 + * @param title 标题 + * @param message 消息 + * @return 对话框引用 + */ + public static AtomicReference showProgressDialog(View view, int title, String message) { + Activity activity = CommonLogic.getActivityFromView(view); + AtomicReference reference = new AtomicReference<>(); + if (activity != null && !activity.isFinishing()) { + activity.runOnUiThread(() -> { + MaterialDialog dialog = new MaterialDialog.Builder(activity) + .title(title) + .content(message) + .progress(false, 100, true) + .cancelable(false) + .show(); + reference.set(dialog); + }); + } + return reference; + } +} diff --git a/app/src/main/java/com/zane/smapiinstaller/utils/FileUtils.java b/app/src/main/java/com/zane/smapiinstaller/utils/FileUtils.java index 2c4b9ab..8c7c520 100644 --- a/app/src/main/java/com/zane/smapiinstaller/utils/FileUtils.java +++ b/app/src/main/java/com/zane/smapiinstaller/utils/FileUtils.java @@ -22,7 +22,15 @@ import java.io.OutputStreamWriter; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; +/** + * 文件工具类 + */ public class FileUtils { + /** + * 读取文本文件 + * @param file 文件 + * @return 文本 + */ public static String getFileText(File file) { try { InputStream inputStream = new FileInputStream(file); @@ -34,6 +42,13 @@ public class FileUtils { return null; } + /** + * 读取本地资源或Asset资源 + * @param context context + * @param filename 文件名 + * @return 输入流 + * @throws IOException 异常 + */ public static InputStream getLocalAsset(Context context, String filename) throws IOException { File file = new File(context.getFilesDir(), filename); if (file.exists()) { @@ -42,6 +57,13 @@ public class FileUtils { return context.getAssets().open(filename); } + /** + * 读取JSON文件 + * @param file 文件 + * @param type 数据类型 + * @param 泛型类型 + * @return 数据 + */ public static T getFileJson(File file, TypeReference type) { try { InputStream inputStream = new FileInputStream(file); @@ -53,6 +75,13 @@ public class FileUtils { return null; } + /** + * 读取JSON文件 + * @param file 文件 + * @param tClass 数据类型 + * @param 泛型类型 + * @return 数据 + */ public static T getFileJson(File file, Class tClass) { try { InputStream inputStream = new FileInputStream(file); @@ -64,6 +93,12 @@ public class FileUtils { return null; } + /** + * 写入JSON文件到本地 + * @param context context + * @param filename 文件名 + * @param content 内容 + */ public static void writeAssetJson(Context context, String filename, Object content) { try { String tmpFilename = filename + ".tmp"; @@ -78,6 +113,14 @@ public class FileUtils { } } + /** + * 读取JSON资源 + * @param context context + * @param filename 资源名 + * @param tClass 数据类型 + * @param 泛型类型 + * @return 数据 + */ public static T getAssetJson(Context context, String filename, Class tClass) { try { InputStream inputStream = getLocalAsset(context, filename); @@ -89,6 +132,14 @@ public class FileUtils { return null; } + /** + * 读取JSON资源 + * @param context context + * @param filename 资源名 + * @param type 数据类型 + * @param 泛型类型 + * @return 数据 + */ public static T getAssetJson(Context context, String filename, TypeReference type) { try { InputStream inputStream = getLocalAsset(context, filename); @@ -100,6 +151,12 @@ public class FileUtils { return null; } + /** + * 读取资源为字节数组 + * @param context context + * @param filename 文件名 + * @return 字节数组 + */ public static byte[] getAssetBytes(Context context, String filename) { try { try (InputStream inputStream = getLocalAsset(context, filename)) { @@ -110,10 +167,21 @@ public class FileUtils { return new byte[0]; } + /** + * 简化路径前缀 + * @param path 文件路径 + * @return 移除前缀后的路径 + */ public static String toPrettyPath(String path) { return StringUtils.removeStart(path, Environment.getExternalStorageDirectory().getAbsolutePath()); } + /** + * 计算资源文件SHA3-256 + * @param context context + * @param filename 资源名 + * @return SHA3-256值 + */ public static String getFileHash(Context context, String filename) { try (InputStream inputStream = getLocalAsset(context, filename)) { return Hashing.sha256().hashBytes(ByteStreams.toByteArray(inputStream)).toString(); @@ -122,6 +190,11 @@ public class FileUtils { return null; } + /** + * 计算文件SHA3-256 + * @param file 文件 + * @return SHA3-256值 + */ public static String getFileHash(File file) { try (InputStream inputStream = new FileInputStream(file)) { return Hashing.sha256().hashBytes(ByteStreams.toByteArray(inputStream)).toString(); diff --git a/app/src/main/java/com/zane/smapiinstaller/utils/JSONUtil.java b/app/src/main/java/com/zane/smapiinstaller/utils/JSONUtil.java index 06f3e28..8b75af9 100644 --- a/app/src/main/java/com/zane/smapiinstaller/utils/JSONUtil.java +++ b/app/src/main/java/com/zane/smapiinstaller/utils/JSONUtil.java @@ -8,22 +8,48 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +/** + * JSON工具类 + */ public class JSONUtil { private static final ObjectMapper mapper = new ObjectMapper(); static { + // 允许未定义的属性 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false); + // 允许尾部额外的逗号 mapper.configure(JsonReadFeature.ALLOW_TRAILING_COMMA.mappedFeature(), true); + // 允许数组设置空值 mapper.configure(JsonReadFeature.ALLOW_MISSING_VALUES.mappedFeature(), true); + // 允许Java注释 mapper.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS.mappedFeature(), true); } + + /** + * 转JSON + * @param object 数据 + * @return JSON字符串 + * @throws Exception 异常 + */ public static String toJson(Object object) throws Exception { return mapper.writeValueAsString(object); } + /** + * 校验JSON + * @param jsonString JSON字符串 + * @throws JsonProcessingException 异常 + */ public static void checkJson(String jsonString) throws JsonProcessingException { mapper.readValue(jsonString, Object.class); } + /** + * JSON反序列化 + * @param jsonString JSON + * @param cls 目标类型 + * @param 泛型参数 + * @return 数据 + */ public static T fromJson(String jsonString, Class cls) { try { return mapper.readValue(jsonString, cls); @@ -33,6 +59,13 @@ public class JSONUtil { return null; } + /** + * JSON反序列化 + * @param jsonString JSON + * @param type 目标类型 + * @param 泛型参数 + * @return 数据 + */ public static T fromJson(String jsonString, TypeReference type) { try { return mapper.readValue(jsonString, type); diff --git a/app/src/main/java/com/zane/smapiinstaller/utils/VersionUtil.java b/app/src/main/java/com/zane/smapiinstaller/utils/VersionUtil.java index 1838980..963ff27 100644 --- a/app/src/main/java/com/zane/smapiinstaller/utils/VersionUtil.java +++ b/app/src/main/java/com/zane/smapiinstaller/utils/VersionUtil.java @@ -1,15 +1,22 @@ package com.zane.smapiinstaller.utils; -import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import org.apache.commons.lang3.StringUtils; -import java.math.BigDecimal; import java.util.List; +/** + * 版本比较工具 + */ public class VersionUtil { + /** + * 比较单个版本段 + * @param sectionA sectionA + * @param sectionB sectionB + * @return 比较结果 + */ private static int compareVersionSection(String sectionA, String sectionB) { try { return Integer.compare(Integer.parseInt(sectionA), Integer.parseInt(sectionB)); @@ -40,6 +47,12 @@ public class VersionUtil { } return Integer.compare(listA.size(), listB.size()); } + + /** + * 判断是否为空版本段 + * @param versionSections 版本段列表 + * @return 是否为空版本段 + */ private static boolean isZero(List versionSections) { return !Iterables.filter(versionSections, version -> { try { @@ -53,6 +66,12 @@ public class VersionUtil { }).iterator().hasNext(); } + /** + * 比较两个版本 + * @param versionA versionA + * @param versionB versionB + * @return 比较结果 + */ public static int compareVersion(String versionA, String versionB) { List versionSectionsA = Splitter.on(".").splitToList(versionA); List versionSectionsB = Splitter.on(".").splitToList(versionB);