1.Mod update check

2.App update check
This commit is contained in:
ZaneYork 2020-03-31 18:07:54 +08:00
parent f15d305661
commit 5cec14c4cb
19 changed files with 245 additions and 149 deletions

View File

@ -8,24 +8,31 @@ import android.os.Bundle;
import android.os.Environment;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.google.android.material.navigation.NavigationView;
import com.hjq.language.LanguagesManager;
import com.lmntrx.android.library.livin.missme.ProgressDialog;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.model.Response;
import com.microsoft.appcenter.AppCenter;
import com.microsoft.appcenter.analytics.Analytics;
import com.microsoft.appcenter.crashes.Crashes;
import com.zane.smapiinstaller.constant.AppConfigKey;
import com.zane.smapiinstaller.constant.Constants;
import com.zane.smapiinstaller.constant.DialogAction;
import com.zane.smapiinstaller.dto.AppUpdateCheckResultDto;
import com.zane.smapiinstaller.entity.AppConfig;
import com.zane.smapiinstaller.entity.AppConfigDao;
import com.zane.smapiinstaller.entity.DaoSession;
import com.zane.smapiinstaller.entity.FrameworkConfig;
import com.zane.smapiinstaller.logic.CommonLogic;
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.JSONUtil;
import com.zane.smapiinstaller.utils.JsonCallback;
import com.zane.smapiinstaller.utils.TranslateUtil;
import org.apache.commons.lang3.StringUtils;
@ -102,6 +109,41 @@ public class MainActivity extends AppCompatActivity {
final NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
checkAppUpdate();
}
private void checkAppUpdate() {
DaoSession daoSession = ((MainApplication) this.getApplication()).getDaoSession();
AppConfigDao appConfigDao = daoSession.getAppConfigDao();
AppConfig appConfig = appConfigDao.queryBuilder().where(AppConfigDao.Properties.Name.eq(AppConfigKey.IGNORE_UPDATE_VERSION_CODE)).build().unique();
OkGo.<AppUpdateCheckResultDto>get(Constants.SELF_UPDATE_CHECK_SERVICE_URL).execute(new JsonCallback<AppUpdateCheckResultDto>(AppUpdateCheckResultDto.class) {
@Override
public void onSuccess(Response<AppUpdateCheckResultDto> response) {
AppUpdateCheckResultDto dto = response.body();
if (dto != null && CommonLogic.getVersionCode(MainActivity.this) < dto.getVersionCode()) {
if(appConfig != null && StringUtils.equals(appConfig.getValue(), String.valueOf(dto.getVersionCode()))) {
return;
}
DialogUtils.showConfirmDialog(toolbar, R.string.settings_check_for_updates,
MainActivity.this.getString(R.string.app_update_detected, dto.getVersionName()), (dialog, which) -> {
if (which == DialogAction.POSITIVE) {
CommonLogic.openInPlayStore(MainActivity.this);
}
else {
AppConfig config;
if(appConfig != null) {
config = appConfig;
config.setValue(String.valueOf(dto.getVersionCode()));
}
else {
config = new AppConfig(null, AppConfigKey.IGNORE_UPDATE_VERSION_CODE, String.valueOf(dto.getVersionCode()));
}
appConfigDao.insertOrReplace(config);
}
});
}
}
});
}
@OnClick(R.id.launch)
@ -170,7 +212,7 @@ public class MainActivity extends AppCompatActivity {
selectTranslateServiceLogic();
return true;
case R.id.toolbar_update_check:
updateCheckLogic();
checkModUpdateLogic();
return true;
default:
return super.onOptionsItemSelected(item);
@ -272,11 +314,14 @@ public class MainActivity extends AppCompatActivity {
});
}
private void updateCheckLogic() {
private void checkModUpdateLogic() {
ModAssetsManager modAssetsManager = new ModAssetsManager(toolbar);
modAssetsManager.checkModUpdate((list) -> {
if (list.isEmpty()) {
CommonLogic.runOnUiThread(this, (activity) -> Toast.makeText(activity, R.string.no_update_text, Toast.LENGTH_SHORT).show());
}
try {
NavController controller = Navigation.findNavController(toolbar);
NavController controller = Navigation.findNavController(this, R.id.nav_host_fragment);
controller.navigate(MobileNavigationDirections.actionNavAnyToModUpdateFragment(JSONUtil.toJson(list)));
} catch (Exception e) {
Crashes.trackError(e);

View File

@ -7,4 +7,6 @@ public class AppConfigKey {
public static final String ACTIVE_TRANSLATOR = "ActiveTranslator";
public static final String MOD_LIST_SORT_BY = "ModListSortBy";
public static final String IGNORE_UPDATE_VERSION_CODE = "UpdateIgnoreVersionCode";
}

View File

@ -74,4 +74,9 @@ public class Constants {
* SMAPI更新服务
*/
public static final String UPDATE_CHECK_SERVICE_URL = "https://smapi.io/api/v" + SMAPI_VERSION + "/mods";
/**
* 软件检查更新服务
*/
public static final String SELF_UPDATE_CHECK_SERVICE_URL = "http://zaneyork.cn/download/app_version.json";
}

View File

@ -0,0 +1,15 @@
package com.zane.smapiinstaller.dto;
import lombok.Data;
@Data
public class AppUpdateCheckResultDto {
/**
* 版本号
*/
private long versionCode;
/**
* 版本名称
*/
private String versionName;
}

View File

@ -14,6 +14,7 @@ import org.apache.commons.lang3.StringUtils;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@ -122,6 +123,7 @@ public class ModUpdateCheckRequestDto {
}
@Data
@EqualsAndHashCode(of = "id")
public static class ModInfo {
private String id;
private List<String> updateKeys;

View File

@ -7,7 +7,10 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.view.View;
@ -94,6 +97,7 @@ public class CommonLogic {
/**
* 在UI线程执行操作
*
* @param activity activity
* @param action 操作
*/
@ -267,4 +271,42 @@ public class CommonLogic {
});
view.startAnimation(animation);
}
/**
* 获取版本号
*
* @return 当前应用的版本号
*/
public static long getVersionCode(Activity activity) {
try {
PackageManager manager = activity.getPackageManager();
PackageInfo info = manager.getPackageInfo(activity.getPackageName(), 0);
String version = info.versionName;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return info.getLongVersionCode();
}
return info.versionCode;
} catch (Exception ignored) {
}
return Integer.MAX_VALUE;
}
/**
* 在谷歌商店打开
*
* @param activity activity
*/
public static void openInPlayStore(Activity activity) {
CommonLogic.doOnNonNull(activity, (context) -> {
try {
Intent intent = new Intent("android.intent.action.VIEW");
intent.setData(Uri.parse("market://details?id=" + context.getPackageName()));
intent.setPackage("com.android.vending");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} catch (Exception ex) {
CommonLogic.openUrl(activity, "https://play.google.com/store/apps/details?id=" + context.getPackageName());
}
});
}
}

View File

@ -36,6 +36,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java9.util.Objects;
import java9.util.function.Consumer;
@ -52,6 +53,8 @@ public class ModAssetsManager {
private static final String TAG = "MANAGER";
private final AtomicBoolean checkUpdating = new AtomicBoolean(false);
public ModAssetsManager(View root) {
this.root = root;
}
@ -292,9 +295,13 @@ public class ModAssetsManager {
}
public void checkModUpdate(Consumer<List<ModUpdateCheckResponseDto>> callback) {
if(checkUpdating.get()) {
return;
}
List<ModUpdateCheckRequestDto.ModInfo> list = StreamSupport.stream(findAllInstalledMods(false))
.filter(mod -> mod.getUpdateKeys() != null && !mod.getUpdateKeys().isEmpty())
.map(ModUpdateCheckRequestDto.ModInfo::fromModManifestEntry)
.distinct()
.filter(modInfo -> modInfo.getInstalledVersion() != null)
.collect(Collectors.toList());
Activity context = CommonLogic.getActivityFromView(root);
@ -302,14 +309,22 @@ public class ModAssetsManager {
if (gamePackageInfo == null) {
return;
}
checkUpdating.set(true);
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 onError(Response<List<ModUpdateCheckResponseDto>> response) {
super.onError(response);
checkUpdating.set(false);
}
@Override
public void onSuccess(Response<List<ModUpdateCheckResponseDto>> response) {
checkUpdating.set(false);
List<ModUpdateCheckResponseDto> checkResponseDtos = response.body();
if (checkResponseDtos != null) {
List<ModUpdateCheckResponseDto> list = StreamSupport.stream(checkResponseDtos).filter(dto -> dto.getSuggestedUpdate() != null).collect(Collectors.toList());
@ -318,6 +333,7 @@ public class ModAssetsManager {
}
});
} catch (Exception e) {
checkUpdating.set(false);
Crashes.trackError(e);
}
}

View File

@ -47,11 +47,7 @@ public class AboutFragment extends Fragment {
@OnClick(R.id.button_gplay)
void gplay() {
try {
CommonLogic.doOnNonNull(this.getActivity(), (activity) -> this.openPlayStore("market://details?id=" + activity.getPackageName()));
} catch (Exception ex) {
CommonLogic.doOnNonNull(this.getActivity(), (activity) -> CommonLogic.openUrl(activity, "https://play.google.com/store/apps/details?id=" + activity.getPackageName()));
}
CommonLogic.openInPlayStore(this.getActivity());
}
private void openPlayStore(String url) {

View File

@ -50,15 +50,14 @@ public class ConfigEditFragment extends Fragment {
View root = inflater.inflate(R.layout.fragment_config_edit, container, false);
ButterKnife.bind(this, root);
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);
}
if (configPath != null) {
ConfigEditFragmentArgs args = ConfigEditFragmentArgs.fromBundle(arguments);
editable = args.getEditable();
if (!editable) {
editText.setKeyListener(null);
buttonConfigSave.setVisibility(View.INVISIBLE);
buttonConfigCancel.setVisibility(View.INVISIBLE);
}
configPath = args.getConfigPath();
File file = new File(configPath);
if (file.exists() && file.length() < Constants.TEXT_FILE_OPEN_SIZE_LIMIT) {
String fileText = FileUtils.getFileText(file);
@ -87,7 +86,7 @@ public class ConfigEditFragment extends Fragment {
onConfigCancel();
}));
}
}
});
return root;
}

View File

@ -3,38 +3,48 @@ package com.zane.smapiinstaller.ui.update;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimaps;
import com.zane.smapiinstaller.R;
import com.zane.smapiinstaller.dto.ModUpdateCheckResponseDto;
import com.zane.smapiinstaller.entity.ModManifestEntry;
import com.zane.smapiinstaller.logic.CommonLogic;
import com.zane.smapiinstaller.logic.ModAssetsManager;
import com.zane.smapiinstaller.utils.VersionUtil;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import java9.util.Optional;
import java9.util.stream.StreamSupport;
/**
* {@link RecyclerView.Adapter} that can display a {@link ModUpdateCheckResponseDto.UpdateInfo}
*
* @author Zane
*/
public class ModUpdateAdapter extends RecyclerView.Adapter<ModUpdateAdapter.ViewHolder> {
private List<ModUpdateCheckResponseDto.UpdateInfo> updateInfoList;
private final ImmutableListMultimap<String, ModManifestEntry> installedModMap;
public void setUpdateInfoList(List<ModUpdateCheckResponseDto.UpdateInfo> updateInfoList) {
this.updateInfoList = updateInfoList;
notifyDataSetChanged();
}
private List<ModUpdateCheckResponseDto> updateInfoList;
public ModUpdateAdapter(List<ModUpdateCheckResponseDto.UpdateInfo> items) {
public ModUpdateAdapter(List<ModUpdateCheckResponseDto> items) {
updateInfoList = items;
installedModMap = Multimaps.index(ModAssetsManager.findAllInstalledMods(), ModManifestEntry::getUniqueID);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.download_content_item, parent, false);
.inflate(R.layout.updatable_mod_list_item, parent, false);
return new ViewHolder(view);
}
@ -48,16 +58,35 @@ public class ModUpdateAdapter extends RecyclerView.Adapter<ModUpdateAdapter.View
return updateInfoList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
public ModUpdateCheckResponseDto.UpdateInfo updateInfo;
class ViewHolder extends RecyclerView.ViewHolder {
public ModUpdateCheckResponseDto updateInfo;
@BindView(R.id.text_view_mod_name)
TextView textModName;
@BindView(R.id.text_view_mod_version)
TextView textModVersion;
public void setUpdateInfo(ModUpdateCheckResponseDto.UpdateInfo updateInfo) {
public void setUpdateInfo(ModUpdateCheckResponseDto updateInfo) {
this.updateInfo = updateInfo;
String id = updateInfo.getId();
Optional<ModManifestEntry> mod = StreamSupport.stream(installedModMap.get(id)).sorted((a, b) -> VersionUtil.compareVersion(a.getVersion(), b.getVersion())).findFirst();
if (mod.isPresent()) {
ModManifestEntry modManifestEntry = mod.get();
textModName.setText(modManifestEntry.getName());
CommonLogic.doOnNonNull(CommonLogic.getActivityFromView(textModName),
activity -> textModVersion.setText(
activity.getString(R.string.mod_version_update_text, modManifestEntry.getVersion(), updateInfo.getSuggestedUpdate().getVersion())
));
}
}
public ViewHolder(View view) {
super(view);
ButterKnife.bind(this, itemView);
}
@OnClick(R.id.button_update_mod)
void onUpdateClick() {
CommonLogic.doOnNonNull(CommonLogic.getActivityFromView(textModName), context -> CommonLogic.openUrl(context, updateInfo.getSuggestedUpdate().getUrl()));
}
}
}

View File

@ -42,9 +42,10 @@ public class ModUpdateFragment extends Fragment {
Context context = view.getContext();
RecyclerView recyclerView = (RecyclerView) view;
recyclerView.setLayoutManager(new LinearLayoutManager(context));
CommonLogic.doOnNonNull(this.getArguments(), arguments -> {
String updateInfoListJson = arguments.getString("updateInfoListJson", "[]");
List<ModUpdateCheckResponseDto.UpdateInfo> updateInfos = JSONUtil.fromJson(updateInfoListJson, new TypeReference<List<ModUpdateCheckResponseDto.UpdateInfo>>() {
String updateInfoListJson = ModUpdateFragmentArgs.fromBundle(arguments).getUpdateInfoListJson();
List<ModUpdateCheckResponseDto> updateInfos = JSONUtil.fromJson(updateInfoListJson, new TypeReference<List<ModUpdateCheckResponseDto>>() {
});
ModUpdateAdapter adapter = new ModUpdateAdapter(updateInfos);
recyclerView.setAdapter(adapter);

View File

@ -3,7 +3,6 @@ package com.zane.smapiinstaller.utils;
import android.app.Activity;
import android.text.InputType;
import android.view.View;
import android.widget.ProgressBar;
import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.input.DialogInputExtKt;
@ -188,20 +187,20 @@ public class DialogUtils {
* 解散当前对话框
*/
public static void dismissDialog() {
if (currentDialog != null) {
if (currentDialog instanceof MaterialDialog) {
MaterialDialog dialog = (MaterialDialog) currentDialog;
if (dialog.isShowing()) {
try {
try {
if (currentDialog != null) {
if (currentDialog instanceof MaterialDialog) {
MaterialDialog dialog = (MaterialDialog) currentDialog;
if (dialog.isShowing()) {
dialog.dismiss();
} catch (Exception e) {
Crashes.trackError(e);
}
} else if (currentDialog instanceof ProgressDialog) {
ProgressDialog dialog = (ProgressDialog) currentDialog;
dialog.dismiss();
}
} else if (currentDialog instanceof ProgressDialog) {
ProgressDialog dialog = (ProgressDialog) currentDialog;
dialog.dismiss();
}
} catch (Exception e) {
Crashes.trackError(e);
}
}

View File

@ -1,6 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@drawable/horizontal_divider"
android:orientation="vertical"
android:showDividers="middle">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/text_view_mod_name"
android:layout_width="0px"
android:layout_height="wrap_content"
android:gravity="start"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/button_update_mod"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_update_mod"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/global"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/text_view_mod_name"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/text_view_mod_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp" />
</LinearLayout>

View File

@ -17,7 +17,10 @@
<fragment
android:id="@+id/config_edit_fragment"
android:name="com.zane.smapiinstaller.ui.config.ConfigEditFragment"
android:label="@string/menu_config_edit" />
android:label="@string/menu_config_edit" >
<argument android:name="configPath" app:argType="string"/>
<argument android:name="editable" android:defaultValue="true" app:argType="boolean"/>
</fragment>
<fragment
android:id="@+id/nav_config"
android:name="com.zane.smapiinstaller.ui.config.ConfigFragment"
@ -58,6 +61,10 @@
android:name="com.zane.smapiinstaller.ui.update.ModUpdateFragment"
android:label="@string/settings_check_for_updates"
tools:layout="@layout/fragment_mod_update_list">
<argument
android:name="updateInfoListJson"
android:defaultValue="{}"
app:argType="string" />
</fragment>
<action
android:id="@+id/action_nav_any_to_mod_update_fragment"

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="donation_methods">
<item>支付寶</item>
<item>微信</item>
<item>QQ</item>
<item>紅包碼</item>
<item>捐贈列表</item>
</string-array>
<string-array name="languages">
<item>自動</item>
<item>English</item>
<item>简体中文</item>
<item>繁體中文</item>
<item>한국어</item>
<item>ไทย</item>
<item>Español</item>
<item>Français</item>
<item>Português</item>
<item>Bahasa Indonesia</item>
</string-array>
<string-array name="translators">
<item>關閉</item>
<item>谷歌</item>
<item>有道</item>
</string-array>
<string-array name="mod_list_sort_by">
<item>名稱(正序)</item>
<item>名稱(倒序)</item>
<item>日期(正序)</item>
<item>日期(倒序)</item>
</string-array>
</resources>

View File

@ -1,70 +0,0 @@
<resources>
<string name="abort">中止</string>
<string name="android_version_confirm">你的系統版本過老可能會導致0Harmony無效建議升級到安卓6及以上版本</string>
<string name="app_name">SMAPI安裝器</string>
<string name="button_compat">兼容性</string>
<string name="button_donation_text">捐贈</string>
<string name="button_gplay" >谷歌商店详情</string>
<string name="button_install">安裝</string>
<string name="button_logs">日誌</string>
<string name="button_nexus">N網</string>
<string name="button_qq_group_1_text">QQ群①: 860453392</string>
<string name="button_qq_group_2_text">QQ群②: 1078428449</string>
<string name="button_release">官網</string>
<string name="cancel">取消</string>
<string name="confirm">確認</string>
<string name="confirm_delete_content">確定刪除該內容?</string>
<string name="confirm_disable_mod">確定禁用該內容?</string>
<string name="continue_text">繼續</string>
<string name="download_unpack_success">已完成下載安裝</string>
<string name="downloading">正在下載: %1$d KB / %2$d KB</string>
<string name="duplicate_mod_found">發現該MOD存在多份拷貝請從以下位置刪除重複MOD%s</string>
<string name="error">錯誤</string>
<string name="error_depends_on_mod">%1$s依賴%2$s前置請先安裝它</string>
<string name="error_depends_on_mod_version" >%1$s依賴%2$s %3$s版本請先更新它</string>
<string name="error_failed_to_create_file">無法創建以下文件: %s</string>
<string name="error_failed_to_download">無法下載目標資源</string>
<string name="error_failed_to_repair">無法修復SMAPI環境</string>
<string name="error_game_not_found">無法找到遊戲本體,你是否安裝了星露穀物語?</string>
<string name="error_illegal_path">請輸入一個有效的路徑</string>
<string name="error_no_supported_game_version">遊戲版本不支持,請更新版本或者下載兼容包</string>
<string name="error_smapi_not_installed">SMAPI尚未安裝請先點擊安裝按鈕</string>
<string name="extracting_package">正在抽取安裝包</string>
<string name="failed_to_patch_game">無法修改安裝包,請聯繫開發者獲取幫助</string>
<string name="failed_to_process_manifest">無法處理AndroidManifest.xml文件</string>
<string name="failed_to_sign_game">無法簽名安裝包,請聯繫開發者獲取幫助</string>
<string name="failed_to_unpack_smapi_files">無法解包SMAPI環境</string>
<string name="info">提示</string>
<string name="input">輸入</string>
<string name="input_mods_path">請輸入Mods路徑</string>
<string name="install_progress_title">安裝進度</string>
<string name="installing_package">正在安裝</string>
<string name="locale_pack">語言包</string>
<string name="menu_about">關於</string>
<string name="menu_config">配置</string>
<string name="menu_config_edit">編輯</string>
<string name="menu_download">下載</string>
<string name="menu_help">幫助</string>
<string name="menu_install">安裝</string>
<string name="nav_header_subtitle">ZaneYork@qq.com</string>
<string name="nav_header_title">SMAPI安裝器</string>
<string name="ok">OK</string>
<string name="patching_package">正在安裝SMAPI補丁</string>
<string name="progress">進度</string>
<string name="save">保存</string>
<string name="settings_check_for_updates">檢查更新</string>
<string name="settings_developer_mode">開發者模式</string>
<string name="settings_set_language">語言</string>
<string name="settings_set_mod_path">設置Mods位置</string>
<string name="settings_translation_service">翻譯服務</string>
<string name="settings_verbose_logging">詳細日誌</string>
<string name="signing_package">正在簽名安裝包</string>
<string name="smapi_game_name">SMAPI星露穀物語</string>
<string name="smapi_version">SMAPI版本: 3.4.1.1</string>
<string name="text_install_tip1">注意需要不低於1.4.5.138版本的遊戲本體</string>
<string name="text_install_tip2">更新或安裝期間需要安裝遊戲本體</string>
<string name="unpacking_smapi_files">正在解包</string>
<string name="sort_by">排序方式</string>
<string name="text_too_large">文本文件過大,無法打開</string>
<string name="open_with">打開為</string>
</resources>

View File

@ -67,4 +67,6 @@
<string name="sort_by">排序方式</string>
<string name="text_too_large">文本文件過大,無法打開</string>
<string name="open_with">打開為</string>
<string name="no_update_text">所有MOD已是最新狀態</string>
<string name="app_update_detected">檢測到新版本: %1$s</string>
</resources>

View File

@ -67,4 +67,6 @@
<string name="sort_by">排序方式</string>
<string name="text_too_large">文本文件过大,无法打开</string>
<string name="open_with">打开为</string>
<string name="no_update_text">所有MOD已是最新状态</string>
<string name="app_update_detected">检测到新版本: %1$s</string>
</resources>

View File

@ -67,7 +67,10 @@
<string name="toast_redpacket_message" translatable="false">红包码已复制\n支付宝首页搜索“9188262” 立即领红包</string>
<string name="icon_desc" translatable="false">icon</string>
<string name="mod_version_update_text" translatable="false">%1$s -> %2$s</string>
<string name="sort_by">Sort by</string>
<string name="text_too_large">Text file is too large for editor</string>
<string name="open_with">Open With</string>
<string name="no_update_text">All mods are up to date</string>
<string name="app_update_detected">New version detected: %1$s</string>
</resources>