diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..a1757ae --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 73a4c41..77b8385 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,6 +47,7 @@ dependencies { implementation 'com.afollestad.material-dialogs:core:0.9.6.0' // https://mvnrepository.com/artifact/com.jakewharton/butterknife implementation 'com.jakewharton:butterknife:10.2.1' + implementation 'androidx.recyclerview:recyclerview:1.1.0' annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1' // https://mvnrepository.com/artifact/com.google.guava/guava implementation group: 'com.google.guava', name: 'guava', version: '28.2-android' @@ -54,9 +55,15 @@ dependencies { implementation group: 'org.zeroturnaround', name: 'zt-zip', version: '1.14' // https://mvnrepository.com/artifact/com.google.code.gson/gson implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6' + // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 + implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.9' + implementation 'com.lzy.net:okgo:3.0.4' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation 'com.android.support:multidex:1.0.3' + + compileOnly 'org.projectlombok:lombok:1.18.12' + annotationProcessor 'org.projectlombok:lombok:1.18.12' } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 1e7caf4..c4d602b 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -146,6 +146,13 @@ } ##---------------End: proguard configuration for Gson ---------- +#okhttp +-dontwarn okhttp3.** +-keep class okhttp3.**{*;} + +#okio +-dontwarn okio.** +-keep class okio.**{*;} -keep class com.zane.** { *; } -keep class pxb.android.** { *; } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 704abfe..e038f03 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,12 +2,14 @@ + manifestEntries; +} diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContent.java b/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContent.java new file mode 100644 index 0000000..000bd78 --- /dev/null +++ b/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContent.java @@ -0,0 +1,11 @@ +package com.zane.smapiinstaller.entity; + +import lombok.Data; + +@Data +public class DownloadableContent { + private String type; + private String name; + private String url; + private String description; +} diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContentList.java b/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContentList.java new file mode 100644 index 0000000..2dbe167 --- /dev/null +++ b/app/src/main/java/com/zane/smapiinstaller/entity/DownloadableContentList.java @@ -0,0 +1,11 @@ +package com.zane.smapiinstaller.entity; + +import java.util.List; + +import lombok.Data; + +@Data +public class DownloadableContentList { + private int version; + List contents; +} diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java b/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java index 99ffe6d..df8c1f1 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/ManifestEntry.java @@ -1,40 +1,11 @@ package com.zane.smapiinstaller.entity; +import lombok.Data; + +@Data public class ManifestEntry { private String targetPath; private String assetPath; private int compression; private int origin; - - public String getTargetPath() { - return targetPath; - } - - public void setTargetPath(String targetPath) { - this.targetPath = targetPath; - } - - public String getAssetPath() { - return assetPath; - } - - public void setAssetPath(String assetPath) { - this.assetPath = assetPath; - } - - public int getCompression() { - return compression; - } - - public void setCompression(int compression) { - this.compression = compression; - } - - public int getOrigin() { - return origin; - } - - public void setOrigin(int origin) { - this.origin = origin; - } } diff --git a/app/src/main/java/com/zane/smapiinstaller/entity/ModManifestEntry.java b/app/src/main/java/com/zane/smapiinstaller/entity/ModManifestEntry.java index 59dc137..93cff1d 100644 --- a/app/src/main/java/com/zane/smapiinstaller/entity/ModManifestEntry.java +++ b/app/src/main/java/com/zane/smapiinstaller/entity/ModManifestEntry.java @@ -1,41 +1,12 @@ package com.zane.smapiinstaller.entity; +import lombok.Data; + +@Data public class ModManifestEntry { private String assetPath; private String Name; private String UniqueID; private String Description; private ModManifestEntry ContentPackFor; - - public String getAssetPath() { - return assetPath; - } - - public void setAssetPath(String assetPath) { - this.assetPath = assetPath; - } - - public String getName() { - return Name; - } - - public void setName(String name) { - Name = name; - } - - public String getUniqueID() { - return UniqueID; - } - - public void setUniqueID(String uniqueID) { - UniqueID = uniqueID; - } - - public String getDescription() { - return Description; - } - - public void setDescription(String description) { - Description = description; - } } diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java b/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java index 84e36f5..f5f4bcf 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/ApkPatcher.java @@ -11,16 +11,20 @@ import android.os.Environment; import android.util.Log; import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.io.Files; import com.google.gson.reflect.TypeToken; import com.zane.smapiinstaller.BuildConfig; import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.constant.Constants; +import com.zane.smapiinstaller.entity.ApkFilesManifest; import com.zane.smapiinstaller.entity.ManifestEntry; import net.fornwall.apksigner.KeyStoreFileManager; import net.fornwall.apksigner.ZipSigner; +import org.apache.commons.lang3.StringUtils; import org.zeroturnaround.zip.ByteSource; import org.zeroturnaround.zip.ZipEntrySource; import org.zeroturnaround.zip.ZipUtil; @@ -29,7 +33,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -48,6 +51,8 @@ public class ApkPatcher { private static final String TAG = "PATCHER"; + private AtomicReference errorMessage = new AtomicReference<>(); + public ApkPatcher(Context context) { this.context = context; } @@ -56,8 +61,10 @@ public class ApkPatcher { PackageManager packageManager = context.getPackageManager(); List packageNames = CommonLogic.getAssetJson(context, "package_names.json", new TypeToken>() { }.getType()); - if (packageNames == null) + if (packageNames == null) { + errorMessage.set(context.getString(R.string.error_game_not_found)); return null; + } for (String packageName : packageNames) { try { PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0); @@ -69,6 +76,7 @@ public class ApkPatcher { File dest = new File(externalFilesDir.getAbsolutePath() + "/SMAPI Installer/"); if (!dest.exists()) { if (!dest.mkdir()) { + errorMessage.set(String.format(context.getString(R.string.error_failed_to_create_file), dest.getAbsolutePath())); return null; } } @@ -78,6 +86,7 @@ public class ApkPatcher { } } catch (PackageManager.NameNotFoundException | IOException e) { Log.e(TAG, "Extract error", e); + errorMessage.set(e.getLocalizedMessage()); } } return null; @@ -90,29 +99,36 @@ public class ApkPatcher { if (!file.exists()) return false; try { - List manifestEntries = CommonLogic.getAssetJson(context, "apk_files_manifest.json", new TypeToken>() { - }.getType()); - if (manifestEntries == null) + ApkFilesManifest apkFilesManifest = CommonLogic.getAssetJson(context, "apk_files_manifest.json", ApkFilesManifest.class); + if (apkFilesManifest == null) return false; + List manifestEntries = apkFilesManifest.getManifestEntries(); List zipEntrySourceList = new ArrayList<>(); - for (ManifestEntry entry : manifestEntries) { - zipEntrySourceList.add(new ByteSource(entry.getTargetPath(), CommonLogic.getAssetBytes(context, entry.getAssetPath()), entry.getCompression())); - } byte[] manifest = ZipUtil.unpackEntry(file, "AndroidManifest.xml"); - byte[] modifiedManifest = modifyManifest(manifest); + List apkFilesManifests = Lists.newArrayList(apkFilesManifest); + byte[] modifiedManifest = modifyManifest(manifest, apkFilesManifests); + if(apkFilesManifests.size() == 0) { + errorMessage.set(context.getString(R.string.error_no_supported_game_version)); + return false; + } if(modifiedManifest == null) { + errorMessage.set(context.getString(R.string.failed_to_process_manifest)); return false; } zipEntrySourceList.add(new ByteSource("AndroidManifest.xml", modifiedManifest, Deflater.DEFLATED)); + for (ManifestEntry entry : manifestEntries) { + zipEntrySourceList.add(new ByteSource(entry.getTargetPath(), CommonLogic.getAssetBytes(context, entry.getAssetPath()), entry.getCompression())); + } ZipUtil.addOrReplaceEntries(file, zipEntrySourceList.toArray(new ZipEntrySource[0])); return true; } catch (Exception e) { Log.e(TAG, "Patch error", e); + errorMessage.set(e.getLocalizedMessage()); } return false; } - private byte[] modifyManifest(byte[] bytes) { + private byte[] modifyManifest(byte[] bytes, List manifests) { AtomicReference packageName = new AtomicReference<>(); Predicate processLogic = (attr) -> { if (attr.type == NodeVisitor.TYPE_STRING) { @@ -140,9 +156,32 @@ public class ApkPatcher { break; } } + else if(attr.type == NodeVisitor.TYPE_FIRST_INT) { + if(StringUtils.equals(attr.name, "versionCode")){ + long versionCode = (int) attr.obj; + Iterables.removeIf(manifests, manifest -> { + if (manifest.getMinBuildCode() != null) { + if (versionCode < manifest.getMinBuildCode()) { + return true; + } + } + if (manifest.getMaxBuildCode() != null) { + if (versionCode > manifest.getMinBuildCode()) { + return true; + } + } + return false; + }); + } + } return true; }; - return CommonLogic.modifyManifest(bytes, processLogic); + try { + return CommonLogic.modifyManifest(bytes, processLogic); + }catch (Exception e) { + errorMessage.set(e.getLocalizedMessage()); + return null; + } } public String sign(String apkPath) { @@ -156,16 +195,14 @@ public class ApkPatcher { } String alias = ks.aliases().nextElement(); X509Certificate publicKey = (X509Certificate) ks.getCertificate(alias); - try { - PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "android".toCharArray()); - ZipSigner.signZip(publicKey, privateKey, "SHA1withRSA", apkPath, signApkPath); - new File(apkPath).delete(); - return signApkPath; - } catch (NoSuchAlgorithmException ignored) { - } + PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "android".toCharArray()); + ZipSigner.signZip(publicKey, privateKey, "SHA1withRSA", apkPath, signApkPath); + new File(apkPath).delete(); + return signApkPath; } } catch (Exception e) { Log.e(TAG, "Sign error", e); + errorMessage.set(e.getLocalizedMessage()); } return null; } @@ -179,6 +216,7 @@ public class ApkPatcher { context.startActivity(intent); } catch (ActivityNotFoundException e) { Log.e(TAG, "Install error", e); + errorMessage.set(e.getLocalizedMessage()); } } @@ -196,4 +234,7 @@ public class ApkPatcher { return Uri.fromFile(file); } + public AtomicReference getErrorMessage() { + return errorMessage; + } } diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java b/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java index fcd6dfc..090a2cc 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/CommonLogic.java @@ -59,6 +59,17 @@ public class CommonLogic { return null; } + public static T getAssetJson(Context context, String filename, Class tClass) { + try { + InputStream inputStream = context.getAssets().open(filename); + try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + return new Gson().fromJson(CharStreams.toString(reader), tClass); + } + } catch (IOException ignored) { + } + return null; + } + public static T getAssetJson(Context context, String filename, Type type) { try { InputStream inputStream = context.getAssets().open(filename); @@ -177,25 +188,16 @@ public class CommonLogic { context.startActivity(intent); } - public static byte[] modifyManifest(byte[] bytes, Predicate processLogic) { + public static byte[] modifyManifest(byte[] bytes, Predicate processLogic) throws IOException { AxmlReader reader = new AxmlReader(bytes); AxmlWriter writer = new AxmlWriter(); - try { - reader.accept(new AxmlVisitor(writer) { - @Override - public NodeVisitor child(String ns, String name) { - NodeVisitor child = super.child(ns, name); - return new ManifestTagVisitor(child, processLogic); - } - }); - } catch (IOException e) { - e.printStackTrace(); - } - try { - return writer.toByteArray(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; + reader.accept(new AxmlVisitor(writer) { + @Override + public NodeVisitor child(String ns, String name) { + NodeVisitor child = super.child(ns, name); + return new ManifestTagVisitor(child, processLogic); + } + }); + return writer.toByteArray(); } } diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/GameLauncher.java b/app/src/main/java/com/zane/smapiinstaller/logic/GameLauncher.java index 9a391b7..75edeb4 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/GameLauncher.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/GameLauncher.java @@ -26,8 +26,11 @@ public class GameLauncher { CommonLogic.showAlertDialog(root, R.string.error, R.string.error_failed_to_repair); return; } - Intent intent = packageManager.getLaunchIntentForPackage(Constants.TARGET_PACKAGE_NAME); - context.startActivity(intent); + ModAssetsManager modAssetsManager = new ModAssetsManager(root); + if(modAssetsManager.checkModEnvironment()) { + Intent intent = packageManager.getLaunchIntentForPackage(Constants.TARGET_PACKAGE_NAME); + context.startActivity(intent); + } } catch (PackageManager.NameNotFoundException ignored) { CommonLogic.showAlertDialog(root, R.string.error, R.string.error_smapi_not_installed); } diff --git a/app/src/main/java/com/zane/smapiinstaller/logic/ModAssetsManager.java b/app/src/main/java/com/zane/smapiinstaller/logic/ModAssetsManager.java index 92711f6..5ea631d 100644 --- a/app/src/main/java/com/zane/smapiinstaller/logic/ModAssetsManager.java +++ b/app/src/main/java/com/zane/smapiinstaller/logic/ModAssetsManager.java @@ -16,6 +16,8 @@ import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.constant.Constants; import com.zane.smapiinstaller.entity.ModManifestEntry; +import org.apache.commons.lang3.StringUtils; +import org.zeroturnaround.zip.NameMapper; import org.zeroturnaround.zip.ZipUtil; import java.io.File; @@ -23,6 +25,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicLong; public class ModAssetsManager { @@ -43,7 +46,7 @@ public class ModAssetsManager { if(currentFile != null && currentFile.exists()) { boolean foundManifest = false; for(File file : currentFile.listFiles(File::isFile)) { - if(file.getName().equalsIgnoreCase("manifest.json")) { + if(StringUtils.equalsIgnoreCase(file.getName(), "manifest.json")) { ModManifestEntry manifest = CommonLogic.getFileJson(file, new TypeToken(){}.getType()); foundManifest = true; if(manifest != null) { @@ -70,7 +73,7 @@ public class ModAssetsManager { File modFolder = new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH); ImmutableListMultimap installedModMap = Multimaps.index(findAllInstalledMods(), ModManifestEntry::getUniqueID); for (ModManifestEntry mod : modManifestEntries) { - if(installedModMap.containsKey(mod.getUniqueID())) { + if(installedModMap.containsKey(mod.getUniqueID()) || installedModMap.containsKey(mod.getUniqueID().replace("ZaneYork.CustomLocalization", "SMAPI.CustomLocalization"))) { ImmutableList installedMods = installedModMap.get(mod.getUniqueID()); if(installedMods.size() > 1) { CommonLogic.showAlertDialog(root, R.string.error, @@ -79,7 +82,7 @@ public class ModAssetsManager { return false; } try { - ZipUtil.unpackEntry(context.getAssets().open(mod.getAssetPath()), mod.getName(), new File(installedMods.get(0).getAssetPath())); + ZipUtil.unpack(context.getAssets().open(mod.getAssetPath()), new File(installedMods.get(0).getAssetPath()), (name)-> StringUtils.removeStart(name, mod.getName() + "/")); } catch (IOException e) { Log.e(TAG, "Install Mod Error", e); } @@ -94,4 +97,8 @@ public class ModAssetsManager { } return true; } + + public boolean checkModEnvironment() { + return true; + } } diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigEditFragment.java b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigEditFragment.java index 226562e..f2427c9 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigEditFragment.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigEditFragment.java @@ -61,7 +61,7 @@ public class ConfigEditFragment extends Fragment { } } catch (Exception e) { - CommonLogic.showAlertDialog(getView(), R.string.error, e.getMessage()); + CommonLogic.showAlertDialog(getView(), R.string.error, e.getLocalizedMessage()); } } diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigFragment.java b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigFragment.java index 9f3885f..8805734 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigFragment.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigFragment.java @@ -17,7 +17,6 @@ import com.zane.smapiinstaller.R; public class ConfigFragment extends Fragment { - private ConfigViewModel configViewModel; @BindView(R.id.view_mod_list) RecyclerView recyclerView; @@ -26,13 +25,11 @@ public class ConfigFragment extends Fragment { ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_config, container, false); ButterKnife.bind(this, root); - configViewModel = new ConfigViewModel(root); recyclerView.setLayoutManager(new LinearLayoutManager(this.getContext())); - recyclerView.setAdapter(new ModManifestAdapter(configViewModel.getModList().getValue())); - recyclerView.addItemDecoration(new DividerItemDecoration(this.getContext(), DividerItemDecoration.VERTICAL)); - configViewModel.getModList().observe(getViewLifecycleOwner(), modList -> { - recyclerView.getAdapter().notifyDataSetChanged(); - }); + ConfigViewModel configViewModel = new ConfigViewModel(root); + ModManifestAdapter modManifestAdapter = new ModManifestAdapter(configViewModel); + recyclerView.setAdapter(modManifestAdapter); + recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL)); return root; } } diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigViewModel.java b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigViewModel.java index e5361fb..01a4430 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigViewModel.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/config/ConfigViewModel.java @@ -1,29 +1,55 @@ package com.zane.smapiinstaller.ui.config; +import android.os.FileObserver; import android.view.View; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.zane.smapiinstaller.entity.ModManifestEntry; import com.zane.smapiinstaller.logic.ModAssetsManager; +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import androidx.annotation.NonNull; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import androidx.recyclerview.widget.RecyclerView; class ConfigViewModel extends ViewModel { - private MutableLiveData> modList; + @NonNull + private List modList; - public ConfigViewModel(View root) { + private RecyclerView view; + + ConfigViewModel(View root) { ModAssetsManager manager = new ModAssetsManager(root); - this.modList = new MutableLiveData<>(); - List entryList = manager.findAllInstalledMods(); - Collections.sort(entryList, (a, b)-> a.getName().compareTo(b.getName())); - this.modList.setValue(entryList); + this.modList = manager.findAllInstalledMods(); + Collections.sort(this.modList, (a, b)-> a.getName().compareTo(b.getName())); } - public MutableLiveData> getModList() { + @NonNull + public List getModList() { return modList; } + + public List removeAll(Predicate predicate) { + List deletedId = new ArrayList<>(); + for (int i = modList.size() - 1; i >=0 ; i--) { + if(predicate.apply(modList.get(i))) { + modList.remove(i); + deletedId.add(i); + } + } + return deletedId; + } + } \ No newline at end of file diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/config/ModManifestAdapter.java b/app/src/main/java/com/zane/smapiinstaller/ui/config/ModManifestAdapter.java index 228b5f1..1fb8f9d 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/config/ModManifestAdapter.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/config/ModManifestAdapter.java @@ -7,11 +7,16 @@ import android.widget.Button; import android.widget.TextView; import com.afollestad.materialdialogs.DialogAction; +import com.google.common.collect.Iterables; import com.zane.smapiinstaller.R; import com.zane.smapiinstaller.entity.ModManifestEntry; import com.zane.smapiinstaller.logic.CommonLogic; +import org.apache.commons.lang3.StringUtils; +import org.zeroturnaround.zip.commons.FileUtils; + import java.io.File; +import java.io.IOException; import java.util.List; import androidx.annotation.NonNull; @@ -23,21 +28,22 @@ import butterknife.ButterKnife; import butterknife.OnClick; public class ModManifestAdapter extends RecyclerView.Adapter { - private List modList; + private ConfigViewModel model; - public ModManifestAdapter(List modList){ - this.modList=modList; + public ModManifestAdapter(ConfigViewModel model){ + this.model=model; } + @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.mod_list_item,null); + View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.mod_list_item,parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - ModManifestEntry mod = modList.get(position); + ModManifestEntry mod = model.getModList().get(position); holder.modName.setText(mod.getName()); holder.modDescription.setText(mod.getDescription()); holder.setModPath(mod.getAssetPath()); @@ -45,10 +51,10 @@ public class ModManifestAdapter extends RecyclerView.Adapter removed = model.removeAll(entry -> StringUtils.equals(entry.getAssetPath(), modPath)); + for (int idx : removed) { + notifyItemRemoved(idx); + } + } catch (IOException e) { + CommonLogic.showAlertDialog(itemView, R.string.error, e.getMessage()); + } } } }); diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/download/DownloadableContentAdapter.java b/app/src/main/java/com/zane/smapiinstaller/ui/download/DownloadableContentAdapter.java new file mode 100644 index 0000000..725eede --- /dev/null +++ b/app/src/main/java/com/zane/smapiinstaller/ui/download/DownloadableContentAdapter.java @@ -0,0 +1,69 @@ +package com.zane.smapiinstaller.ui.download; + +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +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.DownloadableContent; + +import java.util.List; + +/** + * {@link RecyclerView.Adapter} that can display a {@link DownloadableContent} + */ +public class DownloadableContentAdapter extends RecyclerView.Adapter { + + private final List downloadableContentList; + + public DownloadableContentAdapter(List items) { + downloadableContentList = items; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.download_content_item, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int position) { + holder.downloadableContent = downloadableContentList.get(position); + holder.typeTextView.setText(holder.downloadableContent.getType()); + holder.nameTextView.setText(holder.downloadableContent.getName()); + holder.descriptionTextView.setText(holder.downloadableContent.getDescription()); + } + + @Override + public int getItemCount() { + return downloadableContentList.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.text_item_type) + TextView typeTextView; + @BindView(R.id.text_item_name) + TextView nameTextView; + @BindView(R.id.text_item_description) + TextView descriptionTextView; + public DownloadableContent downloadableContent; + + public ViewHolder(View view) { + super(view); + ButterKnife.bind(this, itemView); + } + @OnClick(R.id.button_remove_content) void removeContent() { + + } + @OnClick(R.id.button_download_content) void downloadContent() { + + } + } +} diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/download/DownloadableContentFragment.java b/app/src/main/java/com/zane/smapiinstaller/ui/download/DownloadableContentFragment.java new file mode 100644 index 0000000..57ec392 --- /dev/null +++ b/app/src/main/java/com/zane/smapiinstaller/ui/download/DownloadableContentFragment.java @@ -0,0 +1,47 @@ +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.entity.DownloadableContentList; +import com.zane.smapiinstaller.logic.CommonLogic; + +/** + * A fragment representing a list of Items. + */ +public class DownloadableContentFragment extends Fragment { + + /** + * Mandatory empty constructor for the fragment manager to instantiate the + * fragment (e.g. upon screen orientation changes). + */ + public DownloadableContentFragment() { + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_download_content_list, container, false); + + // Set the adapter + if (view instanceof RecyclerView) { + Context context = view.getContext(); + RecyclerView recyclerView = (RecyclerView) view; + recyclerView.setLayoutManager(new LinearLayoutManager(context)); + DownloadableContentList contentList = CommonLogic.getAssetJson(context, "downloadable_content_list.json", DownloadableContentList.class); + recyclerView.setAdapter(new DownloadableContentAdapter(contentList.getContents())); + recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL)); + } + return view; + } +} diff --git a/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java b/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java index 5f2dd32..25ec3cd 100644 --- a/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java +++ b/app/src/main/java/com/zane/smapiinstaller/ui/install/InstallFragment.java @@ -13,6 +13,8 @@ import com.zane.smapiinstaller.logic.ApkPatcher; import com.zane.smapiinstaller.logic.CommonLogic; import com.zane.smapiinstaller.logic.ModAssetsManager; +import org.apache.commons.lang3.StringUtils; + import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import butterknife.ButterKnife; @@ -52,12 +54,12 @@ public class InstallFragment extends Fragment { CommonLogic.setProgressDialogState(root, dialog, R.string.extracting_package, 0); String path = patcher.extract(); if (path == null) { - CommonLogic.showAlertDialog(root, R.string.error, R.string.error_game_not_found); + CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found))); return; } CommonLogic.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 10); if (!CommonLogic.unpackSmapiFiles(context, path, false)) { - CommonLogic.showAlertDialog(root, R.string.error, R.string.failed_to_unpack_smapi_files); + CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files))); return; } ModAssetsManager modAssetsManager = new ModAssetsManager(root); @@ -65,13 +67,13 @@ public class InstallFragment extends Fragment { modAssetsManager.installDefaultMods(); CommonLogic.setProgressDialogState(root, dialog, R.string.patching_package, 25); if (!patcher.patch(path)) { - CommonLogic.showAlertDialog(root, R.string.error, R.string.failed_to_patch_game); + CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_patch_game))); return; } CommonLogic.setProgressDialogState(root, dialog, R.string.signing_package, 55); String signPath = patcher.sign(path); if (signPath == null) { - CommonLogic.showAlertDialog(root, R.string.error, R.string.failed_to_sign_game); + CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game))); return; } CommonLogic.setProgressDialogState(root, dialog, R.string.installing_package, 99); diff --git a/app/src/main/res/drawable/divider.xml b/app/src/main/res/drawable/horizontal_divider.xml similarity index 100% rename from app/src/main/res/drawable/divider.xml rename to app/src/main/res/drawable/horizontal_divider.xml diff --git a/app/src/main/res/drawable/vertical_divider.xml b/app/src/main/res/drawable/vertical_divider.xml new file mode 100644 index 0000000..5b27a81 --- /dev/null +++ b/app/src/main/res/drawable/vertical_divider.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/download_content_item.xml b/app/src/main/res/layout/download_content_item.xml new file mode 100644 index 0000000..77025c1 --- /dev/null +++ b/app/src/main/res/layout/download_content_item.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + +