mirror of
https://github.com/xoureldeen/Vectras-VM-Android.git
synced 2026-05-19 08:10:36 +00:00
commit
ff77f89609
24 changed files with 1333 additions and 1249 deletions
|
|
@ -15,8 +15,8 @@ android {
|
|||
applicationId "com.vectras.vm"
|
||||
minSdk minApi
|
||||
targetSdk targetApi
|
||||
versionCode 70
|
||||
versionName "3.6.6"
|
||||
versionCode 71
|
||||
versionName "3.6.7"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@
|
|||
android:exported="true"
|
||||
android:hardwareAccelerated="true" />
|
||||
<activity
|
||||
android:name=".QemuParamsEditorActivity"
|
||||
android:name=".creator.QemuParamsEditorActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout"
|
||||
android:exported="false"
|
||||
android:hardwareAccelerated="true"
|
||||
|
|
@ -174,7 +174,7 @@
|
|||
android:exported="true"
|
||||
android:label="Data Explorer" />
|
||||
<activity
|
||||
android:name=".SetArchActivity"
|
||||
android:name=".creator.SetArchActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout"
|
||||
android:label="Set Qemu Version" />
|
||||
|
||||
|
|
|
|||
|
|
@ -5,19 +5,22 @@ import android.app.Activity;
|
|||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.vectras.vm.databinding.ActivityExportRomBinding;
|
||||
import com.vectras.vm.main.vms.DataMainRoms;
|
||||
import com.vectras.vm.utils.DialogUtils;
|
||||
import com.vectras.vm.utils.FileUtils;
|
||||
import com.vectras.vm.utils.PackageUtils;
|
||||
|
|
@ -31,16 +34,12 @@ import java.util.Objects;
|
|||
|
||||
public class ExportRomActivity extends AppCompatActivity {
|
||||
|
||||
private final String TAG = "ExportRomActivity";
|
||||
ActivityExportRomBinding binding;
|
||||
public static int pendingPosition = 0;
|
||||
public static HashMap<String, Object> mapForGetData = new HashMap<>();
|
||||
public static ArrayList<HashMap<String, Object>> listmapForGetData = new ArrayList<>();
|
||||
private SharedPreferences data;
|
||||
public String getRomPath = "";
|
||||
public String iconfile = "";
|
||||
public String diskfile = "";
|
||||
public String cdromfile = "";
|
||||
private boolean isExporting = false;
|
||||
private ActivityResultLauncher<String> folderPicker;
|
||||
private DataMainRoms current;
|
||||
|
||||
|
||||
@Override
|
||||
|
|
@ -59,7 +58,7 @@ public class ExportRomActivity extends AppCompatActivity {
|
|||
binding.edContent.setEnabled(false);
|
||||
binding.edAuthor.setEnabled(true);
|
||||
binding.edContent.setEnabled(true);
|
||||
startCreate();
|
||||
folderPicker.launch(current.itemName + ".cvbi");
|
||||
});
|
||||
|
||||
data = getSharedPreferences("data", Activity.MODE_PRIVATE);
|
||||
|
|
@ -67,6 +66,16 @@ public class ExportRomActivity extends AppCompatActivity {
|
|||
binding.edAuthor.setText(data.getString("author", ""));
|
||||
binding.edContent.setText(data.getString("desc", ""));
|
||||
|
||||
current = VMManager.getVMConfig(getIntent().getIntExtra("POS", 0));
|
||||
|
||||
folderPicker = registerForActivityResult(
|
||||
new ActivityResultContracts.CreateDocument("application/octet-stream"),
|
||||
uri -> {
|
||||
if (uri != null) {
|
||||
startCreate(uri);
|
||||
}
|
||||
});
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
|
|
@ -87,8 +96,9 @@ public class ExportRomActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void startCreate() {
|
||||
File vDir = new File(AppConfig.cvbiFolder);
|
||||
private void startCreate(Uri uri) {
|
||||
String cvbiFolder = Objects.requireNonNull(getExternalCacheDir()).getAbsolutePath() + "/cvbi/";
|
||||
File vDir = new File(cvbiFolder);
|
||||
if (!vDir.exists()) {
|
||||
if (!vDir.mkdirs()) {
|
||||
DialogUtils.oneDialog(this,
|
||||
|
|
@ -104,90 +114,53 @@ public class ExportRomActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
listmapForGetData.clear();
|
||||
mapForGetData.clear();
|
||||
String getRomPath = AppConfig.vmFolder + current.vmID + "/";
|
||||
HashMap<String, Object> vmConfigMap = new HashMap<>();
|
||||
|
||||
listmapForGetData = new Gson().fromJson(FileUtils.readAFile(AppConfig.romsdatajson), new TypeToken<ArrayList<HashMap<String, Object>>>() {
|
||||
}.getType());
|
||||
vmConfigMap.put("title", current.itemName);
|
||||
|
||||
getRomPath = AppConfig.vmFolder + Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("vmID")) + "/";
|
||||
if (FileUtils.isFileExists(current.itemIcon)) {
|
||||
vmConfigMap.put("icon", new File(Objects.requireNonNull(Uri.parse(current.itemIcon).getPath())).getName());
|
||||
} else {
|
||||
vmConfigMap.put("icon", current.itemIcon);
|
||||
}
|
||||
|
||||
if (listmapForGetData.get(pendingPosition).containsKey("imgName")) {
|
||||
mapForGetData.put("title", Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgName")).toString());
|
||||
if (FileUtils.isFileExists(current.itemPath)) {
|
||||
vmConfigMap.put("drive", new File(Objects.requireNonNull(Uri.parse(current.itemPath).getPath())).getName());
|
||||
} else {
|
||||
mapForGetData.put("title", "");
|
||||
vmConfigMap.put("drive", VMManager.quickScanDiskFileInFolder(getRomPath));
|
||||
}
|
||||
if (listmapForGetData.get(pendingPosition).containsKey("imgIcon")) {
|
||||
iconfile = Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgIcon")).toString();
|
||||
try {
|
||||
mapForGetData.put("icon", Uri.parse(Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgIcon")).toString()).getLastPathSegment());
|
||||
} catch (Exception _e) {
|
||||
mapForGetData.put("icon", Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgIcon")).toString());
|
||||
}
|
||||
|
||||
if (FileUtils.isFileExists(current.imgCdrom)) {
|
||||
vmConfigMap.put("cdrom", new File(Objects.requireNonNull(Uri.parse(current.imgCdrom).getPath())).getName());
|
||||
} else {
|
||||
mapForGetData.put("icon", "");
|
||||
}
|
||||
if (listmapForGetData.get(pendingPosition).containsKey("imgPath")) {
|
||||
if (Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgPath")).toString().isEmpty()) {
|
||||
diskfile = VMManager.quickScanDiskFileInFolder(getRomPath);
|
||||
} else {
|
||||
diskfile = Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgPath")).toString();
|
||||
}
|
||||
mapForGetData.put("drive", Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgPath")).toString().replaceAll(getRomPath, ""));
|
||||
} else {
|
||||
diskfile = VMManager.quickScanDiskFileInFolder(getRomPath);
|
||||
mapForGetData.put("drive", "");
|
||||
}
|
||||
if (listmapForGetData.get(pendingPosition).containsKey("imgCdrom")) {
|
||||
if (Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgCdrom")).toString().isEmpty()) {
|
||||
cdromfile = VMManager.quickScanISOFileInFolder(getRomPath);
|
||||
} else {
|
||||
cdromfile = Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgCdrom")).toString();
|
||||
}
|
||||
mapForGetData.put("cdrom", Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgCdrom")).toString().replaceAll(getRomPath, ""));
|
||||
} else {
|
||||
cdromfile = VMManager.quickScanISOFileInFolder(getRomPath);
|
||||
mapForGetData.put("cdrom", "");
|
||||
}
|
||||
if (listmapForGetData.get(pendingPosition).containsKey("bootFrom")) {
|
||||
mapForGetData.put("bootFrom", listmapForGetData.get(pendingPosition).get("bootFrom"));
|
||||
} else {
|
||||
mapForGetData.put("bootFrom", "");
|
||||
}
|
||||
if (listmapForGetData.get(pendingPosition).containsKey("isShowBootMenu")) {
|
||||
mapForGetData.put("isShowBootMenu", listmapForGetData.get(pendingPosition).get("isShowBootMenu"));
|
||||
} else {
|
||||
mapForGetData.put("isShowBootMenu", "");
|
||||
}
|
||||
if (listmapForGetData.get(pendingPosition).containsKey("imgExtra")) {
|
||||
mapForGetData.put("qemu", Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgExtra")).toString().replaceAll(getRomPath, "OhnoIjustrealizeditsmidnightandIstillhavetodothis"));
|
||||
} else {
|
||||
mapForGetData.put("qemu", "");
|
||||
}
|
||||
if (listmapForGetData.get(pendingPosition).containsKey("imgArch")) {
|
||||
mapForGetData.put("arch", Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("imgArch")).toString());
|
||||
} else {
|
||||
mapForGetData.put("arch", "");
|
||||
vmConfigMap.put("cdrom", VMManager.quickScanISOFileInFolder(getRomPath));
|
||||
}
|
||||
|
||||
vmConfigMap.put("bootFrom", current.bootFrom);
|
||||
vmConfigMap.put("isShowBootMenu", current.isShowBootMenu);
|
||||
vmConfigMap.put("qemu", current.itemExtra);
|
||||
vmConfigMap.put("arch", current.itemArch);
|
||||
|
||||
if (Objects.requireNonNull(binding.edAuthor.getText()).toString().isEmpty()) {
|
||||
mapForGetData.put("author", "Unknow");
|
||||
vmConfigMap.put("author", "Unknow");
|
||||
} else {
|
||||
mapForGetData.put("author", binding.edAuthor.getText().toString());
|
||||
vmConfigMap.put("author", binding.edAuthor.getText().toString());
|
||||
}
|
||||
if (Objects.requireNonNull(binding.edContent.getText()).toString().isEmpty()) {
|
||||
mapForGetData.put("desc", "Empty.");
|
||||
vmConfigMap.put("desc", "Empty.");
|
||||
} else {
|
||||
mapForGetData.put("desc", binding.edContent.getText().toString());
|
||||
vmConfigMap.put("desc", binding.edContent.getText().toString());
|
||||
}
|
||||
|
||||
mapForGetData.put("versioncode", PackageUtils.getThisVersionCode(getApplicationContext()));
|
||||
vmConfigMap.put("versioncode", PackageUtils.getThisVersionCode(getApplicationContext()));
|
||||
|
||||
FileUtils.writeToFile(new File(String.valueOf(getExternalFilesDir("data"))).getPath(), "rom-data.json", new Gson().toJson(mapForGetData));
|
||||
FileUtils.writeToFile(new File(String.valueOf(getExternalFilesDir("data"))).getPath(), "rom-data.json", new Gson().toJson(vmConfigMap));
|
||||
|
||||
String[] filePaths = new String[0];
|
||||
|
||||
ArrayList<String> _filelist = new ArrayList<>();
|
||||
FileUtils.getAListOfAllFilesAndFoldersInADirectory(AppConfig.vmFolder + Objects.requireNonNull(listmapForGetData.get(pendingPosition).get("vmID")), _filelist);
|
||||
FileUtils.getAListOfAllFilesAndFoldersInADirectory(AppConfig.vmFolder + current.vmID, _filelist);
|
||||
if (!_filelist.isEmpty()) {
|
||||
for (int _repeat = 0; _repeat < _filelist.size(); _repeat++) {
|
||||
if (!_filelist.get(_repeat).endsWith("vmID.txt") &&
|
||||
|
|
@ -215,14 +188,15 @@ public class ExportRomActivity extends AppCompatActivity {
|
|||
isExporting = true;
|
||||
|
||||
String outputPath;
|
||||
if (!FileUtils.isFileExists(AppConfig.cvbiFolder + Objects.requireNonNull(mapForGetData.get("title")) + ".cvbi")) {
|
||||
outputPath = AppConfig.cvbiFolder + Objects.requireNonNull(mapForGetData.get("title")) + ".cvbi";
|
||||
String outputFileName = current.itemName + ".cvbi";
|
||||
if (!FileUtils.isFileExists(cvbiFolder + current.itemName + ".cvbi")) {
|
||||
outputPath = cvbiFolder + outputFileName;
|
||||
} else {
|
||||
String outputFileName = Objects.requireNonNull(mapForGetData.get("title")).toString();
|
||||
int prefix = 0;
|
||||
while (true) {
|
||||
if (!FileUtils.isFileExists(AppConfig.cvbiFolder + outputFileName + "_" + prefix + ".cvbi")) {
|
||||
outputPath = AppConfig.cvbiFolder + outputFileName + "_" + prefix + ".cvbi";
|
||||
if (!FileUtils.isFileExists(cvbiFolder + current.itemName + "_" + prefix + ".cvbi")) {
|
||||
outputFileName = current.itemName + "_" + prefix + ".cvbi";
|
||||
outputPath = cvbiFolder + outputFileName;
|
||||
break;
|
||||
} else {
|
||||
prefix++;
|
||||
|
|
@ -230,23 +204,31 @@ public class ExportRomActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
boolean result = ZipUtils.compress(
|
||||
final boolean[] result = {ZipUtils.compress(
|
||||
this,
|
||||
finalFilePaths,
|
||||
outputPath,
|
||||
uri,
|
||||
progressText,
|
||||
progressBar
|
||||
);
|
||||
|
||||
)};
|
||||
runOnUiThread(() -> {
|
||||
isExporting = false;
|
||||
progressDialog.dismiss();
|
||||
|
||||
String finalOutputPath = "";
|
||||
try {
|
||||
FileUtils.deleteDirectory(outputPath);
|
||||
finalOutputPath = FileUtils.getPath(this, uri);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "startCreate: ", e);
|
||||
}
|
||||
|
||||
String finalOutputPath1 = finalOutputPath;
|
||||
String title;
|
||||
String content;
|
||||
if (result) {
|
||||
if (result[0]) {
|
||||
title = getString(R.string.done);
|
||||
content = getString(R.string.saved_in) + ": " + outputPath + ".";
|
||||
content = finalOutputPath1 == null || finalOutputPath1.isEmpty() ? getString(R.string.rom_successfully_exported) : getString(R.string.saved_in) + ": " + finalOutputPath1 + ".";
|
||||
} else {
|
||||
title = getString(R.string.oops);
|
||||
content = getString(R.string.something_went_wrong) + ":\n\n" + ZipUtils.lastErrorContent;
|
||||
|
|
@ -255,19 +237,20 @@ public class ExportRomActivity extends AppCompatActivity {
|
|||
DialogUtils.twoDialog(this,
|
||||
title,
|
||||
content,
|
||||
getString(result ? R.string.show_in_folder : R.string.ok),
|
||||
getString(result ? R.string.close : R.string.exit),
|
||||
getString(result[0] ? R.string.show_in_folder : R.string.ok),
|
||||
getString(result[0] ? R.string.close : R.string.exit),
|
||||
true,
|
||||
result ? R.drawable.check_24px : R.drawable.error_96px,
|
||||
result[0] ? R.drawable.check_24px : R.drawable.error_96px,
|
||||
true,
|
||||
() -> {
|
||||
if (result) {
|
||||
File file = new File(outputPath);
|
||||
if (result[0]) {
|
||||
assert finalOutputPath1 != null;
|
||||
File file = new File(finalOutputPath1.isEmpty() ? outputPath : finalOutputPath1);
|
||||
FileUtils.openFolder(this, file.getParent());
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
if (!result) {
|
||||
if (!result[0]) {
|
||||
finish();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -310,30 +310,4 @@ public class Minitools extends AppCompatActivity {
|
|||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void extractLoaderApk() {
|
||||
String apkLoaderAssetPath = "bootstrap/loader.apk";
|
||||
String apkLoaderextractedFilePath = TermuxService.PREFIX_PATH + "/libexec/termux-x11/loader.apk";
|
||||
|
||||
FileUtils.deleteDirectory(apkLoaderextractedFilePath);
|
||||
if (copyAssetToFile(apkLoaderAssetPath, apkLoaderextractedFilePath)) {
|
||||
FileUtils.copyAFile(TermuxService.PREFIX_PATH + "/libexec/termux-x11/loader.apk", AppConfig.maindirpath);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean copyAssetToFile(String assetPath, String outputPath) {
|
||||
try (InputStream in = getAssets().open(assetPath);
|
||||
OutputStream out = new FileOutputStream(outputPath)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
out.flush();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "copyAssetToFile: ", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -983,10 +983,7 @@ public class VMManager {
|
|||
|
||||
public static void killallqemuprocesses(Context context) {
|
||||
Terminal vterm = new Terminal(context);
|
||||
vterm.executeShellCommand2("killall -15 qemu-system-i386", false, null);
|
||||
vterm.executeShellCommand2("killall -15 qemu-system-x86_64", false, null);
|
||||
vterm.executeShellCommand2("killall -15 qemu-system-aarch64", false, null);
|
||||
vterm.executeShellCommand2("killall -15 qemu-system-ppc", false, null);
|
||||
vterm.executeShellCommand2("killall -15 qemu-system-i386 && killall -15 qemu-system-x86_64 && killall -15 qemu-system-aarch64 && killall -15 qemu-system-ppc", false, null);
|
||||
}
|
||||
|
||||
public static void shutdownCurrentVM() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.vectras.vm;
|
||||
package com.vectras.vm.creator;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
|
@ -7,6 +7,7 @@ import android.view.inputmethod.InputMethodManager;
|
|||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.vectras.vm.R;
|
||||
import com.vectras.vm.databinding.ActivityQemuParamsEditorBinding;
|
||||
import com.vectras.vm.utils.UIUtils;
|
||||
|
||||
|
|
@ -1,153 +1,153 @@
|
|||
package com.vectras.vm;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.DragEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.vectras.qemu.MainSettingsManager;
|
||||
import com.vectras.vm.creator.VMCreatorActivity;
|
||||
import com.vectras.vm.databinding.ActivitySetArchBinding;
|
||||
import com.vectras.vm.main.MainActivity;
|
||||
import com.vectras.vm.utils.FileUtils;
|
||||
import com.vectras.vm.utils.UIUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class SetArchActivity extends AppCompatActivity implements View.OnClickListener {
|
||||
|
||||
SetArchActivity activity;
|
||||
ActivitySetArchBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
UIUtils.edgeToEdge(this);
|
||||
binding = ActivitySetArchBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
// UIUtils.setOnApplyWindowInsetsListener(findViewById(R.id.main));
|
||||
activity = this;
|
||||
binding.archi386.setOnClickListener(this);
|
||||
binding.archx8664.setOnClickListener(this);
|
||||
binding.archarm64.setOnClickListener(this);
|
||||
binding.archppc.setOnClickListener(this);
|
||||
binding.webBtn.setOnClickListener(this);
|
||||
binding.buttongetcm.setOnClickListener(this);
|
||||
binding.bntimport.setOnClickListener(this);
|
||||
|
||||
setSupportActionBar(binding.toolbar);
|
||||
binding.toolbar.setNavigationOnClickListener(v -> finish());
|
||||
|
||||
binding.toolbar.setOnMenuItemClickListener(item -> {
|
||||
if (item.getItemId() == R.id.roms_store) {
|
||||
MainActivity.isOpenRomStore = true;
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// if (PackageUtils.isInstalled("com.anbui.cqcm.app", this)) {
|
||||
// binding.buttongetcm.setText(getResources().getString(R.string.open));
|
||||
// }
|
||||
|
||||
binding.bntimport.setOnDragListener((v, event) -> {
|
||||
Log.i("Drag", "onDrag: " + event.getAction());
|
||||
switch (event.getAction()) {
|
||||
case DragEvent.ACTION_DRAG_STARTED:
|
||||
ClipDescription description = event.getClipDescription();
|
||||
if (description != null) {
|
||||
Log.d("DRAG", "MIME: " + description.getMimeType(0));
|
||||
return true; // Accept to go to event DragEvent.ACTION_DROP
|
||||
}
|
||||
return false;
|
||||
|
||||
case DragEvent.ACTION_DROP:
|
||||
ClipData clipData = event.getClipData();
|
||||
if (clipData != null && clipData.getItemCount() > 0) {
|
||||
Uri uri = clipData.getItemAt(0).getUri();
|
||||
String filePath = FileUtils.getFilePathFromUri(getApplicationContext(), uri);
|
||||
|
||||
File file = new File(filePath);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(getApplicationContext(), VMCreatorActivity.class);
|
||||
intent.putExtra("addromnow", "");
|
||||
intent.putExtra("romextra", "");
|
||||
intent.putExtra("romname", "");
|
||||
intent.putExtra("romicon", "");
|
||||
intent.putExtra("rompath", filePath);
|
||||
intent.putExtra("romfilename", file.getName());
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
int id = v.getId();
|
||||
if (id == R.id.archi386) {
|
||||
MainSettingsManager.setArch(this, "I386");
|
||||
|
||||
startActivity(new Intent(activity, VMCreatorActivity.class));
|
||||
finish();
|
||||
} else if (id == R.id.archx86_64) {
|
||||
MainSettingsManager.setArch(this, "X86_64");
|
||||
|
||||
startActivity(new Intent(activity, VMCreatorActivity.class));
|
||||
finish();
|
||||
} else if (id == R.id.archarm64) {
|
||||
MainSettingsManager.setArch(this, "ARM64");
|
||||
|
||||
startActivity(new Intent(activity, VMCreatorActivity.class));
|
||||
finish();
|
||||
} else if (id == R.id.archppc) {
|
||||
MainSettingsManager.setArch(this, "PPC");
|
||||
|
||||
startActivity(new Intent(activity, VMCreatorActivity.class));
|
||||
finish();
|
||||
} else if (id == R.id.webBtn) {
|
||||
String qe = "https://www.qemu.org/";
|
||||
Intent q = new Intent(Intent.ACTION_VIEW);
|
||||
q.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
q.setData(Uri.parse(qe));
|
||||
startActivity(q);
|
||||
} else if (id == R.id.buttongetcm) {
|
||||
PackageManager pm = getPackageManager();
|
||||
Intent intent = pm.getLaunchIntentForPackage("com.anbui.cqcm.app");
|
||||
|
||||
if (intent != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Intent intenturl = new Intent();
|
||||
intenturl.setAction(Intent.ACTION_VIEW);
|
||||
intenturl.setData(Uri.parse("https://play.google.com/store/apps/details?id=com.anbui.cqcm.app"));
|
||||
startActivity(intenturl);
|
||||
}
|
||||
} else if (id == R.id.bntimport) {
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(getApplicationContext(), VMCreatorActivity.class);
|
||||
intent.putExtra("importcvbinow", "");
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.set_arch_toolbar_menu, menu);
|
||||
return true;
|
||||
}
|
||||
package com.vectras.vm.creator;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.DragEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.vectras.qemu.MainSettingsManager;
|
||||
import com.vectras.vm.R;
|
||||
import com.vectras.vm.databinding.ActivitySetArchBinding;
|
||||
import com.vectras.vm.main.MainActivity;
|
||||
import com.vectras.vm.utils.FileUtils;
|
||||
import com.vectras.vm.utils.UIUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class SetArchActivity extends AppCompatActivity implements View.OnClickListener {
|
||||
|
||||
SetArchActivity activity;
|
||||
ActivitySetArchBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
UIUtils.edgeToEdge(this);
|
||||
binding = ActivitySetArchBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
// UIUtils.setOnApplyWindowInsetsListener(findViewById(R.id.main));
|
||||
activity = this;
|
||||
binding.archi386.setOnClickListener(this);
|
||||
binding.archx8664.setOnClickListener(this);
|
||||
binding.archarm64.setOnClickListener(this);
|
||||
binding.archppc.setOnClickListener(this);
|
||||
binding.webBtn.setOnClickListener(this);
|
||||
binding.buttongetcm.setOnClickListener(this);
|
||||
binding.bntimport.setOnClickListener(this);
|
||||
|
||||
setSupportActionBar(binding.toolbar);
|
||||
binding.toolbar.setNavigationOnClickListener(v -> finish());
|
||||
|
||||
binding.toolbar.setOnMenuItemClickListener(item -> {
|
||||
if (item.getItemId() == R.id.roms_store) {
|
||||
MainActivity.isOpenRomStore = true;
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// if (PackageUtils.isInstalled("com.anbui.cqcm.app", this)) {
|
||||
// binding.buttongetcm.setText(getResources().getString(R.string.open));
|
||||
// }
|
||||
|
||||
binding.bntimport.setOnDragListener((v, event) -> {
|
||||
Log.i("Drag", "onDrag: " + event.getAction());
|
||||
switch (event.getAction()) {
|
||||
case DragEvent.ACTION_DRAG_STARTED:
|
||||
ClipDescription description = event.getClipDescription();
|
||||
if (description != null) {
|
||||
Log.d("DRAG", "MIME: " + description.getMimeType(0));
|
||||
return true; // Accept to go to event DragEvent.ACTION_DROP
|
||||
}
|
||||
return false;
|
||||
|
||||
case DragEvent.ACTION_DROP:
|
||||
ClipData clipData = event.getClipData();
|
||||
if (clipData != null && clipData.getItemCount() > 0) {
|
||||
Uri uri = clipData.getItemAt(0).getUri();
|
||||
String filePath = FileUtils.getFilePathFromUri(getApplicationContext(), uri);
|
||||
|
||||
File file = new File(filePath);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(getApplicationContext(), VMCreatorActivity.class);
|
||||
intent.putExtra("addromnow", "");
|
||||
intent.putExtra("romextra", "");
|
||||
intent.putExtra("romname", "");
|
||||
intent.putExtra("romicon", "");
|
||||
intent.putExtra("rompath", filePath);
|
||||
intent.putExtra("romfilename", file.getName());
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
int id = v.getId();
|
||||
if (id == R.id.archi386) {
|
||||
MainSettingsManager.setArch(this, "I386");
|
||||
|
||||
startActivity(new Intent(activity, VMCreatorActivity.class));
|
||||
finish();
|
||||
} else if (id == R.id.archx86_64) {
|
||||
MainSettingsManager.setArch(this, "X86_64");
|
||||
|
||||
startActivity(new Intent(activity, VMCreatorActivity.class));
|
||||
finish();
|
||||
} else if (id == R.id.archarm64) {
|
||||
MainSettingsManager.setArch(this, "ARM64");
|
||||
|
||||
startActivity(new Intent(activity, VMCreatorActivity.class));
|
||||
finish();
|
||||
} else if (id == R.id.archppc) {
|
||||
MainSettingsManager.setArch(this, "PPC");
|
||||
|
||||
startActivity(new Intent(activity, VMCreatorActivity.class));
|
||||
finish();
|
||||
} else if (id == R.id.webBtn) {
|
||||
String qe = "https://www.qemu.org/";
|
||||
Intent q = new Intent(Intent.ACTION_VIEW);
|
||||
q.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
q.setData(Uri.parse(qe));
|
||||
startActivity(q);
|
||||
} else if (id == R.id.buttongetcm) {
|
||||
PackageManager pm = getPackageManager();
|
||||
Intent intent = pm.getLaunchIntentForPackage("com.anbui.cqcm.app");
|
||||
|
||||
if (intent != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Intent intenturl = new Intent();
|
||||
intenturl.setAction(Intent.ACTION_VIEW);
|
||||
intenturl.setData(Uri.parse("https://play.google.com/store/apps/details?id=com.anbui.cqcm.app"));
|
||||
startActivity(intenturl);
|
||||
}
|
||||
} else if (id == R.id.bntimport) {
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(getApplicationContext(), VMCreatorActivity.class);
|
||||
intent.putExtra("importcvbinow", "");
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.set_arch_toolbar_menu, menu);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ import com.google.gson.Gson;
|
|||
import com.vectras.qemu.MainSettingsManager;
|
||||
import com.vectras.vm.AppConfig;
|
||||
import com.vectras.vm.Fragment.CreateImageDialogFragment;
|
||||
import com.vectras.vm.QemuParamsEditorActivity;
|
||||
import com.vectras.vm.R;
|
||||
import com.vectras.vm.RomInfo;
|
||||
import com.vectras.vm.SplashActivity;
|
||||
|
|
@ -674,7 +673,7 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
null));
|
||||
} finally {
|
||||
runOnUiThread(() -> {
|
||||
if (!isImportingCVBI && !isFinishing() && !isDestroyed())
|
||||
if (!isImportingCVBI && progressDialog.isShowing() && !isFinishing() && !isDestroyed())
|
||||
progressDialog.dismiss();
|
||||
});
|
||||
}
|
||||
|
|
@ -879,13 +878,14 @@ public class VMCreatorActivity extends AppCompatActivity {
|
|||
);
|
||||
|
||||
runOnUiThread(() -> {
|
||||
if (progressDialog.isShowing() && !isFinishing() && !isDestroyed())
|
||||
progressDialog.dismiss();
|
||||
|
||||
if (isFinishing() || isDestroyed()) {
|
||||
new Thread(() -> FileUtils.deleteDirectory(AppConfig.vmFolder + vmID)).start();
|
||||
return;
|
||||
}
|
||||
|
||||
progressDialog.dismiss();
|
||||
|
||||
if (result) {
|
||||
afterExtractCVBIFile(fileName);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package com.vectras.vm.creator;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -14,14 +12,10 @@ import androidx.appcompat.app.AlertDialog;
|
|||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.vectras.vm.R;
|
||||
import com.vectras.vm.databinding.RecyclerViewBinding;
|
||||
import com.vectras.vm.databinding.DialogListSelectorLayoutBinding;
|
||||
import com.vectras.vm.databinding.SimpleLayoutListViewWithCheckBinding;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
|
@ -43,20 +37,31 @@ public class VMCreatorSelector {
|
|||
|
||||
public static void showDialog(Activity activity, ArrayList<HashMap<String, Object>> list, int position,SelectorCallback callback, String title) {
|
||||
LinearLayoutManager layoutmanager = new LinearLayoutManager(activity);
|
||||
RecyclerViewBinding binding = RecyclerViewBinding.inflate(activity.getLayoutInflater());
|
||||
DialogListSelectorLayoutBinding binding = DialogListSelectorLayoutBinding.inflate(activity.getLayoutInflater());
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(activity)
|
||||
.setView(binding.getRoot())
|
||||
.create();
|
||||
|
||||
dialog.setTitle(title);
|
||||
dialog.setButton(AlertDialog.BUTTON_POSITIVE, activity.getString(R.string.close), (dialog1, which) -> dialog1.dismiss());
|
||||
binding.tvTitle.setText(title);
|
||||
binding.btnClose.setOnClickListener(v -> dialog.dismiss());
|
||||
|
||||
binding.list.setAdapter(new RecyclerviewAdapter(activity, dialog, list, position, callback));
|
||||
binding.list.setLayoutManager(layoutmanager);
|
||||
|
||||
dialog.show();
|
||||
|
||||
binding.list.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView rv, int dx, int dy) {
|
||||
boolean canScrollUp = rv.canScrollVertically(-1);
|
||||
boolean canScrollDown = rv.canScrollVertically(1);
|
||||
|
||||
binding.dvTop.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
|
||||
binding.dvBottom.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
if (position > -1) binding.list.scrollToPosition(position);
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +95,7 @@ public class VMCreatorSelector {
|
|||
View view = holder.itemView;
|
||||
TextView title = view.findViewById(R.id.textview);
|
||||
title.setText(Objects.requireNonNull(data.get(position).get("name")).toString());
|
||||
view.findViewById(R.id.iv_check).setVisibility(position == currentPosition ? View.VISIBLE : View.GONE);
|
||||
view.findViewById(R.id.iv_check).setVisibility(position == currentPosition ? View.VISIBLE : View.INVISIBLE);
|
||||
view.findViewById(R.id.main).setOnClickListener(v -> {
|
||||
callback.onSelected(
|
||||
position,
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ import com.vectras.vm.databinding.BottomsheetdialogLoggerBinding;
|
|||
import com.vectras.vm.databinding.UpdateBottomDialogLayoutBinding;
|
||||
import com.vectras.vm.main.romstore.RomStoreHomeAdapterSearch;
|
||||
import com.vectras.vm.main.romstore.DataRoms;
|
||||
import com.vectras.vm.SetArchActivity;
|
||||
import com.vectras.vm.creator.SetArchActivity;
|
||||
import com.vectras.vm.VMManager;
|
||||
import com.vectras.vm.adapter.LogsAdapter;
|
||||
import com.vectras.vm.main.core.CallbackInterface;
|
||||
|
|
@ -69,6 +69,7 @@ import com.vectras.vm.settings.UpdaterActivity;
|
|||
import com.vectras.vm.utils.DeviceUtils;
|
||||
import com.vectras.vm.utils.DialogUtils;
|
||||
import com.vectras.vm.utils.FileUtils;
|
||||
import com.vectras.vm.utils.IntentUtils;
|
||||
import com.vectras.vm.utils.LibraryChecker;
|
||||
import com.vectras.vm.utils.NotificationUtils;
|
||||
import com.vectras.vm.utils.PackageUtils;
|
||||
|
|
@ -423,10 +424,7 @@ public class MainActivity extends AppCompatActivity implements RomStoreFragment.
|
|||
startActivity(new Intent(this, AboutActivity.class));
|
||||
}
|
||||
if (id == R.id.navigation_item_help) {
|
||||
String tw = AppConfig.vectrasHelp;
|
||||
Intent w = new Intent(ACTION_VIEW);
|
||||
w.setData(Uri.parse(tw));
|
||||
startActivity(w);
|
||||
IntentUtils.openUrl(this, AppConfig.vectrasHelp, true);
|
||||
} else if (id == R.id.navigation_item_website) {
|
||||
String tw = AppConfig.vectrasWebsite;
|
||||
Intent w = new Intent(ACTION_VIEW);
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ public class RomOptionsDialog {
|
|||
|
||||
Button exportRomBtn = v.findViewById(R.id.exportRomBtn);
|
||||
exportRomBtn.setOnClickListener(v2 -> {
|
||||
ExportRomActivity.pendingPosition = position;
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(activity, ExportRomActivity.class);
|
||||
intent.putExtra("POS", position);
|
||||
activity.startActivity(intent);
|
||||
bottomSheetDialog.cancel();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import com.vectras.vm.main.MainActivity;
|
|||
import com.vectras.vm.utils.DeviceUtils;
|
||||
import com.vectras.vm.utils.DialogUtils;
|
||||
import com.vectras.vm.utils.FileUtils;
|
||||
import com.vectras.vm.utils.IntentUtils;
|
||||
import com.vectras.vm.utils.JSONUtils;
|
||||
import com.vectras.vm.utils.ListUtils;
|
||||
import com.vectras.vm.utils.PermissionUtils;
|
||||
|
|
@ -193,10 +194,7 @@ public class SetupWizard2Activity extends AppCompatActivity {
|
|||
uiController(STEP_SYSTEM_UPDATE);
|
||||
binding.btnSkipSystemUpdate.setVisibility(View.GONE);
|
||||
} else if (isLibProotError) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(ACTION_VIEW);
|
||||
intent.setData(Uri.parse(AppConfig.telegramLink));
|
||||
startActivity(intent);
|
||||
IntentUtils.openTelegramLink(this);
|
||||
} else if (SetupFeatureCore.isInstalledSystemFiles(this)) {
|
||||
getDataForStandardSetup();
|
||||
} else {
|
||||
|
|
@ -210,8 +208,7 @@ public class SetupWizard2Activity extends AppCompatActivity {
|
|||
bindingFinalSteps.btnContinue.setOnClickListener(v -> {
|
||||
if (currentStep == STEP_JOIN_COMMUNITY) {
|
||||
uiControllerFinalSteps(currentStep + 1);
|
||||
Intent intent = new Intent(ACTION_VIEW, Uri.parse(AppConfig.telegramLink));
|
||||
startActivity(intent);
|
||||
IntentUtils.openTelegramLink(this);
|
||||
//Don't show join Telegram dialog again
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
|
|
@ -772,7 +769,7 @@ public class SetupWizard2Activity extends AppCompatActivity {
|
|||
// Assign data
|
||||
HashMap<String, String> item = data.get(position);
|
||||
holder.simpleLayoutListViewWithCheckBinding.textview.setText(item.get("location"));
|
||||
holder.simpleLayoutListViewWithCheckBinding.ivCheck.setVisibility(position == selectedPosition ? View.VISIBLE : View.GONE);
|
||||
holder.simpleLayoutListViewWithCheckBinding.ivCheck.setVisibility(position == selectedPosition ? View.VISIBLE : View.INVISIBLE);
|
||||
|
||||
|
||||
return convertView;
|
||||
|
|
|
|||
|
|
@ -254,12 +254,7 @@ public class DialogUtils {
|
|||
_context.getResources().getString(R.string.join_us_on_telegram_where_we_publish_all_the_news_and_updates_and_receive_your_opinions_and_bugs),
|
||||
_context.getResources().getString(R.string.join), _context.getResources().getString(R.string.cancel), _context.getResources().getString(R.string.dont_show_again),
|
||||
true, R.drawable.send_24px, true,
|
||||
() -> {
|
||||
Intent intent = new Intent(ACTION_VIEW);
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
intent.setData(Uri.parse(AppConfig.telegramLink));
|
||||
_context.startActivity(intent);
|
||||
}, null,
|
||||
() -> IntentUtils.openTelegramLink(_context), null,
|
||||
() -> {
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putBoolean("tgDialog", true);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
44
app/src/main/java/com/vectras/vm/utils/IntentUtils.java
Normal file
44
app/src/main/java/com/vectras/vm/utils/IntentUtils.java
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package com.vectras.vm.utils;
|
||||
|
||||
import static android.content.Intent.ACTION_VIEW;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.vectras.vm.AppConfig;
|
||||
import com.vectras.vm.R;
|
||||
|
||||
public class IntentUtils {
|
||||
public static boolean openTelegramLink(Context context) {
|
||||
return openUrl(context, AppConfig.telegramLink, true);
|
||||
}
|
||||
|
||||
public static boolean openUrl(Context context, String url, boolean isShowErrorDialog) {
|
||||
boolean result = openUrl(context, url);
|
||||
if (isShowErrorDialog && !result) {
|
||||
DialogUtils.oneDialog(
|
||||
context,
|
||||
context.getString(R.string.oops),
|
||||
context.getString(R.string.there_is_no_app_to_perform_this_action),
|
||||
R.drawable.error_96px
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean openUrl(Context context, String url) {
|
||||
Intent intent = new Intent(ACTION_VIEW);
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
intent.setData(Uri.parse(url));
|
||||
|
||||
PackageManager packagemanager = context.getPackageManager();
|
||||
if (intent.resolveActivity(packagemanager) != null) {
|
||||
context.startActivity(intent);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,9 +14,11 @@ import com.vectras.vm.R;
|
|||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
|
@ -258,64 +260,10 @@ public class ZipUtils {
|
|||
ProgressBar progressBar
|
||||
) {
|
||||
try {
|
||||
long totalBytes = 0;
|
||||
for (String path : filePaths) {
|
||||
File f = new File(path);
|
||||
if (f.isFile()) totalBytes += f.length();
|
||||
}
|
||||
|
||||
long bytesWritten = 0;
|
||||
byte[] buffer;
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(outputZip);
|
||||
ZipOutputStream zos = new ZipOutputStream(fos)) {
|
||||
|
||||
for (String filePath : filePaths) {
|
||||
File file = new File(filePath);
|
||||
long size = file.length();
|
||||
long crc = calculateCrc(context, file);
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
ZipEntry entry = new ZipEntry(file.getName());
|
||||
|
||||
entry.setMethod(ZipEntry.DEFLATED);
|
||||
entry.setSize(size);
|
||||
|
||||
if (MainSettingsManager.getCyclicRedundancyCheck(context))
|
||||
entry.setCrc(crc);
|
||||
|
||||
zos.putNextEntry(entry);
|
||||
|
||||
if (DeviceUtils.totalMemoryCapacity(context) < 4L * 1024 * 1024 * 1024)
|
||||
buffer = new byte[64 * 1024];
|
||||
else
|
||||
buffer = new byte[128 * 1024];
|
||||
|
||||
int len;
|
||||
long lastProgress = -1;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
zos.write(buffer, 0, len);
|
||||
bytesWritten += len;
|
||||
|
||||
final int progress = (int) ((bytesWritten * 100L) / totalBytes);
|
||||
|
||||
if (progress > lastProgress) {
|
||||
lastProgress = progress;
|
||||
updateStatus(
|
||||
statusTextView,
|
||||
progressBar,
|
||||
(progress == 0 || progress > 100 ?
|
||||
context.getString(R.string.exporting) :
|
||||
context.getString(R.string.completed) + " " + progress + "%")
|
||||
+ "\n" + context.getString(R.string.please_stay_here),
|
||||
progress
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
compressCore(context, filePaths, zos, statusTextView, progressBar);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
|
|
@ -325,6 +273,91 @@ public class ZipUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean compress(
|
||||
Context context,
|
||||
String[] filePaths,
|
||||
Uri outputZip,
|
||||
TextView statusTextView,
|
||||
ProgressBar progressBar
|
||||
) {
|
||||
try {
|
||||
try (OutputStream os = context.getContentResolver().openOutputStream(outputZip);
|
||||
ZipOutputStream zos = new ZipOutputStream(os)) {
|
||||
|
||||
compressCore(context, filePaths, zos, statusTextView, progressBar);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "compress: ", e);
|
||||
lastErrorContent = e.toString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void compressCore(
|
||||
Context context,
|
||||
String[] filePaths,
|
||||
ZipOutputStream zos,
|
||||
TextView statusTextView,
|
||||
ProgressBar progressBar) throws Exception {
|
||||
|
||||
long totalBytes = 0;
|
||||
for (String path : filePaths) {
|
||||
File f = new File(path);
|
||||
if (f.isFile()) totalBytes += f.length();
|
||||
}
|
||||
|
||||
long bytesWritten = 0;
|
||||
byte[] buffer;
|
||||
|
||||
for (String filePath : filePaths) {
|
||||
File file = new File(filePath);
|
||||
long size = file.length();
|
||||
long crc = calculateCrc(context, file);
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
ZipEntry entry = new ZipEntry(file.getName());
|
||||
|
||||
entry.setMethod(ZipEntry.DEFLATED);
|
||||
entry.setSize(size);
|
||||
|
||||
if (MainSettingsManager.getCyclicRedundancyCheck(context))
|
||||
entry.setCrc(crc);
|
||||
|
||||
zos.putNextEntry(entry);
|
||||
|
||||
if (DeviceUtils.totalMemoryCapacity(context) < 4L * 1024 * 1024 * 1024)
|
||||
buffer = new byte[64 * 1024];
|
||||
else
|
||||
buffer = new byte[128 * 1024];
|
||||
|
||||
int len;
|
||||
long lastProgress = -1;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
zos.write(buffer, 0, len);
|
||||
bytesWritten += len;
|
||||
|
||||
final int progress = (int) ((bytesWritten * 100L) / totalBytes);
|
||||
|
||||
if (progress > lastProgress) {
|
||||
lastProgress = progress;
|
||||
updateStatus(
|
||||
statusTextView,
|
||||
progressBar,
|
||||
(totalBytes > 0 && (progress == 0 || progress > 100) ?
|
||||
context.getString(R.string.exporting) :
|
||||
context.getString(R.string.completed) + " " + progress + "%")
|
||||
+ "\n" + context.getString(R.string.please_stay_here),
|
||||
progress
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateStatus(TextView statusTextView, ProgressBar progressbar, String msg, int progress) {
|
||||
if (!(statusTextView == null || statusTextView.getContext() == null)) {
|
||||
((Activity) statusTextView.getContext()).runOnUiThread(() -> statusTextView.setText(msg));
|
||||
|
|
|
|||
|
|
@ -2,22 +2,20 @@ package com.vectras.vm.view;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.vectras.vm.R;
|
||||
import com.vectras.vm.model.GithubUser;
|
||||
import com.vectras.vm.network.GithubApiService;
|
||||
import com.vectras.vm.utils.IntentUtils;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
|
|
@ -73,7 +71,7 @@ public class GithubUserView extends LinearLayout {
|
|||
Call<GithubUser> call = service.getUser(username);
|
||||
call.enqueue(new Callback<GithubUser>() {
|
||||
@Override
|
||||
public void onResponse(Call<GithubUser> call, Response<GithubUser> response) {
|
||||
public void onResponse(@NonNull Call<GithubUser> call, @NonNull Response<GithubUser> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
GithubUser user = response.body();
|
||||
thisUserNameGitHub = user.getLogin();
|
||||
|
|
@ -95,7 +93,7 @@ public class GithubUserView extends LinearLayout {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<GithubUser> call, Throwable t) {
|
||||
public void onFailure(@NonNull Call<GithubUser> call, @NonNull Throwable t) {
|
||||
userName.setText(getContext().getString(R.string.unknow));
|
||||
userDescription.setText(getContext().getString(R.string.unknow));
|
||||
profileImage.setImageResource(R.drawable.account_circle_24px);
|
||||
|
|
@ -104,8 +102,6 @@ public class GithubUserView extends LinearLayout {
|
|||
}
|
||||
|
||||
private void openGithubProfile(Context context) {
|
||||
String url = "https://github.com/" + thisUserNameGitHub;
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
context.startActivity(intent);
|
||||
IntentUtils.openUrl(context, "https://github.com/" + thisUserNameGitHub, true);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,10 +19,6 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
|
|
@ -30,6 +26,7 @@ import com.vectras.vm.R;
|
|||
import com.vectras.vm.VMManager;
|
||||
import com.vectras.vm.AppConfig;
|
||||
import com.vectras.vm.VectrasApp;
|
||||
import com.vectras.vm.logger.VectrasStatus;
|
||||
import com.vectras.vm.utils.ClipboardUltils;
|
||||
import com.vectras.vm.utils.DialogUtils;
|
||||
import com.vectras.vm.utils.NotificationUtils;
|
||||
|
|
@ -63,18 +60,23 @@ public class Terminal {
|
|||
AtomicReference<StringBuilder> output = new AtomicReference<>(new StringBuilder());
|
||||
StringBuilder errors = new StringBuilder();
|
||||
Log.d(TAG, userCommand);
|
||||
com.vectras.vm.logger.VectrasStatus.logError("<font color='#4db6ac'>VTERM: >" + userCommand + "</font>");
|
||||
VectrasStatus.logError("<font color='#4db6ac'>VTERM: >" + userCommand + "</font>");
|
||||
|
||||
// Show ProgressDialog
|
||||
View progressView = LayoutInflater.from(dialogActivity).inflate(R.layout.dialog_progress_style, null);
|
||||
TextView progress_text = progressView.findViewById(R.id.progress_text);
|
||||
progress_text.setText(progressDialogMessage);
|
||||
AlertDialog progressDialog = new MaterialAlertDialogBuilder(dialogActivity, R.style.CenteredDialogTheme)
|
||||
.setView(progressView)
|
||||
.setCancelable(false)
|
||||
.create();
|
||||
AlertDialog progressDialog;
|
||||
if (dialogActivity != null) {
|
||||
View progressView = LayoutInflater.from(dialogActivity).inflate(R.layout.dialog_progress_style, null);
|
||||
TextView progress_text = progressView.findViewById(R.id.progress_text);
|
||||
progress_text.setText(progressDialogMessage);
|
||||
progressDialog = new MaterialAlertDialogBuilder(dialogActivity, R.style.CenteredDialogTheme)
|
||||
.setView(progressView)
|
||||
.setCancelable(false)
|
||||
.create();
|
||||
|
||||
if (showProgressDialog) progressDialog.show();
|
||||
if (showProgressDialog) progressDialog.show();
|
||||
} else {
|
||||
progressDialog = null;
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
|
|
@ -116,17 +118,18 @@ public class Terminal {
|
|||
|
||||
output.set(streamLog(userCommand, qemuProcess, false));
|
||||
} catch (IOException e) {
|
||||
progressDialog.dismiss(); // Dismiss ProgressDialog
|
||||
output.get().append(e.getMessage());
|
||||
errors.append(Log.getStackTraceString(e));
|
||||
} finally {
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
progressDialog.dismiss(); // Dismiss ProgressDialog
|
||||
AppConfig.temporaryLastedTerminalOutput = output.toString();
|
||||
if (showResultDialog) {
|
||||
String finalOutput = output.toString();
|
||||
String finalErrors = errors.toString();
|
||||
showDialog(finalOutput.isEmpty() ? finalErrors : finalOutput.replace("read interrupted", "Done!"), dialogActivity, userCommand);
|
||||
if (dialogActivity != null) {
|
||||
progressDialog.dismiss(); // Dismiss ProgressDialog
|
||||
if (showResultDialog) {
|
||||
String finalOutput = output.toString();
|
||||
String finalErrors = errors.toString();
|
||||
showDialog(finalOutput.isEmpty() ? finalErrors : finalOutput.replace("read interrupted", "Done!"), dialogActivity, userCommand);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -137,7 +140,7 @@ public class Terminal {
|
|||
AtomicReference<StringBuilder> output = new AtomicReference<>(new StringBuilder());
|
||||
StringBuilder errors = new StringBuilder();
|
||||
Log.d(TAG, userCommand);
|
||||
com.vectras.vm.logger.VectrasStatus.logError("<font color='#4db6ac'>VTERM: >" + userCommand + "</font>");
|
||||
VectrasStatus.logError("<font color='#4db6ac'>VTERM: >" + userCommand + "</font>");
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Set up the qemuProcess builder to start PRoot with environmental variables and commands
|
||||
|
|
@ -185,7 +188,7 @@ public class Terminal {
|
|||
processBuilder.command(prootCommand);
|
||||
qemuProcess = processBuilder.start();
|
||||
|
||||
output.set(streamLog(userCommand, qemuProcess, false));
|
||||
output.set(streamLog(userCommand, qemuProcess, true));
|
||||
} catch (IOException e) {
|
||||
output.get().append(e.getMessage());
|
||||
errors.append(Log.getStackTraceString(e));
|
||||
|
|
@ -210,7 +213,7 @@ public class Terminal {
|
|||
StringBuilder output = new StringBuilder();
|
||||
StringBuilder errors = new StringBuilder();
|
||||
Log.d(TAG, userCommand);
|
||||
com.vectras.vm.logger.VectrasStatus.logError("<font color='#4db6ac'>VTERM: >" + userCommand + "</font>");
|
||||
VectrasStatus.logError("<font color='#4db6ac'>VTERM: >" + userCommand + "</font>");
|
||||
|
||||
try {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||
|
|
@ -261,11 +264,11 @@ public class Terminal {
|
|||
void onCommandCompleted(String output, String errors);
|
||||
}
|
||||
|
||||
public String executeShellCommand(String userCommand, Context dialogActivity, boolean isShowProgressDialog, CommandCallback callback) {
|
||||
public void executeShellCommand(String userCommand, Context dialogActivity, boolean isShowProgressDialog, CommandCallback callback) {
|
||||
AtomicReference<StringBuilder> output = new AtomicReference<>(new StringBuilder());
|
||||
StringBuilder errors = new StringBuilder();
|
||||
Log.d(TAG, userCommand);
|
||||
com.vectras.vm.logger.VectrasStatus.logError("<font color='#4db6ac'>VTERM: >" + userCommand + "</font>");
|
||||
VectrasStatus.logError("<font color='#4db6ac'>VTERM: >" + userCommand + "</font>");
|
||||
|
||||
// Show ProgressDialog on the main thread
|
||||
View progressView = LayoutInflater.from(dialogActivity).inflate(R.layout.dialog_progress_style, null);
|
||||
|
|
@ -330,8 +333,6 @@ public class Terminal {
|
|||
new Handler(Looper.getMainLooper()).post(() -> callback.onCommandCompleted(output.toString(), errors.toString()));
|
||||
}
|
||||
}).start();
|
||||
|
||||
return "Execution is in progress..."; // Returning a message indicating the command execution is ongoing
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -357,7 +358,7 @@ public class Terminal {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static StringBuilder streamLog(String command, Process process, boolean isShortProcess) {
|
||||
public static StringBuilder streamLog(String command, Process process, boolean isShowResultCode) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
try {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
|
||||
|
|
@ -371,23 +372,29 @@ public class Terminal {
|
|||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
com.vectras.vm.logger.VectrasStatus.logError("<font color='#4db6ac'>VTERM: >" + line + "</font>");
|
||||
VectrasStatus.logError("<font color='#4db6ac'>VTERM: >" + line + "</font>");
|
||||
output.append(line).append("\n");
|
||||
}
|
||||
|
||||
while ((line = errorReader.readLine()) != null) {
|
||||
Log.w(TAG, line);
|
||||
com.vectras.vm.logger.VectrasStatus.logError("<font color='red'>VTERM ERROR: >" + line + "</font>");
|
||||
VectrasStatus.logError("<font color='red'>VTERM ERROR: >" + line + "</font>");
|
||||
output.append(line).append("\n");
|
||||
}
|
||||
|
||||
if (isShortProcess) {
|
||||
int exitCode = process.waitFor();
|
||||
if (exitCode == 0) {
|
||||
output.append("Execution finished successfully.\n");
|
||||
} else {
|
||||
output.append("Execution finished with exit code: ").append(exitCode).append("\n");
|
||||
if (isShowResultCode) {
|
||||
new Thread(() -> {
|
||||
int exitCode;
|
||||
try {
|
||||
exitCode = process.waitFor();
|
||||
if (exitCode == 0) {
|
||||
output.append("\nExecution finished successfully.\n");
|
||||
} else {
|
||||
output.append("\nExecution finished with exit code: ").append(exitCode).append("\n");
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
reader.close();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:fitsSystemWindows="true"
|
||||
android:id="@+id/main"
|
||||
tools:context=".QemuParamsEditorActivity">
|
||||
tools:context=".creator.QemuParamsEditorActivity">
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
47
app/src/main/res/layout/dialog_list_selector_layout.xml
Normal file
47
app/src/main/res/layout/dialog_list_selector_layout.xml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?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="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/colorSurfaceContainer"
|
||||
android:animateLayoutChanges="true">
|
||||
<TextView
|
||||
style="@style/MaterialAlertDialog.Material3Expressive.Title.Text"
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:text="@string/options"/>
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:id="@+id/dv_top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible"/>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:scrollbars="vertical"
|
||||
android:fadeScrollbars="false"/>
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:id="@+id/dv_bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical|right"
|
||||
android:padding="16dp">
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.Material3Expressive.Button.OutlinedButton"
|
||||
android:id="@+id/btn_close"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/close"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?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">
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -3,22 +3,20 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingVertical="16dp"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:padding="16dp"
|
||||
android:id="@+id/main"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:paddingRight="16dp"
|
||||
android:text="TextView" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_check"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/check_24px"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:text="TextView" />
|
||||
</LinearLayout>
|
||||
|
|
@ -508,6 +508,8 @@
|
|||
<string name="hard_disk">Hard disk</string>
|
||||
<string name="floppy_disk">Floppy disk</string>
|
||||
<string name="network">Network</string>
|
||||
<string name="rom_successfully_exported">Rom successfully exported.</string>
|
||||
<string name="options">Options</string>
|
||||
|
||||
|
||||
<!--======================TERMUX STRINGS====================-->
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
"url": "https://github.com/xoureldeen/Vectras-VM-Android/releases",
|
||||
"Message": "<h2>3.6.0</h2>\nBugs fixed.",
|
||||
"cancellable": true,
|
||||
"versionCodeBeta":"70",
|
||||
"versionNameBeta":"3.6.6",
|
||||
"versionNameBetas":"3.0.0,3.1.0,3.2.1,3.2.2,3.2.3,3.2.4,3.2.5,3.2.6,3.2.7,3.2.8,3.2.9,3.2.10,3.3.1,3.3.2,3.3.3,3.3.4,3.3.5,3.3.6,3.3.7,3.3.8,3.3.9,3.4.1,3.4.2,3.4.3,3.4.4,3.4.5,3.4.6,3.4.7,3.4.8,3.4.9,3.5.1,3.5.2,3.5.3,3.5.4,3.5.5,3.5.6,3.5.7,3.5.8,3.5.9,3.6.1,3.6.2,3.6.3,3.6.4,3.6.5,3.6.6",
|
||||
"versionCodeBeta":"71",
|
||||
"versionNameBeta":"3.6.7",
|
||||
"versionNameBetas":"3.0.0,3.1.0,3.2.1,3.2.2,3.2.3,3.2.4,3.2.5,3.2.6,3.2.7,3.2.8,3.2.9,3.2.10,3.3.1,3.3.2,3.3.3,3.3.4,3.3.5,3.3.6,3.3.7,3.3.8,3.3.9,3.4.1,3.4.2,3.4.3,3.4.4,3.4.5,3.4.6,3.4.7,3.4.8,3.4.9,3.5.1,3.5.2,3.5.3,3.5.4,3.5.5,3.5.6,3.5.7,3.5.8,3.5.9,3.6.1,3.6.2,3.6.3,3.6.4,3.6.5,3.6.6,3.6.7",
|
||||
"sizeBeta": "43 MB",
|
||||
"urlBeta": "https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases",
|
||||
"MessageBeta": "<h2>3.6.6</h2>Bugs fixed.",
|
||||
"MessageBeta": "<h2>3.6.7</h2>Bugs fixed.",
|
||||
"cancellableBeta": true
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue