1.Traditional Chinese translation

2.Download progress bar
This commit is contained in:
ZaneYork 2020-03-07 15:09:28 +08:00
parent e04aa786c6
commit cc21df5126
12 changed files with 181 additions and 31 deletions

View File

@ -9,8 +9,8 @@ android {
applicationId "com.zane.smapiinstaller" applicationId "com.zane.smapiinstaller"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 28 targetSdkVersion 28
versionCode 4 versionCode 5
versionName "1.1.1" versionName "1.1.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true

View File

@ -1,19 +1,19 @@
{ {
"version": 1, "version": 2,
"contents": [ "contents": [
{ {
"type": "COMPAT", "type": "COMPAT",
"name": "SMAPI for 1.4.5.137", "name": "SMAPI for 1.4.5.137",
"assetPath": "compat/137/", "assetPath": "compat/137/",
"description": "SMAPI compat package for game 1.4.4.128 - 1.4.5.137", "description": "SMAPI compat package for game 1.4.4.128 - 1.4.5.137",
"url": "http://dl.zaneyork.cn/compat/smapi_137.zip", "url": "http://zaneyork.cn/download/compat/smapi_137.zip",
"hash": "bd16e8e4cb52d636e24c6a2d2309b66a60e492d2b97c1b8f6a519c04ac42ebdc" "hash": "bd16e8e4cb52d636e24c6a2d2309b66a60e492d2b97c1b8f6a519c04ac42ebdc"
}, },
{ {
"type": "LOCALE", "type": "LOCALE",
"name": "中文汉化v2.4", "name": "中文汉化v2.4",
"description": "简体中文语言包感谢Wabi-Sabi提供", "description": "简体中文语言包感谢Wabi-Sabi提供",
"url": "http://dl.zaneyork.cn/locale/locale_pack_zh_2.4.zip", "url": "http://zaneyork.cn/download/locale/locale_pack_zh_2.4.zip",
"hash": "59be19240160de61046f05619f3e448d180137e0989feb537e392a014588615f" "hash": "59be19240160de61046f05619f3e448d180137e0989feb537e392a014588615f"
} }
] ]

View File

@ -38,6 +38,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import pxb.android.axml.AxmlReader; import pxb.android.axml.AxmlReader;
import pxb.android.axml.AxmlVisitor; import pxb.android.axml.AxmlVisitor;
@ -59,7 +60,7 @@ public class CommonLogic {
public static InputStream getLocalAsset(Context context, String filename) throws IOException { public static InputStream getLocalAsset(Context context, String filename) throws IOException {
File file = new File(context.getFilesDir(), filename); File file = new File(context.getFilesDir(), filename);
if(file.exists()){ if (file.exists()) {
return new FileInputStream(file); return new FileInputStream(file);
} }
return context.getAssets().open(filename); return context.getAssets().open(filename);
@ -94,8 +95,7 @@ public class CommonLogic {
FileOutputStream outputStream = new FileOutputStream(file); FileOutputStream outputStream = new FileOutputStream(file);
try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) {
writer.write(new Gson().toJson(content)); writer.write(new Gson().toJson(content));
} } finally {
finally {
FileUtils.moveFile(file, new File(context.getFilesDir(), filename)); FileUtils.moveFile(file, new File(context.getFilesDir(), filename));
} }
} catch (Exception ignored) { } catch (Exception ignored) {
@ -136,8 +136,8 @@ public class CommonLogic {
public static void setProgressDialogState(View view, MaterialDialog dialog, int message, int progress) { public static void setProgressDialogState(View view, MaterialDialog dialog, int message, int progress) {
Activity activity = getActivityFromView(view); Activity activity = getActivityFromView(view);
if(activity != null && !activity.isFinishing() && !dialog.isCancelled()) { if (activity != null && !activity.isFinishing() && !dialog.isCancelled()) {
activity.runOnUiThread(()->{ activity.runOnUiThread(() -> {
dialog.incrementProgress(progress - dialog.getCurrentProgress()); dialog.incrementProgress(progress - dialog.getCurrentProgress());
dialog.setContent(message); dialog.setContent(message);
}); });
@ -159,62 +159,80 @@ public class CommonLogic {
public static void showAlertDialog(View view, int title, String message) { public static void showAlertDialog(View view, int title, String message) {
Activity activity = getActivityFromView(view); Activity activity = getActivityFromView(view);
if(activity != null && !activity.isFinishing()) { if (activity != null && !activity.isFinishing()) {
activity.runOnUiThread(()-> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.ok).show()); 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) { public static void showAlertDialog(View view, int title, int message) {
Activity activity = getActivityFromView(view); Activity activity = getActivityFromView(view);
if(activity != null && !activity.isFinishing()) { if (activity != null && !activity.isFinishing()) {
activity.runOnUiThread(()-> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.ok).show()); 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) { public static void showConfirmDialog(View view, int title, int message, MaterialDialog.SingleButtonCallback callback) {
Activity activity = getActivityFromView(view); Activity activity = getActivityFromView(view);
if(activity != null && !activity.isFinishing()) { 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()); 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) { public static void showConfirmDialog(View view, int title, String message, MaterialDialog.SingleButtonCallback callback) {
Activity activity = getActivityFromView(view); Activity activity = getActivityFromView(view);
if(activity != null && !activity.isFinishing()) { 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()); activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.confirm).negativeText(R.string.cancel).onAny(callback).show());
} }
} }
public static AtomicReference<MaterialDialog> showProgressDialog(View view, int title, String message) {
Activity activity = getActivityFromView(view);
AtomicReference<MaterialDialog> 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;
}
public static List<ApkFilesManifest> findAllApkFileManifest(Context context) { public static List<ApkFilesManifest> findAllApkFileManifest(Context context) {
ApkFilesManifest apkFilesManifest = CommonLogic.getAssetJson(context, "apk_files_manifest.json", ApkFilesManifest.class); ApkFilesManifest apkFilesManifest = CommonLogic.getAssetJson(context, "apk_files_manifest.json", ApkFilesManifest.class);
ArrayList<ApkFilesManifest> apkFilesManifests = Lists.newArrayList(apkFilesManifest); ArrayList<ApkFilesManifest> apkFilesManifests = Lists.newArrayList(apkFilesManifest);
File compatFolder = new File(context.getFilesDir(), "compat"); File compatFolder = new File(context.getFilesDir(), "compat");
if(compatFolder.exists()) { if (compatFolder.exists()) {
for (File directory : compatFolder.listFiles(File::isDirectory)) { for (File directory : compatFolder.listFiles(File::isDirectory)) {
File manifestFile = new File(directory, "apk_files_manifest.json"); File manifestFile = new File(directory, "apk_files_manifest.json");
if(manifestFile.exists()) { if (manifestFile.exists()) {
ApkFilesManifest manifest = getFileJson(manifestFile, ApkFilesManifest.class); ApkFilesManifest manifest = getFileJson(manifestFile, ApkFilesManifest.class);
if(manifest != null) { if (manifest != null) {
apkFilesManifests.add(manifest); apkFilesManifests.add(manifest);
} }
} }
} }
} }
Collections.sort(apkFilesManifests, (a, b)-> Long.compare(b.getMinBuildCode(), a.getMinBuildCode())); Collections.sort(apkFilesManifests, (a, b) -> Long.compare(b.getMinBuildCode(), a.getMinBuildCode()));
return apkFilesManifests; return apkFilesManifests;
} }
public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMod) { public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMod) {
List<ManifestEntry> manifestEntries = CommonLogic.getAssetJson(context, "smapi_files_manifest.json", new TypeToken<List<ManifestEntry>>() { List<ManifestEntry> manifestEntries = CommonLogic.getAssetJson(context, "smapi_files_manifest.json", new TypeToken<List<ManifestEntry>>() {
}.getType()); }.getType());
if(manifestEntries == null) if (manifestEntries == null)
return false; return false;
File basePath = new File(Environment.getExternalStorageDirectory() + "/StardewValley/"); File basePath = new File(Environment.getExternalStorageDirectory() + "/StardewValley/");
if(!basePath.exists()) { if (!basePath.exists()) {
if(!basePath.mkdir()) { if (!basePath.mkdir()) {
return false; return false;
} }
} }
File noMedia = new File(basePath,".nomedia"); File noMedia = new File(basePath, ".nomedia");
if (!noMedia.exists()) { if (!noMedia.exists()) {
try { try {
noMedia.createNewFile(); noMedia.createNewFile();
@ -225,7 +243,7 @@ public class CommonLogic {
File targetFile = new File(basePath, entry.getTargetPath()); File targetFile = new File(basePath, entry.getTargetPath());
switch (entry.getOrigin()) { switch (entry.getOrigin()) {
case 0: case 0:
if(!checkMod || !targetFile.exists()) { if (!checkMod || !targetFile.exists()) {
try (InputStream inputStream = context.getAssets().open(entry.getAssetPath())) { try (InputStream inputStream = context.getAssets().open(entry.getAssetPath())) {
if (!targetFile.getParentFile().exists()) { if (!targetFile.getParentFile().exists()) {
if (!targetFile.getParentFile().mkdirs()) { if (!targetFile.getParentFile().mkdirs()) {
@ -241,7 +259,7 @@ public class CommonLogic {
} }
break; break;
case 1: case 1:
if(!checkMod || !targetFile.exists()) { if (!checkMod || !targetFile.exists()) {
ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath(), targetFile); ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath(), targetFile);
} }
break; break;
@ -275,7 +293,7 @@ public class CommonLogic {
} }
public static String getFileHash(Context context, String filename) { public static String getFileHash(Context context, String filename) {
try(InputStream inputStream = getLocalAsset(context, filename)){ try (InputStream inputStream = getLocalAsset(context, filename)) {
return Hashing.sha256().hashBytes(ByteStreams.toByteArray(inputStream)).toString(); return Hashing.sha256().hashBytes(ByteStreams.toByteArray(inputStream)).toString();
} catch (IOException ignored) { } catch (IOException ignored) {
} }
@ -283,7 +301,7 @@ public class CommonLogic {
} }
public static String getFileHash(File file) { public static String getFileHash(File file) {
try(InputStream inputStream = new FileInputStream(file)){ try (InputStream inputStream = new FileInputStream(file)) {
return Hashing.sha256().hashBytes(ByteStreams.toByteArray(inputStream)).toString(); return Hashing.sha256().hashBytes(ByteStreams.toByteArray(inputStream)).toString();
} catch (IOException ignored) { } catch (IOException ignored) {
} }

View File

@ -0,0 +1,65 @@
package com.zane.smapiinstaller.ui.about;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.zane.smapiinstaller.R;
/**
* A simple {@link Fragment} subclass.
* Use the {@link AboutFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class AboutFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
public AboutFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment AboutFragment.
*/
// TODO: Rename and change types and number of parameters
public static AboutFragment newInstance(String param1, String param2) {
AboutFragment fragment = new AboutFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_about, container, false);
}
}

View File

@ -17,6 +17,7 @@ import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.lzy.okgo.OkGo; import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.FileCallback; import com.lzy.okgo.callback.FileCallback;
import com.lzy.okgo.model.Progress;
import com.lzy.okgo.model.Response; import com.lzy.okgo.model.Response;
import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.entity.DownloadableContent; import com.zane.smapiinstaller.entity.DownloadableContent;
@ -31,6 +32,8 @@ import org.zeroturnaround.zip.commons.FileUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* {@link RecyclerView.Adapter} that can display a {@link DownloadableContent} * {@link RecyclerView.Adapter} that can display a {@link DownloadableContent}
@ -71,6 +74,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
Button buttonRemove; Button buttonRemove;
@BindView(R.id.button_download_content) @BindView(R.id.button_download_content)
Button buttonDownload; Button buttonDownload;
private AtomicBoolean downloading = new AtomicBoolean(false);
public DownloadableContent downloadableContent; public DownloadableContent downloadableContent;
@ -134,16 +138,40 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
return; return;
} }
} }
if (downloading.get())
return;
downloading.set(true);
ModManifestEntry finalModManifestEntry = modManifestEntry; ModManifestEntry finalModManifestEntry = modManifestEntry;
AtomicReference<MaterialDialog> dialogRef = CommonLogic.showProgressDialog(itemView, R.string.progress, "");
OkGo.<File>get(downloadableContent.getUrl()).execute(new FileCallback(file.getParentFile().getAbsolutePath(), file.getName()) { OkGo.<File>get(downloadableContent.getUrl()).execute(new FileCallback(file.getParentFile().getAbsolutePath(), file.getName()) {
@Override @Override
public void onError(Response<File> response) { public void onError(Response<File> response) {
super.onError(response); super.onError(response);
MaterialDialog dialog = dialogRef.get();
if (dialog != null && !dialog.isCancelled()) {
dialog.dismiss();
}
downloading.set(false);
CommonLogic.showAlertDialog(itemView, R.string.error, R.string.error_failed_to_download); CommonLogic.showAlertDialog(itemView, R.string.error, R.string.error_failed_to_download);
} }
@Override
public void downloadProgress(Progress progress) {
super.downloadProgress(progress);
MaterialDialog dialog = dialogRef.get();
if (dialog != null && !dialog.isCancelled()) {
dialog.setContent(R.string.downloading, progress.currentSize / 1024, progress.totalSize / 1024);
dialog.setProgress((int) (progress.currentSize / progress.totalSize));
}
}
@Override @Override
public void onSuccess(Response<File> response) { public void onSuccess(Response<File> response) {
MaterialDialog dialog = dialogRef.get();
if (dialog != null && !dialog.isCancelled()) {
dialog.dismiss();
}
downloading.set(false);
File downloadedFile = response.body(); File downloadedFile = response.body();
String hash = CommonLogic.getFileHash(downloadedFile); String hash = CommonLogic.getFileHash(downloadedFile);
if (!StringUtils.equalsIgnoreCase(hash, downloadableContent.getHash())) { if (!StringUtils.equalsIgnoreCase(hash, downloadableContent.getHash())) {

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@drawable/horizontal_divider"
android:showDividers="middle"
tools:context=".ui.about.AboutFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/test_message" />
</LinearLayout>

View File

@ -20,5 +20,9 @@
android:id="@+id/nav_help" android:id="@+id/nav_help"
android:icon="@android:drawable/ic_menu_help" android:icon="@android:drawable/ic_menu_help"
android:title="@string/menu_help" /> android:title="@string/menu_help" />
<item
android:id="@+id/nav_about"
android:icon="@android:drawable/ic_dialog_info"
android:title="@string/menu_about" />
</group> </group>
</menu> </menu>

View File

@ -44,4 +44,9 @@
android:name="com.zane.smapiinstaller.ui.download.DownloadableContentFragment" android:name="com.zane.smapiinstaller.ui.download.DownloadableContentFragment"
android:label="@string/menu_download" android:label="@string/menu_download"
tools:layout="@layout/fragment_download_content_list" /> tools:layout="@layout/fragment_download_content_list" />
<fragment
android:id="@+id/nav_about"
android:name="com.zane.smapiinstaller.ui.about.AboutFragment"
android:label="@string/menu_about"
tools:layout="@layout/fragment_about" />
</navigation> </navigation>

View File

@ -49,4 +49,7 @@
<string name="locale_pack">語言包</string> <string name="locale_pack">語言包</string>
<string name="info">提示</string> <string name="info">提示</string>
<string name="download_unpack_success">已完成下載安裝</string> <string name="download_unpack_success">已完成下載安裝</string>
<string name="progress">進度</string>
<string name="downloading">正在下載: %d KB / %d KB</string>
<string name="menu_about">關於</string>
</resources> </resources>

View File

@ -49,4 +49,7 @@
<string name="locale_pack">語言包</string> <string name="locale_pack">語言包</string>
<string name="info">提示</string> <string name="info">提示</string>
<string name="download_unpack_success">已完成下載安裝</string> <string name="download_unpack_success">已完成下載安裝</string>
<string name="progress">進度</string>
<string name="downloading">正在下載: %d KB / %d KB</string>
<string name="menu_about">關於</string>
</resources> </resources>

View File

@ -49,4 +49,7 @@
<string name="locale_pack">语言包</string> <string name="locale_pack">语言包</string>
<string name="info">提示</string> <string name="info">提示</string>
<string name="download_unpack_success">已完成下载安装</string> <string name="download_unpack_success">已完成下载安装</string>
<string name="progress">进度</string>
<string name="downloading">正在下载: %d KB / %d KB</string>
<string name="menu_about">关于</string>
</resources> </resources>

View File

@ -37,7 +37,7 @@
<string name="button_compat">Compat</string> <string name="button_compat">Compat</string>
<string name="button_nexus">Nexus</string> <string name="button_nexus">Nexus</string>
<string name="button_release">Release</string> <string name="button_release">Release</string>
<string name="test_message">Still at test stage now, not release yet.</string> <string name="test_message">Still at test stage now.</string>
<string name="button_logs">Logs</string> <string name="button_logs">Logs</string>
<string name="smapi_game_name">SMAPI Stardew Valley</string> <string name="smapi_game_name">SMAPI Stardew Valley</string>
<string name="error_failed_to_create_file">Failed to create target file : %s</string> <string name="error_failed_to_create_file">Failed to create target file : %s</string>
@ -49,4 +49,10 @@
<string name="locale_pack">Locale Pack</string> <string name="locale_pack">Locale Pack</string>
<string name="info">Info</string> <string name="info">Info</string>
<string name="download_unpack_success">Download and unpack success</string> <string name="download_unpack_success">Download and unpack success</string>
<string name="progress">Progress</string>
<string name="downloading">Downloading: %d KB / %d KB</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="menu_about">About</string>
</resources> </resources>