Help tips, minor refactor

This commit is contained in:
ZaneYork 2020-03-12 17:41:07 +08:00
parent df58b507cc
commit f32ba1e0e6
28 changed files with 315 additions and 89 deletions

View File

@ -9,8 +9,8 @@ android {
applicationId "com.zane.smapiinstaller"
minSdkVersion 19
targetSdkVersion 28
versionCode 7
versionName "1.1.3"
versionCode 8
versionName "1.2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
@ -42,8 +42,8 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.navigation:navigation-fragment:2.3.0-alpha02'
implementation 'androidx.navigation:navigation-ui:2.3.0-alpha02'
implementation 'androidx.navigation:navigation-fragment:2.3.0-alpha03'
implementation 'androidx.navigation:navigation-ui:2.3.0-alpha03'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'com.madgag.spongycastle:core:1.54.0.0'
implementation 'com.madgag.spongycastle:prov:1.54.0.0'

View File

@ -11,6 +11,7 @@
<application
android:name=".MainApplication"
android:allowBackup="true"
android:fullBackupContent="@xml/appcenter_backup_rule"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"

View File

@ -0,0 +1,14 @@
{
"version": 0,
"items": [
{
"title": "Mod安装一般流程",
"author": "ZaneYork",
"content": "1.前往<a href=\"https://smapi.io/mods\">兼容性查询</a>网站确认目标Mod的兼容性以及最新版本下载地址<br>2.下载Mod到手机<br>3.通过压缩文件管理器解压Mod文件包到手机内置存储的StardewValley/Mods目录下<br>4.点击安装器提供的启动按钮确认新增Mod之后的环境是否正常并启动游戏"
},{
"title": "部分常用框架包下载链接",
"author": "ZaneYork",
"content": "<a href=\"https://www.nexusmods.com/stardewvalley/mods/1915?tab=files\">Content Patcher</a>: CP前置大部分美化包(非xnb替换类)需要的前置Mod<br><a href=\"https://www.nexusmods.com/stardewvalley/mods/1348?tab=files\">Space Core</a>: SC前置被Json Assets等Mod依赖的框架<br><a href=\"https://www.nexusmods.com/stardewvalley/mods/1720?tab=files\">Json Assets</a>: JA前置大部分新增物品类Mod需要的前置Mod<br><a href=\"https://www.nexusmods.com/stardewvalley/mods/4970?tab=files\">Producer Framework Mod</a>: PFM前置大部分新增制作配方类Mod需要的前置Mod"
}
]
}

View File

@ -158,11 +158,6 @@
"~1.1.2 | Status": "AssumeBroken" // crashes game on startup
},
"Fix Dice": {
"ID": "ashley.fixdice",
"~1.1.2 | Status": "AssumeBroken" // crashes game on startup
},
"Grass Growth": {
"ID": "bcmpinc.GrassGrowth",
"~1.0 | Status": "AssumeBroken"

View File

@ -8,5 +8,7 @@ public class Constants {
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";
public static final String APP_CENTER_SECRET = "cb44e94a-7b2f-431e-9ad9-48013ec8c208";
}

View File

@ -3,9 +3,10 @@ package com.zane.smapiinstaller.entity;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
public class DownloadableContentList {
private int version;
@EqualsAndHashCode(callSuper = true)
public class DownloadableContentList extends UpdatableList {
List<DownloadableContent> contents;
}

View File

@ -0,0 +1,10 @@
package com.zane.smapiinstaller.entity;
import lombok.Data;
@Data
public class HelpItem {
private String title;
private String content;
private String author;
}

View File

@ -0,0 +1,12 @@
package com.zane.smapiinstaller.entity;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class HelpItemList extends UpdatableList {
private List<HelpItem> items;
}

View File

@ -0,0 +1,8 @@
package com.zane.smapiinstaller.entity;
import lombok.Data;
@Data
public class UpdatableList {
private int version;
}

View File

@ -1,44 +0,0 @@
package com.zane.smapiinstaller.logic;
import android.view.View;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.StringCallback;
import com.lzy.okgo.model.Response;
import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.entity.DownloadableContentList;
import com.zane.smapiinstaller.utils.FileUtils;
import com.zane.smapiinstaller.utils.JSONUtil;
public class DownloadabeContentManager {
private final View root;
private static final String TAG = "DLC_MGR";
private static boolean updateChecked = false;
private static DownloadableContentList downloadableContentList = null;
public DownloadabeContentManager(View root) {
this.root = root;
downloadableContentList = FileUtils.getAssetJson(root.getContext(), "downloadable_content_list.json", DownloadableContentList.class);
if(!updateChecked) {
updateChecked = true;
OkGo.<String>get(Constants.DLC_LIST_UPDATE_URL).execute(new StringCallback(){
@Override
public void onSuccess(Response<String> response) {
DownloadableContentList content = JSONUtil.fromJson(response.body(), DownloadableContentList.class);
if(content != null && downloadableContentList.getVersion() < content.getVersion()) {
FileUtils.writeAssetJson(root.getContext(), "downloadable_content_list.json", content);
downloadableContentList = content;
}
}
});
}
}
public DownloadableContentList getDownloadableContentList() {
return downloadableContentList;
}
}

View File

@ -6,6 +6,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.view.View;
import com.microsoft.appcenter.crashes.Crashes;
import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.constant.Constants;
@ -35,6 +36,9 @@ public class GameLauncher {
});
} catch (PackageManager.NameNotFoundException ignored) {
CommonLogic.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());
}
}
}

View File

@ -78,19 +78,25 @@ public class ModAssetsManager {
File currentFile = files.poll();
if (currentFile != null && currentFile.exists()) {
boolean foundManifest = false;
for (File file : currentFile.listFiles(File::isFile)) {
if (StringUtils.equalsIgnoreCase(file.getName(), "manifest.json")) {
ModManifestEntry manifest = FileUtils.getFileJson(file, ModManifestEntry.class);
foundManifest = true;
if (manifest != null) {
manifest.setAssetPath(file.getParentFile().getAbsolutePath());
mods.add(manifest);
File[] listFiles = currentFile.listFiles(File::isFile);
if(listFiles != null) {
for (File file : listFiles) {
if (StringUtils.equalsIgnoreCase(file.getName(), "manifest.json")) {
ModManifestEntry manifest = FileUtils.getFileJson(file, ModManifestEntry.class);
foundManifest = true;
if (manifest != null && StringUtils.isNoneBlank(manifest.getUniqueID())) {
manifest.setAssetPath(file.getParentFile().getAbsolutePath());
mods.add(manifest);
}
break;
}
break;
}
}
if (!foundManifest) {
files.addAll(Lists.newArrayList(currentFile.listFiles(File::isDirectory)));
File[] listDirectories = currentFile.listFiles(File::isDirectory);
if(listDirectories != null) {
files.addAll(Lists.newArrayList(listDirectories));
}
}
}
} while (!files.isEmpty());

View File

@ -0,0 +1,50 @@
package com.zane.smapiinstaller.logic;
import android.view.View;
import com.google.common.base.Predicate;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.StringCallback;
import com.lzy.okgo.model.Response;
import com.zane.smapiinstaller.entity.UpdatableList;
import com.zane.smapiinstaller.utils.FileUtils;
import com.zane.smapiinstaller.utils.JSONUtil;
import java.util.ArrayList;
import java.util.List;
public class UpdatableListManager<T extends UpdatableList> {
private static boolean updateChecked = false;
private static UpdatableList updatableList = null;
private List<Predicate<T>> onChangedListener = new ArrayList<>();
public UpdatableListManager(View root, String filename, Class<T> tClass, String updateUrl) {
updatableList = FileUtils.getAssetJson(root.getContext(), filename, tClass);
if(!updateChecked) {
updateChecked = true;
OkGo.<String>get(updateUrl).execute(new StringCallback(){
@Override
public void onSuccess(Response<String> response) {
UpdatableList content = JSONUtil.fromJson(response.body(), tClass);
if(content != null && updatableList.getVersion() < content.getVersion()) {
FileUtils.writeAssetJson(root.getContext(), filename, content);
updatableList = content;
for (Predicate<T> listener : onChangedListener) {
listener.apply(getList());
}
}
}
});
}
}
public T getList() {
return (T) updatableList;
}
public void registerListChangeListener(Predicate<T> onChanged) {
this.onChangedListener.add(onChanged);
}
}

View File

@ -39,7 +39,12 @@ import java.util.concurrent.atomic.AtomicReference;
*/
public class DownloadableContentAdapter extends RecyclerView.Adapter<DownloadableContentAdapter.ViewHolder> {
private final List<DownloadableContent> downloadableContentList;
private List<DownloadableContent> downloadableContentList;
public void setDownloadableContentList(List<DownloadableContent> downloadableContentList) {
this.downloadableContentList = downloadableContentList;
notifyDataSetChanged();
}
public DownloadableContentAdapter(List<DownloadableContent> items) {
downloadableContentList = items;
@ -62,7 +67,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
return downloadableContentList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.text_item_type)
TextView typeTextView;
@BindView(R.id.text_item_name)

View File

@ -2,20 +2,19 @@ package com.zane.smapiinstaller.ui.download;
import android.content.Context;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.entity.DownloadableContentList;
import com.zane.smapiinstaller.logic.CommonLogic;
import com.zane.smapiinstaller.logic.DownloadabeContentManager;
import com.zane.smapiinstaller.logic.UpdatableListManager;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
/**
* A fragment representing a list of Items.
@ -39,8 +38,13 @@ public class DownloadableContentFragment extends Fragment {
Context context = view.getContext();
RecyclerView recyclerView = (RecyclerView) view;
recyclerView.setLayoutManager(new LinearLayoutManager(context));
DownloadabeContentManager manager = new DownloadabeContentManager(view);
recyclerView.setAdapter(new DownloadableContentAdapter(manager.getDownloadableContentList().getContents()));
UpdatableListManager<DownloadableContentList> manager = new UpdatableListManager<>(view, "downloadable_content_list.json", DownloadableContentList.class, Constants.DLC_LIST_UPDATE_URL);
DownloadableContentAdapter adapter = new DownloadableContentAdapter(manager.getList().getContents());
recyclerView.setAdapter(adapter);
manager.registerListChangeListener((list) -> {
adapter.setDownloadableContentList(list.getContents());
return true;
});
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
}
return view;

View File

@ -8,7 +8,9 @@ import android.view.ViewGroup;
import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.entity.HelpItemList;
import com.zane.smapiinstaller.logic.CommonLogic;
import com.zane.smapiinstaller.logic.UpdatableListManager;
import java.io.File;
@ -16,15 +18,31 @@ import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class HelpFragment extends Fragment {
@BindView(R.id.view_help_list)
RecyclerView recyclerView;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_help, container, false);
ButterKnife.bind(this, root);
recyclerView.setLayoutManager(new LinearLayoutManager(this.getContext()));
UpdatableListManager<HelpItemList> manager = new UpdatableListManager<>(root, "help_item_list.json", HelpItemList.class, Constants.HELP_LIST_UPDATE_URL);
HelpItemAdapter adapter = new HelpItemAdapter(manager.getList().getItems());
recyclerView.setAdapter(adapter);
manager.registerListChangeListener((list) -> {
adapter.setHelpItems(list.getItems());
return true;
});
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
return root;
}
@OnClick(R.id.button_compat) void compat() {

View File

@ -0,0 +1,70 @@
package com.zane.smapiinstaller.ui.help;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.entity.HelpItem;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
public class HelpItemAdapter extends RecyclerView.Adapter<HelpItemAdapter.ViewHolder> {
public void setHelpItems(List<HelpItem> helpItems) {
this.helpItems = helpItems;
notifyDataSetChanged();
}
private List<HelpItem> helpItems;
public HelpItemAdapter(List<HelpItem> helpItems) {
this.helpItems = helpItems;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.help_list_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.setHelpItem(helpItems.get(position));
}
@Override
public int getItemCount() {
return helpItems.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.text_item_title)
TextView textTitle;
@BindView(R.id.text_item_author)
TextView textAuthor;
@BindView(R.id.text_item_content)
TextView textContent;
public ViewHolder(View view) {
super(view);
ButterKnife.bind(this, itemView);
}
void setHelpItem(HelpItem item) {
textTitle.setText(item.getTitle());
textAuthor.setText(item.getAuthor());
textContent.setText(Html.fromHtml(item.getContent()));
textContent.setMovementMethod(LinkMovementMethod.getInstance());
}
}
}

View File

@ -10,6 +10,8 @@ import android.view.ViewGroup;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.GravityEnum;
import com.afollestad.materialdialogs.MaterialDialog;
import com.microsoft.appcenter.AppCenter;
import com.microsoft.appcenter.crashes.Crashes;
import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.logic.ApkPatcher;
import com.zane.smapiinstaller.logic.CommonLogic;
@ -42,12 +44,11 @@ public class InstallFragment extends Fragment {
void Install() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
CommonLogic.showConfirmDialog(root, R.string.confirm, R.string.android_version_confirm, ((dialog, which) -> {
if(which == DialogAction.POSITIVE) {
if (which == DialogAction.POSITIVE) {
installLogic();
}
}));
}
else {
} else {
installLogic();
}
}
@ -95,7 +96,12 @@ public class InstallFragment extends Fragment {
patcher.install(signPath);
dialog.incrementProgress(1);
} finally {
}
catch (Exception e) {
Crashes.trackError(e);
CommonLogic.showAlertDialog(root, R.string.error, e.getLocalizedMessage());
}
finally {
if (!dialog.isCancelled()) {
dialog.dismiss();
}

View File

@ -74,7 +74,7 @@
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_menu_share"
android:contentDescription="@string/icon_desc" />
<TextView
<Button
android:id="@+id/button_release"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -82,6 +82,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/img_test"
app:layout_constraintEnd_toEndOf="parent"
style="@style/Widget.AppCompat.Button.Borderless"
android:gravity="center"
android:textSize="24sp"
android:text="@string/button_release" />

View File

@ -8,6 +8,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/view_mod_list"
app:layoutManager="LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>

View File

@ -30,6 +30,15 @@
app:layout_constraintTop_toBottomOf="@id/guideline_h1"
app:layout_constraintStart_toEndOf="@id/button_nexus" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/view_help_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintStart_toEndOf="@id/guideline_v1"
app:layout_constraintEnd_toStartOf="@id/guideline_v2"
app:layout_constraintTop_toBottomOf="@id/button_compat"/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_h1"
android:layout_width="wrap_content"
@ -37,10 +46,25 @@
android:orientation="horizontal"
app:layout_constraintGuide_begin="20dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_h2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_end="60dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_v1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="20dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_v2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="20dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:divider="@drawable/horizontal_divider"
android:showDividers="middle"
android:orientation="vertical">
<RelativeLayout
android:background="@color/colorTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text_item_title"
android:textSize="16sp"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/text_item_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/text_item_author"
app:layout_constraintTop_toTopOf="parent"
android:layout_alignParentEnd="true"
android:textSize="12sp"
android:paddingTop="2sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_item_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.google.android.material.textview.MaterialTextView>
</LinearLayout>

View File

@ -1,7 +1,5 @@
<resources>
<string name="app_name">SMAPI安裝器</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title">SMAPI安裝器</string>
<string name="nav_header_subtitle">ZaneYork@qq.com</string>
<string name="action_settings">設置</string>

View File

@ -1,7 +1,5 @@
<resources>
<string name="app_name">SMAPI安裝器</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title">SMAPI安裝器</string>
<string name="nav_header_subtitle">ZaneYork@qq.com</string>
<string name="action_settings">設置</string>

View File

@ -1,7 +1,5 @@
<resources>
<string name="app_name">SMAPI安装器</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title">SMAPI安装器</string>
<string name="nav_header_subtitle">ZaneYork@qq.com</string>
<string name="action_settings">设置</string>

View File

@ -3,4 +3,5 @@
<color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
<color name="colorTitle">#8BD</color>
</resources>

View File

@ -1,7 +1,5 @@
<resources>
<string name="app_name">SMAPI Installer</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title">SMAPI Installer</string>
<string name="nav_header_subtitle">ZaneYork@qq.com</string>
<string name="action_settings">Settings</string>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content xmlns:tools="http://schemas.android.com/tools">
<exclude domain="sharedpref" path="AppCenter.xml"/>
<exclude domain="database" path="com.microsoft.appcenter.persistence"/>
<exclude domain="database" path="com.microsoft.appcenter.persistence-journal"/>
<exclude domain="file" path="error" tools:ignore="FullBackupContent"/>
<exclude domain="file" path="appcenter" tools:ignore="FullBackupContent"/>
</full-backup-content>