1.Mod update check(in progress)

2.code clean up
This commit is contained in:
ZaneYork 2020-03-30 15:08:54 +08:00
parent 5743e50e04
commit e69565d5e3
19 changed files with 430 additions and 114 deletions

View File

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectPlainTextFileTypeManager">
<file url="file://$PROJECT_DIR$/app/src/main/assets/AndroidManifest.xml" />
<file url="file://$PROJECT_DIR$/app/src/main/assets/apk/AndroidManifest.xml" />
<file url="file://$PROJECT_DIR$/app/src/main/assets/apk/AndroidManifest.xml" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>

View File

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

View File

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

View File

@ -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";
}

View File

@ -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<ModInfo> 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<String> 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<String> 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;
}
}
}

View File

@ -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<String> errors;
@Data
public static class UpdateInfo {
private String version;
private String url;
}
}

View File

@ -189,6 +189,9 @@ public class ApkPatcher {
AtomicReference<String> packageName = new AtomicReference<>();
AtomicLong versionCode = new AtomicLong();
Predicate<ManifestTagVisitor.AttrArgs> 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;
}

View File

@ -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 <T> 泛型
*/
public static <T> void doOnNonNull(T data, Consumer<T> 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);
CommonLogic.doOnNonNull(context.getSystemService(Context.CLIPBOARD_SERVICE), cm -> {
ClipData mClipData = ClipData.newPlainText("Label", copyStr);
cm.setPrimaryClip(mClipData);
((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 checkMode 是否为校验模式
* @return 操作是否成功
*/
public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMode) {
List<ManifestEntry> manifestEntries = FileUtils.getAssetJson(context, "smapi_files_manifest.json", new TypeReference<List<ManifestEntry>>() { });
List<ManifestEntry> manifestEntries = FileUtils.getAssetJson(context, "smapi_files_manifest.json", new TypeReference<List<ManifestEntry>>() {
});
if (manifestEntries == null) {
return false;
}
@ -199,6 +219,7 @@ public class CommonLogic {
/**
* 修改AndroidManifest.xml文件
*
* @param bytes AndroidManifest.xml文件字符数组
* @param processLogic 处理逻辑
* @return 修改后的AndroidManifest.xml文件字符数组

View File

@ -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());

View File

@ -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<ModManifestEntry> 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<ModUpdateCheckRequestDto.ModInfo> 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.<List<ModUpdateCheckResponseDto>>post(Constants.UPDATE_CHECK_SERVICE_URL)
.upJson(JSONUtil.toJson(requestDto))
.execute(new JsonCallback<List<ModUpdateCheckResponseDto>>(new TypeReference<List<ModUpdateCheckResponseDto>>() {
}) {
@Override
public void onSuccess(Response<List<ModUpdateCheckResponseDto>> response) {
List<ModUpdateCheckResponseDto> checkResponseDtos = response.body();
if (checkResponseDtos != null) {
List<ModUpdateCheckResponseDto> list = StreamSupport.stream(checkResponseDtos).filter(dto -> dto.getSuggestedUpdate() != null).collect(Collectors.toList());
}
}
});
} catch (Exception e) {
Crashes.trackError(e);
}
}
private String checkModDependencyError(ModManifestEntry mod, ImmutableListMultimap<String, ModManifestEntry> installedModMap) {
if (mod.getDependencies() != null) {
List<ModManifestEntry> unsatisfiedDependencies = StreamSupport.stream(mod.getDependencies())

View File

@ -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");
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;

View File

@ -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);
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());
}
}

View File

@ -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,7 +72,10 @@ 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) -> {
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");
@ -89,6 +93,6 @@ public class ConfigFragment extends Fragment {
return false;
}
return true;
}).show());
}).show()));
}
}

View File

@ -39,9 +39,11 @@ class ConfigViewModel extends ViewModel implements ListenableObject<List<ModMani
private List<ModManifestEntry> filteredModList;
private String sortBy = "Name asc";
public String getSortBy() {
return sortBy;
}
private final View root;
private final List<Predicate<List<ModManifestEntry>>> onChangedListener = new ArrayList<>();
@ -55,7 +57,7 @@ class ConfigViewModel extends ViewModel implements ListenableObject<List<ModMani
AppConfigDao appConfigDao = app.getDaoSession().getAppConfigDao();
Query<AppConfig> 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<List<ModMani
public void switchSortBy(String sortBy) {
MainApplication app = CommonLogic.getApplicationFromView(root);
if(null == app) {
if (null == app) {
return;
}
this.sortBy = sortBy;
@ -78,35 +80,34 @@ class ConfigViewModel extends ViewModel implements ListenableObject<List<ModMani
switch (sortBy) {
case "Name asc":
Collections.sort(modList, (a, b) -> 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,7 +137,8 @@ class ConfigViewModel extends ViewModel implements ListenableObject<List<ModMani
}
}).filter(Objects::nonNull).distinct().collect(Collectors.toList());
if (!untranslatedText.isEmpty()) {
TranslateUtil.translateText(untranslatedText, translator, language, (results) -> {
TranslateUtil.translateText(untranslatedText, translator, language, (result) -> {
CommonLogic.doOnNonNull(result, (results) -> {
daoSession.getTranslationResultDao().insertOrReplaceInTx(results);
ImmutableMap<String, TranslationResult> map = Maps.uniqueIndex(results, TranslationResult::getOrigin);
for (ModManifestEntry mod : modList) {
@ -145,6 +147,7 @@ class ConfigViewModel extends ViewModel implements ListenableObject<List<ModMani
}
}
emitDataChangeEvent(modList);
});
return true;
});
}

View File

@ -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<ModManifestAdapter.
public List<Integer> removeAll(Predicate<ModManifestEntry> predicate) {
List<Integer> 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<ModManifestAdapter.
public Integer findFirst(Predicate<ModManifestEntry> predicate) {
for (int i = 0; i < modList.size(); i++) {
if (predicate.apply(modList.get(i))) {
if (predicate.test(modList.get(i))) {
return i;
}
}

View File

@ -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());
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);
}
});
}
}

View File

@ -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<T> extends AbsCallback<T> {
private TypeReference<T> type;
private Class<T> clazz;
public JsonCallback(TypeReference<T> type) {
this.type = type;
}
public JsonCallback(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public void onStart(Request<T, ? extends 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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,6 +1,12 @@
<?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/toolbar_update_check"
android:orderInCategory="99"
android:icon="@drawable/update_check"
app:showAsAction="always"
android:title="" />
<group
android:checkableBehavior="all">
<item