diff --git a/.idea/misc.xml b/.idea/misc.xml index e6714f8..7bfef59 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,10 +1,5 @@ - - - - - diff --git a/app/src/main/java/com/zane/smapiinstaller/MainActivity.java b/app/src/main/java/com/zane/smapiinstaller/MainActivity.java index d9fe736..2e42a6f 100644 --- a/app/src/main/java/com/zane/smapiinstaller/MainActivity.java +++ b/app/src/main/java/com/zane/smapiinstaller/MainActivity.java @@ -23,6 +23,7 @@ import com.zane.smapiinstaller.entity.DaoSession; import com.zane.smapiinstaller.entity.FrameworkConfig; import com.zane.smapiinstaller.logic.ConfigManager; import com.zane.smapiinstaller.logic.GameLauncher; +import com.zane.smapiinstaller.logic.ModAssetsManager; import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.TranslateUtil; @@ -168,6 +169,9 @@ public class MainActivity extends AppCompatActivity { case R.id.settings_translation_service: selectTranslateServiceLogic(); return true; + case R.id.toolbar_update_check: + updateCheckLogic(); + return true; default: return super.onOptionsItemSelected(item); } @@ -270,6 +274,11 @@ public class MainActivity extends AppCompatActivity { }).show()); } + private void updateCheckLogic() { + ModAssetsManager modAssetsManager = new ModAssetsManager(toolbar); + modAssetsManager.checkModUpdate(); + } + @Override public boolean onSupportNavigateUp() { NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); diff --git a/app/src/main/java/com/zane/smapiinstaller/MainApplication.java b/app/src/main/java/com/zane/smapiinstaller/MainApplication.java index 1c54b56..812a1cb 100644 --- a/app/src/main/java/com/zane/smapiinstaller/MainApplication.java +++ b/app/src/main/java/com/zane/smapiinstaller/MainApplication.java @@ -8,7 +8,6 @@ import com.lzy.okgo.OkGo; import com.zane.smapiinstaller.entity.DaoMaster; import com.zane.smapiinstaller.entity.DaoSession; import com.zane.smapiinstaller.utils.DbOpenHelper; -import com.zane.smapiinstaller.utils.GzipRequestInterceptor; import org.greenrobot.greendao.database.Database; @@ -27,7 +26,7 @@ public class MainApplication extends Application { super.onCreate(); OkHttpClient okHttpClient = new OkHttpClient.Builder() //开启Gzip压缩 - .addInterceptor(new GzipRequestInterceptor()) +// .addInterceptor(new GzipRequestInterceptor()) .build(); OkGo.getInstance().setOkHttpClient(okHttpClient).init(this); LanguagesManager.init(this); 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 3451e49..e8dc346 100644 --- a/app/src/main/java/com/zane/smapiinstaller/constant/Constants.java +++ b/app/src/main/java/com/zane/smapiinstaller/constant/Constants.java @@ -69,4 +69,9 @@ public class Constants { * 平台 */ public static final String PLATFORM = "Android"; + + /** + * SMAPI更新服务 + */ + public static final String UPDATE_CHECK_SERVICE_URL = "https://smapi.io/api/v" + SMAPI_VERSION + "/mods"; } diff --git a/app/src/main/java/com/zane/smapiinstaller/dto/ModUpdateCheckRequestDto.java b/app/src/main/java/com/zane/smapiinstaller/dto/ModUpdateCheckRequestDto.java new file mode 100644 index 0000000..e075991 --- /dev/null +++ b/app/src/main/java/com/zane/smapiinstaller/dto/ModUpdateCheckRequestDto.java @@ -0,0 +1,142 @@ +package com.zane.smapiinstaller.dto; + +import android.util.Log; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.google.common.base.CharMatcher; +import com.google.common.base.Splitter; +import com.zane.smapiinstaller.constant.Constants; +import com.zane.smapiinstaller.entity.ModManifestEntry; + +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +import lombok.Data; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * @author Zane + */ +@Data +@RequiredArgsConstructor +public class ModUpdateCheckRequestDto { + + /** + * 待检查MOD列表 + */ + @NonNull + private List mods; + /** + * SMAPI版本 + */ + private SemanticVersion apiVersion = new SemanticVersion(Constants.SMAPI_VERSION); + /** + * 游戏版本 + */ + @NonNull + private SemanticVersion gameVersion; + /** + * 平台版本 + */ + private String platform = Constants.PLATFORM; + /** + * 是否拉取MOD详情 + */ + private boolean includeExtendedMetadata = false; + + @Data + @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE) + public static class SemanticVersion { + private int MajorVersion; + private int MinorVersion; + private int PatchVersion; + private int PlatformRelease; + private String PrereleaseTag; + private String BuildMetadata; + + public SemanticVersion(String versionStr) { + // init + MajorVersion = 0; + MinorVersion = 0; + PatchVersion = 0; + PlatformRelease = 0; + PrereleaseTag = null; + BuildMetadata = null; + // normalize + versionStr = StringUtils.trim(versionStr); + if (StringUtils.isBlank(versionStr)) { + return; + } + List versionSections = Splitter.on(CharMatcher.anyOf(".-+")).splitToList(versionStr); + // read major/minor version + int i = 0; + if (versionSections.size() > i) { + MajorVersion = Integer.parseInt(versionSections.get(i)); + } + else { + return; + } + i++; + if (versionSections.size() > i) { + MinorVersion = Integer.parseInt(versionSections.get(i)); + } + else { + return; + } + i++; + // read optional patch version + if (versionSections.size() > i) { + PatchVersion = Integer.parseInt(versionSections.get(i)); + } + else { + return; + } + i++; + // read optional non-standard platform release version + try { + if (versionSections.size() > i) { + PlatformRelease = Integer.parseInt(versionSections.get(i)); + } + else { + return; + } + } catch (NumberFormatException ignored) { + } + // read optional prerelease tag + versionSections = Splitter.on("-").limit(2).splitToList(versionStr); + if (versionSections.size() > 1) { + PrereleaseTag = RegExUtils.removeFirst(versionSections.get(1), "\\+.*"); + } + else { + return; + } + // read optional build tag + versionSections = Splitter.on("+").limit(2).splitToList(versionStr); + if (versionSections.size() > 1) { + BuildMetadata = versionSections.get(1); + } + } + } + + @Data + public static class ModInfo { + private String id; + private List updateKeys; + private SemanticVersion installedVersion; + + public static ModInfo fromModManifestEntry(ModManifestEntry mod) { + ModInfo modInfo = new ModInfo(); + modInfo.setId(mod.getUniqueID()); + try { + modInfo.setInstalledVersion(new SemanticVersion(mod.getVersion())); + } catch (Exception e) { + Log.d("", "", e); + } + modInfo.setUpdateKeys(mod.getUpdateKeys()); + return modInfo; + } + } +} diff --git a/app/src/main/java/com/zane/smapiinstaller/dto/ModUpdateCheckResponseDto.java b/app/src/main/java/com/zane/smapiinstaller/dto/ModUpdateCheckResponseDto.java new file mode 100644 index 0000000..b652b88 --- /dev/null +++ b/app/src/main/java/com/zane/smapiinstaller/dto/ModUpdateCheckResponseDto.java @@ -0,0 +1,20 @@ +package com.zane.smapiinstaller.dto; + +import java.util.List; + +import lombok.Data; + +/** + * @author Zane + */ +@Data +public class ModUpdateCheckResponseDto { + private String id; + private UpdateInfo suggestedUpdate; + private List errors; + @Data + public static class UpdateInfo { + private String version; + private String url; + } +} 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 6a8ed51..b5133e7 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java @@ -189,6 +189,9 @@ public class ApkPatcher { AtomicReference packageName = new AtomicReference<>(); AtomicLong versionCode = new AtomicLong(); Predicate processLogic = (attr) -> { + if(attr == null) { + return true; + } if (attr.type == NodeVisitor.TYPE_STRING) { String strObj = (String) attr.obj; switch (attr.name) { @@ -227,6 +230,9 @@ public class ApkPatcher { try { byte[] modifyManifest = CommonLogic.modifyManifest(bytes, processLogic); Iterables.removeIf(manifests, manifest -> { + if(manifest == null) { + return true; + } if (versionCode.get() < manifest.getMinBuildCode()) { return true; } 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 9d7a727..62ad9bc 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java @@ -42,11 +42,13 @@ import pxb.android.axml.NodeVisitor; /** * 通用逻辑 + * * @author Zane */ public class CommonLogic { /** * 从View获取所属Activity + * * @param view context容器 * @return Activity */ @@ -65,19 +67,34 @@ public class CommonLogic { /** * 从一个View获取Application + * * @param view 控件 * @return Application */ public static MainApplication getApplicationFromView(View view) { Activity activity = getActivityFromView(view); - if(null != activity) { + if (null != activity) { return (MainApplication) activity.getApplication(); } return null; } + /** + * 当data非null时执行操作 + * + * @param data 数据 + * @param action 操作 + * @param 泛型 + */ + public static void doOnNonNull(T data, Consumer action) { + if (data != null) { + action.accept(data); + } + } + /** * 打开指定URL + * * @param context context * @param url 目标URL */ @@ -87,22 +104,23 @@ public class CommonLogic { intent.setData(Uri.parse(url)); intent.setAction(Intent.ACTION_VIEW); context.startActivity(intent); - } - catch (ActivityNotFoundException ignored){ + } catch (ActivityNotFoundException ignored) { } } /** * 复制文本到剪贴板 + * * @param context 上下文 * @param copyStr 文本 * @return 是否复制成功 */ public static boolean copyToClipboard(Context context, String copyStr) { try { - ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - ClipData mClipData = ClipData.newPlainText("Label", copyStr); - cm.setPrimaryClip(mClipData); + CommonLogic.doOnNonNull(context.getSystemService(Context.CLIPBOARD_SERVICE), cm -> { + ClipData mClipData = ClipData.newPlainText("Label", copyStr); + ((ClipboardManager) cm).setPrimaryClip(mClipData); + }); return true; } catch (Exception e) { return false; @@ -111,6 +129,7 @@ public class CommonLogic { /** * 扫描全部兼容包 + * * @param context context * @return 兼容包列表 */ @@ -130,10 +149,9 @@ public class CommonLogic { } } Collections.sort(apkFilesManifests, (a, b) -> { - if(a.getTargetPackageName() != null && b.getTargetPackageName() == null) { + if (a.getTargetPackageName() != null && b.getTargetPackageName() == null) { return -1; - } - else if(b.getTargetPackageName() != null){ + } else if (b.getTargetPackageName() != null) { return Long.compare(b.getMinBuildCode(), a.getMinBuildCode()); } return 1; @@ -143,13 +161,15 @@ public class CommonLogic { /** * 提取SMAPI环境文件到内部存储对应位置 - * @param context context - * @param apkPath 安装包路径 + * + * @param context context + * @param apkPath 安装包路径 * @param checkMode 是否为校验模式 * @return 操作是否成功 */ public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMode) { - List manifestEntries = FileUtils.getAssetJson(context, "smapi_files_manifest.json", new TypeReference>() { }); + List manifestEntries = FileUtils.getAssetJson(context, "smapi_files_manifest.json", new TypeReference>() { + }); if (manifestEntries == null) { return false; } @@ -199,6 +219,7 @@ public class CommonLogic { /** * 修改AndroidManifest.xml文件 + * * @param bytes AndroidManifest.xml文件字符数组 * @param processLogic 处理逻辑 * @return 修改后的AndroidManifest.xml文件字符数组 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 598974b..6697a15 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/GameLauncher.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/GameLauncher.java @@ -24,10 +24,11 @@ public class GameLauncher { } /** - * 启动逻辑 + * 检查已安装MOD版本游戏 + * @param context 上下文 + * @return 软件包信息 */ - public void launch() { - Activity context = CommonLogic.getActivityFromView(root); + public static PackageInfo getGamePackageInfo(Activity context) { PackageManager packageManager = context.getPackageManager(); try { PackageInfo packageInfo; @@ -36,20 +37,35 @@ public class GameLauncher { } catch (PackageManager.NameNotFoundException ignored) { packageInfo = packageManager.getPackageInfo(Constants.TARGET_PACKAGE_NAME_SAMSUNG, 0); } + return packageInfo; + } catch (PackageManager.NameNotFoundException ignored) { + return null; + } + } + + /** + * 启动逻辑 + */ + public void launch() { + Activity context = CommonLogic.getActivityFromView(root); + PackageManager packageManager = context.getPackageManager(); + try { + PackageInfo packageInfo = getGamePackageInfo(context); + if(packageInfo == null) { + DialogUtils.showAlertDialog(root, R.string.error, R.string.error_smapi_not_installed); + return; + } if(!CommonLogic.unpackSmapiFiles(context, packageInfo.applicationInfo.publicSourceDir, true)) { DialogUtils.showAlertDialog(root, R.string.error, R.string.error_failed_to_repair); return; } ModAssetsManager modAssetsManager = new ModAssetsManager(root); - PackageInfo finalPackageInfo = packageInfo; modAssetsManager.checkModEnvironment((isConfirm) -> { if(isConfirm) { - Intent intent = packageManager.getLaunchIntentForPackage(finalPackageInfo.packageName); + Intent intent = packageManager.getLaunchIntentForPackage(packageInfo.packageName); context.startActivity(intent); } }); - } catch (PackageManager.NameNotFoundException ignored) { - DialogUtils.showAlertDialog(root, R.string.error, R.string.error_smapi_not_installed); } catch (Exception e) { Crashes.trackError(e); DialogUtils.showAlertDialog(root, R.string.error, e.getLocalizedMessage()); 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 d92c571..dc861c6 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/ModAssetsManager.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/ModAssetsManager.java @@ -1,6 +1,7 @@ package com.zane.smapiinstaller.logic; import android.app.Activity; +import android.content.pm.PackageInfo; import android.os.Environment; import android.util.Log; import android.view.View; @@ -8,17 +9,23 @@ import android.view.View; import com.afollestad.materialdialogs.DialogAction; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.base.Joiner; -import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimaps; import com.google.common.collect.Queues; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.model.Response; +import com.microsoft.appcenter.crashes.Crashes; import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.constant.Constants; +import com.zane.smapiinstaller.dto.ModUpdateCheckRequestDto; +import com.zane.smapiinstaller.dto.ModUpdateCheckResponseDto; import com.zane.smapiinstaller.entity.ModManifestEntry; import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.FileUtils; +import com.zane.smapiinstaller.utils.JSONUtil; +import com.zane.smapiinstaller.utils.JsonCallback; import com.zane.smapiinstaller.utils.VersionUtil; import org.apache.commons.lang3.StringUtils; @@ -30,12 +37,12 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; +import androidx.core.util.Consumer; import java9.util.Objects; +import java9.util.function.Predicate; import java9.util.stream.Collectors; import java9.util.stream.StreamSupport; -import androidx.core.util.Consumer; - /** * Mod资源管理器 */ @@ -68,7 +75,7 @@ public class ModAssetsManager { foundManifest = true; if (manifest != null) { manifest.setAssetPath(file.getParentFile().getAbsolutePath()); - if (filter.apply(manifest)) { + if (filter.test(manifest)) { return manifest; } } @@ -155,7 +162,7 @@ public class ModAssetsManager { if (installedMods.size() > 1) { 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()))))); + StreamSupport.stream(installedMods).map(item -> FileUtils.toPrettyPath(item.getAssetPath())).collect(Collectors.joining(",")))); return false; } else if (installedMods.size() == 0) { installedMods = installedModMap.get(mod.getUniqueID().replace("ZaneYork.CustomLocalization", "SMAPI.CustomLocalization")); @@ -212,7 +219,7 @@ public class ModAssetsManager { for (String key : installedModMap.keySet()) { ImmutableList installedMods = installedModMap.get(key); if (installedMods.size() > 1) { - list.add(Joiner.on(",").join(Lists.transform(installedMods, item -> FileUtils.toPrettyPath(item.getAssetPath())))); + list.add(StreamSupport.stream(installedMods).map(item -> FileUtils.toPrettyPath(item.getAssetPath())).collect(Collectors.joining(","))); } } if (!list.isEmpty()) { @@ -284,6 +291,37 @@ public class ModAssetsManager { } } + public void checkModUpdate() { + List list = StreamSupport.stream(findAllInstalledMods(false)) + .filter(mod -> mod.getUpdateKeys() != null && !mod.getUpdateKeys().isEmpty()) + .map(ModUpdateCheckRequestDto.ModInfo::fromModManifestEntry) + .filter(modInfo -> modInfo.getInstalledVersion() != null) + .collect(Collectors.toList()); + Activity context = CommonLogic.getActivityFromView(root); + PackageInfo gamePackageInfo = GameLauncher.getGamePackageInfo(context); + if (gamePackageInfo == null) { + return; + } + try { + ModUpdateCheckRequestDto requestDto = new ModUpdateCheckRequestDto(list, new ModUpdateCheckRequestDto.SemanticVersion(gamePackageInfo.versionName)); + OkGo.>post(Constants.UPDATE_CHECK_SERVICE_URL) + .upJson(JSONUtil.toJson(requestDto)) + .execute(new JsonCallback>(new TypeReference>() { + }) { + @Override + public void onSuccess(Response> response) { + List checkResponseDtos = response.body(); + if (checkResponseDtos != null) { + List list = StreamSupport.stream(checkResponseDtos).filter(dto -> dto.getSuggestedUpdate() != null).collect(Collectors.toList()); + + } + } + }); + } catch (Exception e) { + Crashes.trackError(e); + } + } + private String checkModDependencyError(ModManifestEntry mod, ImmutableListMultimap installedModMap) { if (mod.getDependencies() != null) { List unsatisfiedDependencies = StreamSupport.stream(mod.getDependencies()) diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/about/AboutFragment.java b/app/src/main/java/com/zane/smapiinstaller/ui/about/AboutFragment.java index 2a5b6f2..799a859 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/about/AboutFragment.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/about/AboutFragment.java @@ -24,7 +24,6 @@ import androidx.fragment.app.Fragment; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import java9.util.Objects; /** * @author Zane @@ -44,17 +43,16 @@ public class AboutFragment extends Fragment { @OnClick(R.id.button_release) void release() { - CommonLogic.openUrl(this.getContext(), "https://github.com/ZaneYork/SMAPI-Android-Installer/releases"); + CommonLogic.doOnNonNull(this.getContext(), (context) -> CommonLogic.openUrl(context, "https://github.com/ZaneYork/SMAPI-Android-Installer/releases")); } @OnClick(R.id.button_gplay) void gplay() { try { - this.openPlayStore("market://details?id=" + this.getActivity().getPackageName()); + CommonLogic.doOnNonNull(this.getActivity(), (activity) -> this.openPlayStore("market://details?id=" + activity.getPackageName())); } catch (ActivityNotFoundException ex) { - CommonLogic.openUrl(this.getContext(), "https://play.google.com/store/apps/details?id=" + this.getActivity().getPackageName()); + CommonLogic.doOnNonNull(this.getActivity(), (activity) -> CommonLogic.openUrl(activity, "https://play.google.com/store/apps/details?id=" + activity.getPackageName())); } - } private void openPlayStore(String url) { @@ -62,29 +60,26 @@ public class AboutFragment extends Fragment { intent.setData(Uri.parse(url)); intent.setPackage("com.android.vending"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - this.getActivity().startActivity(intent); + CommonLogic.doOnNonNull(this.getActivity(), (activity) -> activity.startActivity(intent)); } @OnClick({R.id.button_qq_group_1, R.id.button_qq_group_2}) void joinQQ(Button which) { String baseUrl = "mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26k%3D"; if (which.getId() == R.id.button_qq_group_1) { - CommonLogic.openUrl(this.getContext(), baseUrl + "AAflCLHiWw1haM1obu_f-CpGsETxXc6b"); + CommonLogic.doOnNonNull(this.getContext(), (context) -> CommonLogic.openUrl(context, baseUrl + "AAflCLHiWw1haM1obu_f-CpGsETxXc6b")); } else { - CommonLogic.openUrl(this.getContext(), baseUrl + "kshK7BavcS2jXZ6exDvezc18ksLB8YsM"); + CommonLogic.doOnNonNull(this.getContext(), (context) -> CommonLogic.openUrl(context, baseUrl + "kshK7BavcS2jXZ6exDvezc18ksLB8YsM")); } } @OnClick(R.id.button_donation) void donation() { - Context context = this.getContext(); - DialogUtils.setCurrentDialog(new MaterialDialog.Builder(context) + CommonLogic.doOnNonNull(this.getContext(), (context) -> DialogUtils.setCurrentDialog(new MaterialDialog.Builder(context) .title(R.string.button_donation_text) .items(R.array.donation_methods) .itemsCallback((dialog, itemView, position, text) -> - CommonLogic.showAnimation(imgHeart, R.anim.heart_beat, (animation) -> { - listSelectLogic(context, position); - })).show()); + CommonLogic.showAnimation(imgHeart, R.anim.heart_beat, (animation) -> listSelectLogic(context, position))).show())); } private void listSelectLogic(Context context, int position) { @@ -108,10 +103,11 @@ public class AboutFragment extends Fragment { if (hasInstalledAlipayClient) { if (CommonLogic.copyToClipboard(context, Constants.RED_PACKET_CODE)) { PackageManager packageManager = context.getPackageManager(); - Intent intent = packageManager.getLaunchIntentForPackage("com.eg.android.AlipayGphone"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - Toast.makeText(context, R.string.toast_redpacket_message, Toast.LENGTH_LONG).show(); + CommonLogic.doOnNonNull(packageManager.getLaunchIntentForPackage("com.eg.android.AlipayGphone"), (intent) -> { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + Toast.makeText(context, R.string.toast_redpacket_message, Toast.LENGTH_LONG).show(); + }); } } break; 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 92dc9eb..0e129c6 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 @@ -14,6 +14,7 @@ import com.afollestad.materialdialogs.DialogAction; import com.zane.smapiinstaller.BuildConfig; import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.constant.Constants; +import com.zane.smapiinstaller.logic.CommonLogic; import com.zane.smapiinstaller.utils.DialogUtils; import com.zane.smapiinstaller.utils.FileUtils; import com.zane.smapiinstaller.utils.JSONUtil; @@ -48,33 +49,36 @@ public class ConfigEditFragment extends Fragment { ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_config_edit, container, false); ButterKnife.bind(this, root); - editable = this.getArguments().getBoolean("editable"); - if(!editable) { + CommonLogic.doOnNonNull(this.getArguments(), arguments -> { + editable = arguments.getBoolean("editable"); + configPath = arguments.getString("configPath"); + }); + if (!editable) { editText.setKeyListener(null); buttonConfigSave.setVisibility(View.INVISIBLE); buttonConfigCancel.setVisibility(View.INVISIBLE); } - configPath = this.getArguments().getString("configPath"); - if(configPath != null) { + if (configPath != null) { File file = new File(configPath); - if(file.exists() && file.length() < Constants.TEXT_FILE_OPEN_SIZE_LIMIT) { + if (file.exists() && file.length() < Constants.TEXT_FILE_OPEN_SIZE_LIMIT) { String fileText = FileUtils.getFileText(file); if (fileText != null) { editText.setText(fileText); } - } - else { + } else { editText.setText(""); editText.setKeyListener(null); DialogUtils.showConfirmDialog(root, R.string.error, this.getString(R.string.text_too_large), R.string.open_with, R.string.cancel, ((dialog, which) -> { - if(which == DialogAction.POSITIVE) { + if (which == DialogAction.POSITIVE) { Intent intent = new Intent("android.intent.action.VIEW"); intent.addCategory("android.intent.category.DEFAULT"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - Uri contentUri = FileProvider.getUriForFile(this.getContext(), BuildConfig.APPLICATION_ID + ".provider", file); - intent.setDataAndType(contentUri, "text/plain"); + CommonLogic.doOnNonNull(this.getContext(), (context -> { + Uri contentUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); + intent.setDataAndType(contentUri, "text/plain"); + })); } else { intent.setDataAndType(Uri.fromFile(file), "text/plain"); } @@ -86,21 +90,23 @@ public class ConfigEditFragment extends Fragment { } return root; } - @OnClick(R.id.button_config_save) void onConfigSave() { + + @OnClick(R.id.button_config_save) + void onConfigSave() { try { JSONUtil.checkJson(editText.getText().toString()); FileOutputStream outputStream = new FileOutputStream(configPath); - try(OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream)){ + try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream)) { outputStreamWriter.write(editText.getText().toString()); outputStreamWriter.flush(); } - } - catch (Exception e) { + } catch (Exception e) { DialogUtils.showAlertDialog(getView(), R.string.error, e.getLocalizedMessage()); } } - @OnClick(R.id.button_config_cancel) void onConfigCancel() { - Navigation.findNavController(getView()).popBackStack(); + @OnClick(R.id.button_config_cancel) + void onConfigCancel() { + CommonLogic.doOnNonNull(getView(), view -> Navigation.findNavController(view).popBackStack()); } } diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigFragment.java b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigFragment.java index 0baf503..b3edeee 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigFragment.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigFragment.java @@ -17,6 +17,7 @@ import butterknife.OnTextChanged; import com.afollestad.materialdialogs.MaterialDialog; import com.zane.smapiinstaller.R; +import com.zane.smapiinstaller.logic.CommonLogic; import com.zane.smapiinstaller.utils.DialogUtils; import java.util.ArrayList; @@ -55,7 +56,7 @@ public class ConfigFragment extends Fragment { @OnClick(R.id.button_sort_by) void onSortByClick() { int index; - switch (configViewModel.getSortBy()){ + switch (configViewModel.getSortBy()) { case "Name asc": index = 0; break; @@ -71,24 +72,27 @@ public class ConfigFragment extends Fragment { default: index = 0; } - DialogUtils.setCurrentDialog(new MaterialDialog.Builder(this.getContext()).title(R.string.sort_by).items(R.array.mod_list_sort_by).itemsCallbackSingleChoice(index, (dialog, itemView, position, text) -> { - switch (position) { - case 0: - configViewModel.switchSortBy("Name asc"); - break; - case 1: - configViewModel.switchSortBy("Name desc"); - break; - case 2: - configViewModel.switchSortBy("Date asc"); - break; - case 3: - configViewModel.switchSortBy("Date desc"); - break; - default: - return false; - } - return true; - }).show()); + CommonLogic.doOnNonNull(this.getContext(), context -> DialogUtils.setCurrentDialog(new MaterialDialog.Builder(context) + .title(R.string.sort_by) + .items(R.array.mod_list_sort_by) + .itemsCallbackSingleChoice(index, (dialog, itemView, position, text) -> { + switch (position) { + case 0: + configViewModel.switchSortBy("Name asc"); + break; + case 1: + configViewModel.switchSortBy("Name desc"); + break; + case 2: + configViewModel.switchSortBy("Date asc"); + break; + case 3: + configViewModel.switchSortBy("Date desc"); + break; + default: + return false; + } + return true; + }).show())); } } diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigViewModel.java b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigViewModel.java index 255d886..b138073 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigViewModel.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigViewModel.java @@ -39,9 +39,11 @@ class ConfigViewModel extends ViewModel implements ListenableObject filteredModList; private String sortBy = "Name asc"; + public String getSortBy() { return sortBy; } + private final View root; private final List>> onChangedListener = new ArrayList<>(); @@ -55,7 +57,7 @@ class ConfigViewModel extends ViewModel implements ListenableObject query = appConfigDao.queryBuilder().where(AppConfigDao.Properties.Name.eq(AppConfigKey.MOD_LIST_SORT_BY)).build(); AppConfig appConfig = query.unique(); - if(null != appConfig) { + if (null != appConfig) { sortBy = appConfig.getValue(); } } @@ -64,7 +66,7 @@ class ConfigViewModel extends ViewModel implements ListenableObject a.getName().compareTo(b.getName())); - if(filteredModList != null && filteredModList != modList) { + if (filteredModList != null && filteredModList != modList) { Collections.sort(filteredModList, (a, b) -> a.getName().compareTo(b.getName())); } break; case "Name desc": Collections.sort(modList, (a, b) -> b.getName().compareTo(a.getName())); - if(filteredModList != null && filteredModList != modList) { + if (filteredModList != null && filteredModList != modList) { Collections.sort(filteredModList, (a, b) -> b.getName().compareTo(a.getName())); } break; case "Date asc": Collections.sort(modList, (a, b) -> a.getLastModified().compareTo(b.getLastModified())); - if(filteredModList != null && filteredModList != modList) { + if (filteredModList != null && filteredModList != modList) { Collections.sort(filteredModList, (a, b) -> a.getLastModified().compareTo(b.getLastModified())); } break; case "Date desc": Collections.sort(modList, (a, b) -> b.getLastModified().compareTo(a.getLastModified())); - if(filteredModList != null && filteredModList != modList) { + if (filteredModList != null && filteredModList != modList) { Collections.sort(filteredModList, (a, b) -> b.getLastModified().compareTo(a.getLastModified())); } break; default: return; } - if(filteredModList != null) { + if (filteredModList != null) { emitDataChangeEvent(filteredModList); - } - else { + } else { emitDataChangeEvent(modList); } } @@ -136,15 +137,17 @@ class ConfigViewModel extends ViewModel implements ListenableObject { - daoSession.getTranslationResultDao().insertOrReplaceInTx(results); - ImmutableMap map = Maps.uniqueIndex(results, TranslationResult::getOrigin); - for (ModManifestEntry mod : modList) { - if (map.containsKey(mod.getDescription())) { - mod.setTranslatedDescription(map.get(mod.getDescription()).getTranslation()); + TranslateUtil.translateText(untranslatedText, translator, language, (result) -> { + CommonLogic.doOnNonNull(result, (results) -> { + daoSession.getTranslationResultDao().insertOrReplaceInTx(results); + ImmutableMap map = Maps.uniqueIndex(results, TranslationResult::getOrigin); + for (ModManifestEntry mod : modList) { + if (map.containsKey(mod.getDescription())) { + mod.setTranslatedDescription(map.get(mod.getDescription()).getTranslation()); + } } - } - emitDataChangeEvent(modList); + emitDataChangeEvent(modList); + }); return true; }); } 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 5a1b443..da5f9c4 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 @@ -8,7 +8,6 @@ import android.widget.Button; import android.widget.TextView; import com.afollestad.materialdialogs.DialogAction; -import com.google.common.base.Predicate; import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.constant.Constants; import com.zane.smapiinstaller.entity.ModManifestEntry; @@ -29,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; +import java9.util.function.Predicate; /** * @author Zane @@ -104,7 +104,7 @@ public class ModManifestAdapter extends RecyclerView.Adapter removeAll(Predicate predicate) { List deletedId = new ArrayList<>(); for (int i = modList.size() - 1; i >= 0; i--) { - if (predicate.apply(modList.get(i))) { + if (predicate.test(modList.get(i))) { modList.remove(i); deletedId.add(i); } @@ -151,7 +151,7 @@ public class ModManifestAdapter extends RecyclerView.Adapter predicate) { for (int i = 0; i < modList.size(); i++) { - if (predicate.apply(modList.get(i))) { + if (predicate.test(modList.get(i))) { return i; } } diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/help/HelpFragment.java b/app/src/main/java/com/zane/smapiinstaller/ui/help/HelpFragment.java index 2a0420b..cb6f6ef 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/help/HelpFragment.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/help/HelpFragment.java @@ -50,18 +50,20 @@ public class HelpFragment extends Fragment { return root; } @OnClick(R.id.button_compat) void compat() { - CommonLogic.openUrl(this.getContext(), "https://smapi.io/mods"); + CommonLogic.doOnNonNull(this.getContext(), context -> CommonLogic.openUrl(context, "https://smapi.io/mods")); } @OnClick(R.id.button_nexus) void nexus() { - CommonLogic.openUrl(this.getContext(), "https://www.nexusmods.com/stardewvalley/mods/"); + CommonLogic.doOnNonNull(this.getContext(), context -> CommonLogic.openUrl(context, "https://www.nexusmods.com/stardewvalley/mods/")); } @OnClick({R.id.button_logs}) void showLog() { - NavController controller = Navigation.findNavController(this.getView()); - File logFile = new File(Environment.getExternalStorageDirectory(), Constants.LOG_PATH); - if(logFile.exists()) { - HelpFragmentDirections.ActionNavHelpToConfigEditFragment action = HelpFragmentDirections.actionNavHelpToConfigEditFragment(logFile.getAbsolutePath()); - action.setEditable(false); - controller.navigate(action); - } + CommonLogic.doOnNonNull(this.getView(), view -> { + NavController controller = Navigation.findNavController(view); + File logFile = new File(Environment.getExternalStorageDirectory(), Constants.LOG_PATH); + if(logFile.exists()) { + HelpFragmentDirections.ActionNavHelpToConfigEditFragment action = HelpFragmentDirections.actionNavHelpToConfigEditFragment(logFile.getAbsolutePath()); + action.setEditable(false); + controller.navigate(action); + } + }); } } diff --git a/app/src/main/java/com/zane/smapiinstaller/utils/JsonCallback.java b/app/src/main/java/com/zane/smapiinstaller/utils/JsonCallback.java new file mode 100644 index 0000000..26e9973 --- /dev/null +++ b/app/src/main/java/com/zane/smapiinstaller/utils/JsonCallback.java @@ -0,0 +1,48 @@ +package com.zane.smapiinstaller.utils; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.lzy.okgo.callback.AbsCallback; +import com.lzy.okgo.request.base.Request; + +import okhttp3.Response; +import okhttp3.ResponseBody; + +public abstract class JsonCallback extends AbsCallback { + + private TypeReference type; + private Class clazz; + + public JsonCallback(TypeReference type) { + this.type = type; + } + + public JsonCallback(Class clazz) { + this.clazz = clazz; + } + + @Override + public void onStart(Request request) { + super.onStart(request); + } + + /** + * 该方法是子线程处理,不能做ui相关的工作 + * 主要作用是解析网络返回的 response 对象,生产onSuccess回调中需要的数据对象 + * 这里的解析工作不同的业务逻辑基本都不一样,所以需要自己实现,以下给出的时模板代码,实际使用根据需要修改 + */ + @Override + public T convertResponse(Response response) throws Throwable { + ResponseBody body = response.body(); + if (body == null) { + return null; + } + T data = null; + if (type != null) { + data = JSONUtil.fromJson(body.string(), type); + } + if (clazz != null) { + data = JSONUtil.fromJson(body.string(), clazz); + } + return data; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/update_check.png b/app/src/main/res/drawable/update_check.png new file mode 100644 index 0000000..944748f Binary files /dev/null and b/app/src/main/res/drawable/update_check.png differ diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index 087b7bb..556fb99 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -1,6 +1,12 @@ +