diff --git a/README.md b/README.md index 9c91a4d..d69160f 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ QEMU 9.2.2 - 3dfx (recommended and for Vectras VM 3.5.1+): - [For Android ARM (32-bit)](https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases/download/3.5.4/base-vectras-vm-armeabi-v7a.tar.gz) - [For Android x86 (64-bit)](https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases/download/3.5.1/base-generic-vectras-vm-x86_64.tar.gz) - [For Android x86 (32-bit)](https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases/download/3.5.4/base-vectras-vm-x86.tar.gz) -- [See them at the Internet Archive (64-bit)](https://archive.org/details/vectras-vm-x86_64.tar_202603nbab) +- [See them at the Internet Archive (64-bit)](https://archive.org/details/qemu-9-2-2-3dfx-for-vectras-vm-nbab) - [See them at the Internet Archive (32-bit)](https://archive.org/details/qemu-9-2-2-for-vectras-vm-nbab) QEMU 9.2.2 - 3dfx (for Vectras VM 3.2.9 - 3.4.9): diff --git a/app/build.gradle b/app/build.gradle index 87bf687..e8f09b9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vectras.vm" minSdk minApi targetSdk targetApi - versionCode 93 - versionName "3.8.9" + versionCode 94 + versionName "3.9.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true @@ -78,48 +78,41 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.7.1' - implementation 'com.google.android.material:material:1.14.0-alpha10' - implementation "androidx.annotation:annotation:1.9.1" - implementation "androidx.core:core-ktx:1.18.0" - implementation "androidx.drawerlayout:drawerlayout:1.2.0" - implementation "androidx.preference:preference-ktx:1.2.1" - implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.2.0" - implementation "androidx.viewpager:viewpager:1.1.0" - implementation platform('com.google.firebase:firebase-bom:34.11.0') - implementation 'com.google.firebase:firebase-analytics' -// implementation("com.google.firebase:firebase-auth") -// implementation("com.google.firebase:firebase-database") -// implementation 'com.google.firebase:firebase-storage' -// implementation("com.google.android.gms:play-services-auth:21.4.0") - implementation("com.google.firebase:firebase-crashlytics") -// implementation 'com.google.android.gms:play-services-ads:24.6.0' - implementation 'com.google.firebase:firebase-messaging:25.0.1' - implementation 'com.google.guava:guava:33.5.0-jre' - implementation 'com.google.code.gson:gson:2.13.2' - implementation "androidx.window:window:1.5.1" - implementation "commons-io:commons-io:2.21.0" - implementation 'com.airbnb.android:lottie:6.7.1' - implementation 'org.apache.commons:commons-compress:1.28.0' -// implementation 'com.google.firebase:firebase-crashlytics-buildtools:3.0.6' - implementation 'androidx.activity:activity-ktx:1.13.0' - implementation 'androidx.constraintlayout:constraintlayout:2.2.1' - implementation 'androidx.preference:preference-ktx:1.2.1' - implementation "androidx.documentfile:documentfile:1.1.0" - implementation 'androidx.core:core-ktx:1.18.0' + implementation libs.androidx.appcompat + implementation libs.material + implementation libs.androidx.annotation + implementation libs.androidx.core.ktx + implementation libs.androidx.drawerlayout + implementation libs.androidx.preference.ktx + implementation libs.androidx.swiperefreshlayout + implementation libs.androidx.viewpager + implementation platform(libs.firebase.bom) + implementation libs.firebase.analytics + implementation libs.firebase.messaging + implementation libs.guava + implementation libs.gson + implementation libs.androidx.window + implementation libs.commons.io + implementation libs.lottie + implementation libs.commons.compress + implementation libs.androidx.activity.ktx + implementation libs.androidx.constraintlayout + implementation libs.androidx.preference.ktx + implementation libs.androidx.documentfile + implementation libs.androidx.core.ktx compileOnly project(':shell-loader:stub') implementation project(":terminal-view") // Retrofit - implementation 'com.squareup.retrofit2:retrofit:3.0.0' - implementation 'com.squareup.retrofit2:converter-gson:3.0.0' + implementation libs.retrofit + implementation libs.converter.gson // Glide - implementation 'com.github.bumptech.glide:glide:5.0.5' - annotationProcessor 'com.github.bumptech.glide:compiler:5.0.5' + implementation libs.glide + annotationProcessor libs.compiler // Test dependencies - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.3.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' + testImplementation libs.junit + androidTestImplementation libs.androidx.junit + androidTestImplementation libs.androidx.espresso.core } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ea4f6b6..e8f787e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -258,7 +258,7 @@ diff --git a/app/src/main/java/com/anbui/elephant/interaction/Interaction.java b/app/src/main/java/com/anbui/elephant/interaction/Interaction.java index 4fa3243..8fc1607 100644 --- a/app/src/main/java/com/anbui/elephant/interaction/Interaction.java +++ b/app/src/main/java/com/anbui/elephant/interaction/Interaction.java @@ -2,6 +2,8 @@ package com.anbui.elephant.interaction; import android.content.Context; import android.content.SharedPreferences; +import android.os.Handler; +import android.os.Looper; import androidx.preference.PreferenceManager; @@ -23,6 +25,9 @@ public class Interaction { private final String contentId; private DataInteraction dataInteraction; private final SharedPreferences sharedPreferences; + public boolean isRequesting; + public boolean isAllowAction; + private Runnable waitingAction; public interface InteractionCallback { void onResult(boolean isSuccess, int views, int likes); @@ -44,19 +49,32 @@ public class Interaction { return; } - get(((isSuccess, views, likes) -> view(callback))); + get(((isSuccess, views, likes) -> { + view(callback); + isAllowAction = true; + })); LogPrinter.print(TAG, "Initialized."); } public void get(InteractionCallback callback) { + if (isRequesting) { + LogPrinter.print(TAG, "Busy, there's another connection in view."); + return; + } + if (!isReady()) { callback.onResult(false, 1, 0); LogPrinter.print(TAG, "Not ready in get."); return; } + isRequesting = true; + Retrofit2Utils.get(GET_URL, ((isSuccess, body, status, error) -> { + isRequesting = false; + isAllowAction = true; + if (isSuccess && JSONUtils.isValidFromString(body)) { dataInteraction = new Gson().fromJson(body, DataInteraction.class); callback.onResult(true, dataInteraction.views, dataInteraction.likes); @@ -71,6 +89,12 @@ public class Interaction { private boolean isTryingView; public void view(InteractionCallback callback) { + if (isRequesting || !isAllowAction) { + if (isAllowAction) waitingAction = () -> view(callback); + LogPrinter.print(TAG, "Busy, there's another connection, or the action was blocked due to the action being performed too quickly in view."); + return; + } + if (isViewed()) { callback.onResult(true, dataInteraction != null ? dataInteraction.views : 1, dataInteraction != null ? dataInteraction.likes : 0); LogPrinter.print(TAG, "Viewed."); @@ -95,7 +119,11 @@ public class Interaction { + "\"token\":" + "\"" + dataInteraction.token + "\"" + "}"; + isRequesting = true; + Retrofit2Utils.post(VIEW_URL, jsonRaw, ((isSuccess, body, status, error) -> { + isRequesting = false; + if (isNeedRetry(status) && !isTryingView) { isTryingView = true; get((success, views, likes) -> view(callback)); @@ -115,12 +143,26 @@ public class Interaction { callback.onResult(false, 1, 0); LogPrinter.print(TAG, "View unsucceed."); } + + if (waitingAction != null) { + waitingAction.run(); + waitingAction = null; + } + + isAllowAction = false; + new Handler(Looper.getMainLooper()).postDelayed(() -> isAllowAction = true, 1000); })); } private boolean isTryingLike; public void like(InteractionCallback callback) { + if (isRequesting || !isAllowAction) { + if (isAllowAction) waitingAction = () -> like(callback); + LogPrinter.print(TAG, "Busy, there's another connection, or the action was blocked due to the action being performed too quickly in like."); + return; + } + if (!isReadyToPost() && !isTryingLike) { isTryingLike = true; get((success, views, likes) -> like(callback)); @@ -140,7 +182,11 @@ public class Interaction { + "\"token\":" + "\"" + dataInteraction.token + "\"" + "}"; + isRequesting = true; + Retrofit2Utils.post(LIKE_URL, jsonRaw, ((isSuccess, body, status, error) -> { + isRequesting = false; + if (isNeedRetry(status) && !isTryingLike) { isTryingLike = true; get((success, views, likes) -> like(callback)); @@ -160,6 +206,14 @@ public class Interaction { callback.onResult(false, 1, 0); LogPrinter.print(TAG, "Like unsucceed."); } + + if (waitingAction != null) { + waitingAction.run(); + waitingAction = null; + } + + isAllowAction = false; + new Handler(Looper.getMainLooper()).postDelayed(() -> isAllowAction = true, 1000); })); } @@ -172,11 +226,11 @@ public class Interaction { } public int getViewCount() { - return dataInteraction.views; + return dataInteraction != null ? dataInteraction.views : 1; } public int getLikeCount() { - return dataInteraction.likes; + return dataInteraction != null ? dataInteraction.likes : (isLiked() ? 1 : 0); } private boolean isReady() { diff --git a/app/src/main/java/com/vectras/qemu/utils/RamInfo.java b/app/src/main/java/com/vectras/qemu/utils/RamInfo.java index d43940a..53d76b2 100644 --- a/app/src/main/java/com/vectras/qemu/utils/RamInfo.java +++ b/app/src/main/java/com/vectras/qemu/utils/RamInfo.java @@ -11,8 +11,6 @@ import com.vectras.qemu.MainSettingsManager; import com.vectras.vm.utils.TextUtils; public class RamInfo { - public static Activity activity; - public static int safeLongToInt(long l) { if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) { throw new IllegalArgumentException(l + " cannot be cast to int without changing its value."); @@ -31,6 +29,11 @@ public class RamInfo { int totalRamInt = safeLongToInt(totalMem); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); if (prefs.getBoolean("customMemory", false) && TextUtils.isNumberOnly(prefs.getString("memory", String.valueOf(256)))) { + if (Long.parseLong(prefs.getString("memory", String.valueOf(256))) > totalMem) { + prefs.edit().putInt("memory", totalRamInt / 2).apply(); + return totalRamInt / 2; + } + return Integer.parseInt(prefs.getString("memory", String.valueOf(256))); } else { return freeRamInt - 100; diff --git a/app/src/main/java/com/vectras/vm/ExportRomActivity.java b/app/src/main/java/com/vectras/vm/ExportRomActivity.java index ac49076..8ff74e8 100644 --- a/app/src/main/java/com/vectras/vm/ExportRomActivity.java +++ b/app/src/main/java/com/vectras/vm/ExportRomActivity.java @@ -141,7 +141,7 @@ public class ExportRomActivity extends AppCompatActivity { vmConfigMap.put("bootFrom", current.bootFrom); vmConfigMap.put("isShowBootMenu", current.isShowBootMenu); - vmConfigMap.put("qemu", current.itemExtra.replace(AppConfig.vmFolder + current.vmID + "/", "OhnoIjustrealizeditsmidnightandIstillhavetodothis")); + vmConfigMap.put("qemu", current.itemExtra.replace(getRomPath, "OhnoIjustrealizeditsmidnightandIstillhavetodothis")); vmConfigMap.put("arch", current.itemArch); if (Objects.requireNonNull(binding.edAuthor.getText()).toString().isEmpty()) { @@ -173,8 +173,14 @@ public class ExportRomActivity extends AppCompatActivity { if (_filelist.get(_repeat).endsWith("rom-data.json")) { filePaths[filePaths.length - 1] = tempFolder + "rom-data.json"; + } else if (_filelist.get(_repeat).endsWith("snapshot.sh")) { + String snapshotParams = FileUtils.readAFile(_filelist.get(_repeat)); + snapshotParams = StartVM.removeQmpParams(snapshotParams); + snapshotParams = StartVM.removeDisplayParams(snapshotParams); + FileUtils.writeToFile(tempFolder, "snapshot.sh", snapshotParams.replace(getRomPath, "OhnoIjustrealizeditsmidnightandIstillhavetodothis")); + filePaths[filePaths.length - 1] = tempFolder + "snapshot.sh"; } else if (_filelist.get(_repeat).endsWith("cqcm.json")) { - FileUtils.writeToFile(tempFolder, "cqcm.json", FileUtils.readAFile(_filelist.get(_repeat)).replace(AppConfig.vmFolder + current.vmID + "/", "OhnoIjustrealizeditsmidnightandIstillhavetodothis")); + FileUtils.writeToFile(tempFolder, "cqcm.json", FileUtils.readAFile(_filelist.get(_repeat)).replace(getRomPath, "OhnoIjustrealizeditsmidnightandIstillhavetodothis")); filePaths[filePaths.length - 1] = tempFolder + "cqcm.json"; } else { filePaths[filePaths.length - 1] = _filelist.get(_repeat); diff --git a/app/src/main/java/com/vectras/vm/FCMService.java b/app/src/main/java/com/vectras/vm/FCMService.java deleted file mode 100644 index e289824..0000000 --- a/app/src/main/java/com/vectras/vm/FCMService.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.vectras.vm; - -import androidx.annotation.NonNull; - -import com.google.firebase.messaging.FirebaseMessagingService; -import com.google.firebase.messaging.RemoteMessage; -import com.vectras.vm.utils.NotificationUtils; - -import java.util.Map; - -public class FCMService - extends FirebaseMessagingService { - @Override - public void onMessageReceived(@NonNull RemoteMessage message) { - - if (message.getNotification() != null) { - String title = message.getNotification().getTitle(); - String body = message.getNotification().getBody(); - String image = message.getNotification().getImageUrl() != null ? message.getNotification().getImageUrl().toString() : null; - - Map data = message.getData(); - - NotificationUtils.pushNow(this, - 1, - NotificationUtils.generalChannelId, - title, - body, - R.drawable.ic_vectras_vm_48, - image, - -1, - data.get("url"), - null); - } - } - - @Override - public void onNewToken(@NonNull String token) { - - } -} diff --git a/app/src/main/java/com/vectras/vm/RomInfo.java b/app/src/main/java/com/vectras/vm/RomInfo.java index 48bf877..9a9fa83 100644 --- a/app/src/main/java/com/vectras/vm/RomInfo.java +++ b/app/src/main/java/com/vectras/vm/RomInfo.java @@ -335,6 +335,8 @@ public class RomInfo extends AppCompatActivity { } private void sendLikeUpdate() { + if (interaction.isRequesting || !interaction.isAllowAction) return; + binding.btnLike.setIcon(ContextCompat.getDrawable(RomInfo.this, !interaction.isLiked() ? R.drawable.thumb_up_filled_24px : R.drawable.thumb_up_24px)); binding.btnLike.setText(!interaction.isLiked() ? getString(R.string.liked) : getString(R.string.like)); diff --git a/app/src/main/java/com/vectras/vm/StartVM.java b/app/src/main/java/com/vectras/vm/StartVM.java index a52b781..ce112a7 100644 --- a/app/src/main/java/com/vectras/vm/StartVM.java +++ b/app/src/main/java/com/vectras/vm/StartVM.java @@ -1,6 +1,8 @@ package com.vectras.vm; import android.app.Activity; +import android.content.Context; +import android.util.Log; import com.vectras.qemu.Config; import com.vectras.qemu.MainSettingsManager; @@ -8,6 +10,7 @@ import com.vectras.qemu.utils.RamInfo; import com.vectras.vm.creator.VMCreatorSelector; import com.vectras.vm.main.vms.DataMainRoms; import com.vectras.vm.utils.FileUtils; +import com.vectras.vm.utils.TextUtils; import java.io.File; import java.util.ArrayList; @@ -20,6 +23,21 @@ public class StartVM { public static String cdrompath; public static String env(Activity activity, DataMainRoms vmData) { + if (VMManager.isNeedLoadMigrate() && FileUtils.isFileExists(AppConfig.vmFolder + Config.vmID + "/snapshot.sh")) { + String snapshotParams = FileUtils.readAFile(AppConfig.vmFolder + Config.vmID + "/snapshot.sh").replace("\n", ""); + if (VMManager.isthiscommandsafe(snapshotParams, activity)) { + snapshotParams = removeQemuSystem(snapshotParams); + snapshotParams = removeQmpParams(snapshotParams); + snapshotParams = removeDisplayParams(snapshotParams); + snapshotParams = getQmpParams() + " " + snapshotParams; + snapshotParams = getQemuSystem(activity) + " " + snapshotParams; + snapshotParams += " " + getDisplayParams(activity); + if (!snapshotParams.contains("-incoming defer")) snapshotParams += " -incoming defer"; + Log.d("StartVM.env", snapshotParams); + return snapshotParams; + } + } + String extraParams = vmData.itemExtra; String bootFromParams = Objects.requireNonNull(VMCreatorSelector.getBootFrom(activity, vmData.bootFrom).get("value")).toString(); @@ -56,8 +74,7 @@ public class StartVM { else if (MainSettingsManager.getArch(activity).equals("PPC")) params.add("qemu-system-ppc"); - params.add("-qmp"); - params.add("unix:" + Config.getLocalQMPSocketPath() + ",server,nowait"); + params.add(getQmpParams()); String ifType; ifType= MainSettingsManager.getIfType(activity); @@ -252,47 +269,10 @@ public class StartVM { params.add(finalextra); if (isQuickRun) { - params.add("-qmp"); - params.add("unix:" + Config.getLocalQMPSocketPath() + ",server,nowait"); + params.add(getQmpParams()); } - if (MainSettingsManager.getVmUi(activity).equals("VNC")) { - - if (!MainSettingsManager.getVncExternalPassword(activity).isEmpty()) { - params.add("-object "); - params.add("secret,id=vncpass,data=\"" + MainSettingsManager.getVncExternalPassword(activity) + "\""); - } - - String vncStr = "-vnc "; - params.add(vncStr); - // Allow connections only from localhost using localsocket without a password - if (MainSettingsManager.getVncExternal(activity)) { - - String vncParams = Config.defaultVNCHost + ":" + Config.defaultVNCPort; - - if (!MainSettingsManager.getVncExternalPassword(activity).isEmpty()) vncParams += ",password-secret=vncpass"; - - params.add(vncParams); - } else { - String vncSocketParams = "unix:"; - vncSocketParams += Config.getLocalVNCSocketPath(); - params.add(vncSocketParams); - } - - //if (!MainSettingsManager.getArch(activity).equals("PPC") || !MainSettingsManager.getArch(activity).equals("ARM64")) { - params.add("-monitor"); - params.add("vc"); - //} - } else if (MainSettingsManager.getVmUi(activity).equals("SPICE")) { - String spiceStr = "-spice "; - spiceStr += "port=6999,disable-ticketing=on"; - params.add(spiceStr); - } else if (MainSettingsManager.getVmUi(activity).equals("X11")) { - params.add("-display"); - params.add(MainSettingsManager.getUseSdl(activity) ? "sdl" : "gtk" + ",gl=on"); - params.add("-monitor"); - params.add(MainSettingsManager.getRunQemuWithXterm(activity) ? "stdio" : "vc"); - } + params.add(getDisplayParams(activity)); if (VMManager.isNeedLoadMigrate()) { params.add("-incoming"); @@ -302,4 +282,77 @@ public class StartVM { return String.join(" ", params); } + public static String getQemuSystem(Context context) { + if (MainSettingsManager.getArch(context).equals("I386")) + return "qemu-system-i386"; + else if (MainSettingsManager.getArch(context).equals("X86_64")) + return "qemu-system-x86_64"; + else if (MainSettingsManager.getArch(context).equals("ARM64")) + return "qemu-system-aarch64"; + else if (MainSettingsManager.getArch(context).equals("PPC")) + return "qemu-system-ppc"; + + return "qemu-system-x86_64"; + } + + public static String removeQemuSystem(String params) { + return params.replaceAll("^qemu-system-\\S+\\s*", ""); + } + + + public static String getQmpParams() { + return "-qmp unix:" + Config.getLocalQMPSocketPath() + ",server,nowait"; + } + public static String removeQmpParams(String params) { + return params.replaceAll("(?<=\\s|^)-qmp\\s+(\"[^\"]+\"|\\S+)", ""); + } + + + public static String getDisplayParams(Context context) { + String params = ""; + + if (MainSettingsManager.getVmUi(context).equals("VNC")) { + if (!MainSettingsManager.getVncExternalPassword(context).isEmpty()) { + params += "-object secret,id=vncpass,data=\"" + MainSettingsManager.getVncExternalPassword(context) + "\""; + } + + params += (params.isEmpty() ? "" : " ") + "-vnc "; + // Allow connections only from localhost using localsocket without a password + if (MainSettingsManager.getVncExternal(context)) { + String vncParams = Config.defaultVNCHost + ":" + Config.defaultVNCPort; + + if (!MainSettingsManager.getVncExternalPassword(context).isEmpty()) + vncParams += ",password-secret=vncpass"; + + params += vncParams; + } else { + params += "unix:" + Config.getLocalVNCSocketPath(); + } + + params += " -monitor vc"; + } else if (MainSettingsManager.getVmUi(context).equals("SPICE")) { + params += "-spice port=6999,disable-ticketing=on"; + } else if (MainSettingsManager.getVmUi(context).equals("X11")) { + params += "-display "; + params += MainSettingsManager.getUseSdl(context) ? "sdl" : "gtk" + ",gl=on"; + params += " -monitor "; + params += MainSettingsManager.getRunQemuWithXterm(context) ? "stdio" : "vc"; + } + + return params; + } + + public static String removeDisplayParams(String params) { + return params + .replaceAll("(?<=\\s|^)-object\\s+secret,id=vncpass[^\\s]*", "") + .replaceAll("(?<=\\s|^)-vnc\\b.*?(?=\\s+-monitor\\s+\\S+|$)", "") + .replaceAll("(?<=\\s|^)-vnc\\b[^\\s]*", "") + .replaceAll("(?<=\\s|^)-monitor\\s+\\S+", "") + .replaceAll("(?<=\\s|^)-display\\b.*?(?=\\s+-monitor\\s+\\S+|$)", "") + .replaceAll("(?<=\\s|^)-display\\s+\\S+", "") + .replaceAll("(?<=\\s|^)-spice\\s+\\S+", "") + .replaceAll("\\s{2,}", " ") + .trim(); + } + } diff --git a/app/src/main/java/com/vectras/vm/VMManager.java b/app/src/main/java/com/vectras/vm/VMManager.java index c4c6ece..caca7db 100644 --- a/app/src/main/java/com/vectras/vm/VMManager.java +++ b/app/src/main/java/com/vectras/vm/VMManager.java @@ -918,9 +918,15 @@ public class VMManager { progressDialog.show(); new Thread(() -> { pauseCurrentVM(); - if (!startMigrate().contains("terminal does not allow synchronous migration, continuing detached")) { + + String migrateResult = startMigrate(); + + if (migrateResult == null || !migrateResult.contains("terminal does not allow synchronous migration, continuing detached")) { resumeCurrentVM(); - _activity.runOnUiThread(progressDialog::reset); + _activity.runOnUiThread(() -> { + DialogUtils.oopsDialog(_activity, _activity.getString(R.string.vm_state_save_failed_note)); + progressDialog.reset(); + }); Log.e(TAG, "Pause VM failed."); return; } diff --git a/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java b/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java index 3d82750..4299bcb 100644 --- a/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java +++ b/app/src/main/java/com/vectras/vm/creator/VMCreatorActivity.java @@ -39,6 +39,7 @@ import com.vectras.vm.utils.DeviceUtils; import com.vectras.vm.utils.DialogUtils; import com.vectras.vm.utils.FileUtils; import com.vectras.vm.utils.ImageUtils; +import com.vectras.vm.utils.JSONUtils; import com.vectras.vm.utils.PackageUtils; import com.vectras.vm.utils.UIUtils; @@ -989,6 +990,11 @@ public class VMCreatorActivity extends AppCompatActivity { } } } else { + if (!JSONUtils.isValidFromFile(AppConfig.vmFolder + vmID + "/rom-data.json")) { + DialogUtils.oneDialog(this, getResources().getString(R.string.oops), getResources().getString(R.string.error_CR_CVBI4), getResources().getString(R.string.ok), true, R.drawable.warning_48px, true, null, null); + return; + } + loadConfig(new Gson().fromJson(FileUtils.readFromFile(this, new File(AppConfig.vmFolder + vmID + "/rom-data.json")), DataMainRoms.class)); JSONObject jObj = new JSONObject(FileUtils.readFromFile(this, new File(AppConfig.vmFolder + vmID + "/rom-data.json"))); @@ -1028,7 +1034,11 @@ public class VMCreatorActivity extends AppCompatActivity { } if (FileUtils.isFileExists(AppConfig.vmFolder + vmID + "/cqcm.json")) { - FileUtils.writeToFile(AppConfig.vmFolder + vmID, "cqcm.json", FileUtils.readAFile(AppConfig.vmFolder + vmID + "/cqcm.json").replace("OhnoIjustrealizeditsmidnightandIstillhavetodothis", AppConfig.vmFolder + current.vmID + "/")); + FileUtils.writeToFile(AppConfig.vmFolder + vmID, "cqcm.json", FileUtils.readAFile(AppConfig.vmFolder + vmID + "/cqcm.json").replace("OhnoIjustrealizeditsmidnightandIstillhavetodothis", AppConfig.vmFolder + vmID + "/")); + } + + if (FileUtils.isFileExists(AppConfig.vmFolder + vmID + "/snapshot.sh")) { + FileUtils.writeToFile(AppConfig.vmFolder + vmID, "snapshot.sh", FileUtils.readAFile(AppConfig.vmFolder + vmID + "/snapshot.sh").replace("OhnoIjustrealizeditsmidnightandIstillhavetodothis", AppConfig.vmFolder + vmID + "/")); } } diff --git a/app/src/main/java/com/vectras/vm/creator/VMCreatorSelector.java b/app/src/main/java/com/vectras/vm/creator/VMCreatorSelector.java index d644477..32e0cf7 100644 --- a/app/src/main/java/com/vectras/vm/creator/VMCreatorSelector.java +++ b/app/src/main/java/com/vectras/vm/creator/VMCreatorSelector.java @@ -5,6 +5,7 @@ import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; @@ -12,6 +13,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.google.android.material.color.MaterialColors; import com.vectras.vm.R; import com.vectras.vm.databinding.DialogListSelectorLayoutBinding; import com.vectras.vm.databinding.SimpleLayoutListViewWithCheckBinding; @@ -96,9 +98,11 @@ public class VMCreatorSelector { public void onBindViewHolder(ViewHolder holder, final int position) { View view = holder.itemView; TextView title = view.findViewById(R.id.textview); + ImageView check = view.findViewById(R.id.iv_check); title.setText(Objects.requireNonNull(data.get(position).get("name")).toString()); + title.setTextColor(MaterialColors.getColor(title, position == currentPosition ? androidx.appcompat.R.attr.colorPrimary : com.google.android.material.R.attr.colorOnSurface)); view.setBackgroundResource(position == currentPosition ? R.drawable.dialog_shape_single_button : R.drawable.dialog_shape_click_effect_button); - view.findViewById(R.id.iv_check).setVisibility(position == currentPosition ? View.VISIBLE : View.INVISIBLE); + check.setVisibility(position == currentPosition ? View.VISIBLE : View.INVISIBLE); view.findViewById(R.id.main).setOnClickListener(v -> { if (activity.isFinishing() || activity.isDestroyed()) return; callback.onSelected( diff --git a/app/src/main/java/com/vectras/vm/fcm/FCMManager.java b/app/src/main/java/com/vectras/vm/fcm/FCMManager.java new file mode 100644 index 0000000..429a0fd --- /dev/null +++ b/app/src/main/java/com/vectras/vm/fcm/FCMManager.java @@ -0,0 +1,33 @@ +package com.vectras.vm.fcm; + +import android.util.Log; + +import com.google.firebase.messaging.FirebaseMessaging; + +public class FCMManager { + private static final String TAG = "FCMManager"; + + public static void subscribe() { + FirebaseMessaging.getInstance() + .subscribeToTopic("vectrasvmandroidgithub") + .addOnCompleteListener(task -> { + if (task.isSuccessful()) { + Log.d(TAG, "Subscribed: vectrasvmandroidgithub"); + } else { + Log.e(TAG, "Subscription failed: vectrasvmandroidgithub.", task.getException()); + } + }); + } + + public static void unSubscribe() { + FirebaseMessaging.getInstance() + .unsubscribeFromTopic("vectrasvmandroidgithub") + .addOnCompleteListener(task -> { + if (task.isSuccessful()) { + Log.d(TAG, "Unsubscribed: vectrasvmandroidgithub"); + } else { + Log.e(TAG, "Cancellation failed: vectrasvmandroidgithub.", task.getException()); + } + }); + } +} diff --git a/app/src/main/java/com/vectras/vm/fcm/FCMService.java b/app/src/main/java/com/vectras/vm/fcm/FCMService.java new file mode 100644 index 0000000..c8a8db3 --- /dev/null +++ b/app/src/main/java/com/vectras/vm/fcm/FCMService.java @@ -0,0 +1,34 @@ +package com.vectras.vm.fcm; + +import androidx.annotation.NonNull; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; +import com.vectras.vm.R; +import com.vectras.vm.utils.NotificationUtils; + +import java.util.Map; + +public class FCMService + extends FirebaseMessagingService { + @Override + public void onMessageReceived(@NonNull RemoteMessage message) { + Map data = message.getData(); + + NotificationUtils.pushNow(this, + 1, + NotificationUtils.generalChannelId, + data.get("title") != null ? data.get("title") : getString(R.string.new_notification), + data.get("message") != null ? data.get("message") : getString(R.string.tap_to_view), + R.drawable.ic_vectras_vm_48, + data.get("image") != null ? data.get("image") : null, + -1, + data.get("url") != null ? data.get("url") : null, + null); + } + + @Override + public void onNewToken(@NonNull String token) { + + } +} diff --git a/app/src/main/java/com/vectras/vm/main/MainActivity.java b/app/src/main/java/com/vectras/vm/main/MainActivity.java index 4c2f7e5..add3e40 100644 --- a/app/src/main/java/com/vectras/vm/main/MainActivity.java +++ b/app/src/main/java/com/vectras/vm/main/MainActivity.java @@ -50,6 +50,7 @@ import com.vectras.vm.WebViewActivity; import com.vectras.vm.databinding.ActivityMainBinding; import com.vectras.vm.databinding.ActivityMainContentBinding; import com.vectras.vm.databinding.UpdateBottomDialogLayoutBinding; +import com.vectras.vm.fcm.FCMManager; import com.vectras.vm.main.romstore.RomStoreHomeAdpater; import com.vectras.vm.main.softwarestore.SoftwareStoreFragment; import com.vectras.vm.main.softwarestore.SoftwareStoreHomeAdapter; @@ -352,6 +353,8 @@ public class MainActivity extends AppCompatActivity implements RomStoreFragment. updateApp(); NotificationUtils.requestPermission(this); + + FCMManager.subscribe(); } @Override diff --git a/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java b/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java index 748547b..648519b 100644 --- a/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java +++ b/app/src/main/java/com/vectras/vm/main/core/MainStartVM.java @@ -316,6 +316,8 @@ public class MainStartVM { activity.runOnUiThread(() -> DisplaySystem.launch(context)); } + FileUtils.writeToFile(AppConfig.vmFolder + vmID, "snapshot.sh", env); + Log.i(TAG, "Virtual machine running."); if (activity != null) { diff --git a/app/src/main/java/com/vectras/vm/utils/FileUtils.java b/app/src/main/java/com/vectras/vm/utils/FileUtils.java index 2db2c8f..55f3970 100644 --- a/app/src/main/java/com/vectras/vm/utils/FileUtils.java +++ b/app/src/main/java/com/vectras/vm/utils/FileUtils.java @@ -1000,11 +1000,29 @@ public class FileUtils { return; } - Uri uri = FileProvider.getUriForFile( - context, - context.getPackageName() + ".provider", - folder - ); + Uri uri; + + try { + uri = FileProvider.getUriForFile( + context, + context.getPackageName() + ".provider", + folder + ); + } catch (IllegalArgumentException e) { + DialogUtils.oneDialog( + context, + context.getString(R.string.oops), + context.getString(R.string.this_folder_cannot_be_opened), + context.getString(R.string.ok), + true, + R.drawable.error_96px, + true, + null, + null + ); + Log.e(TAG, "openFolder: Folder not found!"); + return; + } Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(uri, "resource/folder"); diff --git a/app/src/main/java/com/vectras/vm/utils/JSONUtils.java b/app/src/main/java/com/vectras/vm/utils/JSONUtils.java index 10dba1e..6953428 100644 --- a/app/src/main/java/com/vectras/vm/utils/JSONUtils.java +++ b/app/src/main/java/com/vectras/vm/utils/JSONUtils.java @@ -1,11 +1,14 @@ package com.vectras.vm.utils; import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import com.vectras.vm.AppConfig; +import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.Objects; @@ -28,11 +31,24 @@ public class JSONUtils { } } + public static boolean isValidArray(String content) { + if (content == null || content.trim().isEmpty()) { + return false; + } + + try { + return JsonParser.parseString(content).isJsonArray(); + } catch (Exception e) { + return false; + } + } + public static boolean isValidVmList() { try { - ArrayList> vmList = new Gson().fromJson(FileUtils.readAFile(AppConfig.romsdatajson), new TypeToken>>() { + String jsonRaw = FileUtils.readAFile(AppConfig.romsdatajson); + ArrayList> vmList = new Gson().fromJson(jsonRaw, new TypeToken>>() { }.getType()); - return true; + return isValidArray(jsonRaw); } catch (Exception e) { return false; } diff --git a/app/src/main/java/com/vectras/vm/utils/TarUtils.java b/app/src/main/java/com/vectras/vm/utils/TarUtils.java index 297bc4e..8218a2c 100644 --- a/app/src/main/java/com/vectras/vm/utils/TarUtils.java +++ b/app/src/main/java/com/vectras/vm/utils/TarUtils.java @@ -92,7 +92,7 @@ public class TarUtils { File canonicalDestDir = tarTestDir.getCanonicalFile(); long MAX_FILE_SIZE = 200L * 1024 * 1024; // 200 MB - long MAX_TOTAL_SIZE = 2000L * 1024 * 1024; // 2000 MB + long MAX_TOTAL_SIZE = 4000L * 1024 * 1024; // 4000 MB int MAX_FILE_COUNT = 10_000; long totalSize = 0; diff --git a/app/src/main/java/com/vectras/vm/utils/TextUtils.java b/app/src/main/java/com/vectras/vm/utils/TextUtils.java index 589a954..155ba03 100644 --- a/app/src/main/java/com/vectras/vm/utils/TextUtils.java +++ b/app/src/main/java/com/vectras/vm/utils/TextUtils.java @@ -7,6 +7,10 @@ public class TextUtils { return content.matches("\\d+"); } + public static boolean isUnique(String content, String text) { + return content.contains(text) && (content.indexOf(text) == content.lastIndexOf(text)); + } + public static String randomALetter() { String addAdb; Random random = new Random(); diff --git a/app/src/main/java/com/vectras/vm/x11/X11Activity.java b/app/src/main/java/com/vectras/vm/x11/X11Activity.java index 49b8cb1..1f01eed 100644 --- a/app/src/main/java/com/vectras/vm/x11/X11Activity.java +++ b/app/src/main/java/com/vectras/vm/x11/X11Activity.java @@ -462,11 +462,10 @@ public class X11Activity extends AppCompatActivity implements View.OnApplyWindow dialog.dismiss(); }); - if (!isFinishing() - && !isDestroyed() - && getWindow() != null - && getWindow().getDecorView().isShown()) { + try { dialog.show(); + } catch (WindowManager.BadTokenException e) { + e.printStackTrace(); } }); diff --git a/app/src/main/res/layout/dialog_list_selector_layout.xml b/app/src/main/res/layout/dialog_list_selector_layout.xml index 2239ef1..365ea2d 100644 --- a/app/src/main/res/layout/dialog_list_selector_layout.xml +++ b/app/src/main/res/layout/dialog_list_selector_layout.xml @@ -38,7 +38,7 @@ android:gravity="center_vertical|end" android:padding="16dp"> + android:src="@drawable/check_24px" + app:tint="?attr/colorPrimary"/> \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 474c5fc..66d53a8 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -197,6 +197,7 @@ Đã xảy ra sự cố. Rom này không chứa đầy đủ thông tin và một số cài đặt mặc định đã được áp dụng. Mã lỗi: CR_CVBI2. Từ Đã xảy ra lỗi và không thể tiếp tục. Rom này không chứa đầy đủ thông tin và cũng không chứa các tệp ổ đĩa. Mã lỗi: CR_CVBI3. + Đã xảy ra sự cố. Thông tin của rom này đã bị hỏng và một số cài đặt mặc định đã được áp dụng. Mã lỗi: CR_CVBI4. Dọn dẹp Chà có chút bừa bộn nhỉ? Các rom, tệp và thư mục không còn được sử dụng sẽ bị xoá. Đến cửa hàng rom @@ -511,13 +512,16 @@ Đã xảy ra lỗi khi xoá máy ảo. Các tệp của máy ảo đã được giữ lại vì chúng vẫn còn được sử dụng ở nơi khác. Tạm dừng - Trạng thái của máy ảo sẽ được lưu lại và tắt để tiết kiệm tài nguyên. Nó sẽ tiếp tục ở trạng thái này ở lần khởi động tiếp theo. Lưu ý rằng nếu bạn không bật đặt dung lượng bộ nhớ tuỳ chỉnh trong cài đặt bạn nên đặt dung lượng bộ nhớ ở tham số Qemu riêng cho máy ảo này, không được sửa máy ảo này hoặc các cài đặt Qemu ở trong cài đặt của Vectras VM có thể ảnh hưởng đến máy ảo này cho đến lần khởi động tiếp theo. Bất kỳ thay đổi nào cũng có thể khiến việc tải trạng thái máy ảo thất bại. + Trạng thái của máy ảo sẽ được lưu lại và tắt để tiết kiệm tài nguyên. Nó sẽ tiếp tục ở trạng thái này ở lần khởi động tiếp theo. Vui lòng không thay đổi, di chuyển hoặc xoá các tệp được dùng với máy ảo này cho đến lần khởi động tiếp theo. Đang tạm dừng máy ảo…\nĐừng rời khỏi đây. Đang tiếp tục… Đã xảy ra sự cố khi tải trạng thái máy ảo đã tạm dừng trước đó để tiếp tục dùng. Nếu bạn đã chỉnh sửa máy ảo này hoặc các cài đặt Qemu trong cài đặt của Vectras VM trước khi khởi động, hãy kiểm tra sửa chúng giống như lúc lần cuối cùng máy ảo này được bật trước đó. Bạn muốn giữ hay xoá bản lưu trạng thái? Giữ và tiếp tục Xoá và tiếp tục Đã xảy ra lỗi và không thể lưu lại trạng thái của máy ảo. Hãy thử lại sau. + Không thể mở thư mục này. + Thông báo mới + Chạm để xem. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ab48810..34968e2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -206,6 +206,7 @@ An error has occurred. Extraction was successful but the cvbi file does not contain information about this ROM so some default settings will be used. Error code: CR_CVBI2. From An error has occurred and cannot continue. The extraction was successful but the cvbi file does not contain information for this ROM and does not contain any ROM files. Error code: CR_CVBI3. + An error has occurred. The ROM information is corrupted and some default settings have been applied. Error code: CR_CVBI4. Clean up ROMs, files and folders that are no longer in use will be deleted. Go to ROM store @@ -520,13 +521,16 @@ An error occurred while deleting the virtual machine. The virtual machine\'s files were retained because they are still being used elsewhere. Pause - The virtual machine\'s state will be saved and shut down to conserve resources. It will remain in this state on the next boot. Note that if you haven\'t enabled custom memory allocation in your settings, you should set the memory allocation in the Qemu parameter specifically for this virtual machine. Do not modify this virtual machine or its Qemu settings within the Vectras VM settings, as this may affect the virtual machine until the next boot. Any changes may cause the virtual machine state to fail to load. + The virtual machine\'s state will be saved and shut down to conserve resources. It will remain in this state on the next boot. Please do not change, move, or delete files used with this virtual machine. VM is pausing…\nDon\'t leave here. Resuming… An issue occurred while resuming loading of previously paused virtual machine state. If you modified this virtual machine or Qemu settings in the Vectras VM setup before booting, please check that you changed them to the same settings as the last time this virtual machine was powered on. Do you want to keep or remove the saved state? Keep and continue Remove and continue An error occurred and the virtual machine\'s state could not be saved. Please try again later. + This folder cannot be opened. + New notification + Tap to view. diff --git a/build.gradle b/build.gradle index c6b29a3..768f8e5 100644 --- a/build.gradle +++ b/build.gradle @@ -9,16 +9,16 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:9.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath libs.gradle + classpath libs.kotlin.gradle.plugin // Android Gradle plugin // Add other classpaths like Google Services and Firebase Crashlytics if needed here. - classpath 'com.google.gms:google-services:4.4.4' // assuming you need it for your project - classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.6' + classpath libs.google.services // assuming you need it for your project + classpath libs.firebase.crashlytics.gradle // assuming you need it for project } } -task clean(type: Delete) { +tasks.register('clean', Delete) { delete rootProject.buildDir } diff --git a/gradle.properties b/gradle.properties index cb97f60..97f5c06 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,5 @@ SDK_VERSION=21 android.useAndroidX=true android.uniquePackageNames=false android.dependency.useConstraints=true -android.r8.strictFullModeForKeepRules=false \ No newline at end of file +android.r8.strictFullModeForKeepRules=false +android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..ed47105 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,76 @@ +[versions] +activityKtx = "1.13.0" +agp = "9.1.0" +annotation = "1.9.1" +appcompat = "1.7.1" +comGoogleFirebase = "firebase-crashlytics" +commonsCompress = "1.28.0" +commonsIo = "2.21.0" +constraintlayout = "2.2.1" +coreKtx = "1.18.0" +documentfile = "1.1.0" +drawerlayout = "1.2.0" +espressoCore = "3.7.0" +firebaseBom = "34.11.0" +firebaseCrashlyticsGradle = "3.0.6" +firebaseMessaging = "25.0.1" +glide = "5.0.5" +googleFirebase = "firebase-analytics" +googleFirebaseCrashlytics = "3.0.6" +googleGmsGoogleServices = "4.4.4" +googleServices = "4.4.4" +gradle = "9.1.0" +gradleToolchainsFoojayResolverConvention = "1.0.0" +gson = "2.13.2" +guava = "33.5.0-jre" +junit = "4.13.2" +junitVersion = "1.3.0" +kotlinGradlePlugin = "2.3.20" +lottie = "6.7.1" +material = "1.14.0-alpha10" +preferenceKtx = "1.2.1" +retrofit2 = "3.0.0" +swiperefreshlayout = "1.2.0" +viewpager = "1.1.0" +window = "1.5.1" + +[libraries] +androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" } +androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } +androidx-drawerlayout = { group = "androidx.drawerlayout", name = "drawerlayout", version.ref = "drawerlayout" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preferenceKtx" } +androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" } +androidx-viewpager = { group = "androidx.viewpager", name = "viewpager", version.ref = "viewpager" } +androidx-window = { group = "androidx.window", name = "window", version.ref = "window" } +commons-compress = { group = "org.apache.commons", name = "commons-compress", version.ref = "commonsCompress" } +commons-io = { group = "commons-io", name = "commons-io", version.ref = "commonsIo" } +compiler = { group = "com.github.bumptech.glide", name = "compiler", version.ref = "glide" } +converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit2" } +firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" } +firebase-crashlytics-gradle = { group = "com.google.firebase", name = "firebase-crashlytics-gradle", version.ref = "firebaseCrashlyticsGradle" } +firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging", version.ref = "firebaseMessaging" } +firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" } +glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" } +google-services = { group = "com.google.gms", name = "google-services", version.ref = "googleServices" } +gradle = { group = "com.android.tools.build", name = "gradle", version.ref = "gradle" } +gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } +guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlinGradlePlugin" } +lottie = { group = "com.airbnb.android", name = "lottie", version.ref = "lottie" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit2" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +com-google-firebase = { id = "com.google.firebase", version.ref = "comGoogleFirebase" } +google-firebase = { id = "com.google.firebase", version.ref = "googleFirebase" } +google-firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "googleFirebaseCrashlytics" } +google-gms-google-services = { id = "com.google.gms.google-services", version.ref = "googleGmsGoogleServices" } +foojay-resolver = { id = "org.gradle.toolchains.foojay-resolver-convention", version.ref = "gradleToolchainsFoojayResolverConvention" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 92ed943..8e61ef1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=60ea723356d81263e8002fec0fcf9e2b0eee0c0850c7a3d7ab0a63f2ccc601f3 -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip +distributionSha256Sum=2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/js/push_notification.js b/js/push_notification.js new file mode 100644 index 0000000..70cb5bc --- /dev/null +++ b/js/push_notification.js @@ -0,0 +1,25 @@ +import admin from "firebase-admin"; +import serviceAccount from './*-*-firebase-adminsdk-*-*.json' with { type: 'json' }; +admin.initializeApp({ + credential: admin.credential.cert(serviceAccount) +}); + +async function sendNotification() { + try { + const res = await admin.messaging().send({ + data: { + title: "Vectras VM", + message: "Welcome!", + image: "https://github.com/xoureldeen/Vectras-VM-Android/blob/master/resources/vectrasvm.png", + url : "https://github.com/xoureldeen/Vectras-VM-Android" + }, + topic: "vectrasvmandroidgithub" + }); + + console.log("Sent:", res); + } catch (err) { + console.error("Error:", err); + } +} + +sendNotification(); \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index bf2c562..7c21802 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' + id "org.gradle.toolchains.foojay-resolver-convention" version "1.0.0" } rootProject.name = "Vectras VM" diff --git a/shell-loader/stub/build.gradle b/shell-loader/stub/build.gradle index ca95e2e..91a3fdf 100644 --- a/shell-loader/stub/build.gradle +++ b/shell-loader/stub/build.gradle @@ -31,5 +31,5 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.9.1' + implementation libs.androidx.annotation } diff --git a/terminal-emulator/build.gradle b/terminal-emulator/build.gradle index 061b06a..fa4f01c 100644 --- a/terminal-emulator/build.gradle +++ b/terminal-emulator/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'maven-publish' android { namespace "com.termux.terminal" - compileSdkVersion 34 + compileSdkVersion 36 buildTypes { release { @@ -33,14 +33,14 @@ android { } -tasks.withType(Test) { +tasks.withType(Test).configureEach { testLogging { events "started", "passed", "skipped", "failed" } } dependencies { - testImplementation 'junit:junit:4.13.2' + testImplementation libs.junit } diff --git a/terminal-view/build.gradle b/terminal-view/build.gradle index 57b8f2a..7243dda 100644 --- a/terminal-view/build.gradle +++ b/terminal-view/build.gradle @@ -3,10 +3,10 @@ apply plugin: 'maven-publish' android { namespace "com.termux.view" - compileSdkVersion 34 + compileSdkVersion 36 dependencies { - implementation "androidx.annotation:annotation:1.9.1" + implementation libs.androidx.annotation api project(":terminal-emulator") } @@ -36,7 +36,7 @@ android { } dependencies { - testImplementation 'junit:junit:4.13.2' + testImplementation libs.junit } publishing { diff --git a/web/data/UpdateConfig.json b/web/data/UpdateConfig.json index e78cd48..6351583 100644 --- a/web/data/UpdateConfig.json +++ b/web/data/UpdateConfig.json @@ -1,15 +1,15 @@ { - "versionCode":"84", - "versionName":"3.8.0", + "versionCode":"94", + "versionName":"3.9.0", "size": "45 MB", "url": "https://github.com/xoureldeen/Vectras-VM-Android/releases", - "Message": "

3.8.0

\nBugs fixed.", + "Message": "

3.9.0

\nBugs fixed.", "cancellable": true, - "versionCodeBeta":"93", - "versionNameBeta":"3.8.9", - "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,3.6.8,3.6.9,3.7.1,3.7.2,3.7.3,3.7.4,3.7.5,3.7.6,3.7.7,3.7.8,3.7.9,3.8.0,3.8.1,3.8.2,3.8.3,3.8.4,3.8.5,3.8.6,3.8.7,3.8.8,3.8.9", + "versionCodeBeta":"94", + "versionNameBeta":"3.9.0", + "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,3.6.8,3.6.9,3.7.1,3.7.2,3.7.3,3.7.4,3.7.5,3.7.6,3.7.7,3.7.8,3.7.9,3.8.0,3.8.1,3.8.2,3.8.3,3.8.4,3.8.5,3.8.6,3.8.7,3.8.8,3.8.9,3.9.0", "sizeBeta": "45 MB", "urlBeta": "https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases", - "MessageBeta": "

3.8.9

Bugs fixed.", + "MessageBeta": "

3.9.0

Bugs fixed.", "cancellableBeta": true } diff --git a/web/data/vroms-store.json b/web/data/vroms-store.json index 90587e6..96a1d62 100644 --- a/web/data/vroms-store.json +++ b/web/data/vroms-store.json @@ -1152,6 +1152,24 @@ "vecid": "windows10build10114cvbi", "id": "99668" }, + { + "rom_name": "Windows 10 build 10120", + "rom_icon": "https://vuetiwuvbyxywfukompp.supabase.co/storage/v1/object/public/getmyos/v1/files/2018/09/17/windows-logo_1_926ed76111646acbbe332bc5af0cf2ce.png", + "rom_url": "https://youtu.be/WK5c26GLb_U", + "rom_path": "Windows 10 build 10120.cvbi", + "rom_avail": true, + "rom_size": "Insider Preview", + "rom_arch": "X86_64", + "rom_kernel": "windows", + "rom_extra": "", + "final_rom_file_name": "", + "desc": "This Rom is from Nguyen Bao An Bui. You can get it on An Bui app: https://play.google.com/store/apps/details?id=com.anbui.app\n\nWindows 10 is a major release of Microsoft's Windows NT operating system. The successor to Windows 8.1, it was released to manufacturing on July 15, 2015, and later to retail on July 29, 2015. Windows 10 was made available for download via MSDN and TechNet, as a free upgrade for retail copies of Windows 8 and Windows 8.1 users via the Microsoft Store, and to Windows 7 users via Windows Update. Unlike previous Windows NT releases, Windows 10 receives new builds on an ongoing basis, which are available at no additional cost to users; devices in enterprise environments can alternatively use long-term support milestones that only receive critical updates, such as security patches.", + "file_size": "9 GB", + "creator": "Nguyen Bao An Bui", + "verified": true, + "vecid": "windows10build10120cvbi", + "id": "99650" + }, { "rom_name": "Windows 10 build 10240", "rom_icon": "https://vuetiwuvbyxywfukompp.supabase.co/storage/v1/object/public/getmyos/v1/files/2018/09/17/windows-logo_1_926ed76111646acbbe332bc5af0cf2ce.png",