diff --git a/app/build.gradle b/app/build.gradle index ff447ae..7a50e7b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vectras.vm" minSdk minApi targetSdk targetApi - versionCode 88 - versionName "3.8.4" + versionCode 89 + versionName "3.8.5" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true diff --git a/app/src/main/java/com/vectras/vm/VMManager.java b/app/src/main/java/com/vectras/vm/VMManager.java index 4bff60c..151f4b9 100644 --- a/app/src/main/java/com/vectras/vm/VMManager.java +++ b/app/src/main/java/com/vectras/vm/VMManager.java @@ -44,6 +44,7 @@ import com.vectras.vm.settings.X11DisplaySettingsActivity; import com.vectras.vm.utils.DialogUtils; import com.vectras.vm.utils.FileUtils; import com.vectras.vm.utils.JSONUtils; +import com.vectras.vm.utils.ProgressDialog; import com.vectras.vm.utils.TextUtils; import com.vectras.vm.utils.UIUtils; import com.vectras.vterm.Terminal; @@ -222,6 +223,18 @@ public class VMManager { return position == -1 ? addToVMList(vmConfigMap, Objects.requireNonNull(vmConfigMap.get("vmID")).toString()) : replaceToVMList(position, "", vmConfigMap); } + public static boolean isVMHidden(String vmPath) { + return new File(vmPath).getName().startsWith("_"); + } + + public static boolean hideVM(String vmId) { + return FileUtils.rename(AppConfig.vmFolder + vmId, "_" + vmId); + } + public static boolean unHideVM(String vmPath) { + return FileUtils.rename(vmPath, new File(vmPath).getName().replace("_", "")); + } + + public static boolean createNewVM(String name, String thumbnail, String drive, String arch, String cdrom, String params, String vmID, int port) { HashMap vmConfigMap = new HashMap<>(); vmConfigMap.put("imgName", name); @@ -267,39 +280,35 @@ public class VMManager { public static void deleteVMDialog(String _vmName, int _position, Activity _activity) { DialogUtils.threeDialog(_activity, _activity.getString(R.string.remove) + " " + _vmName, _activity.getString(R.string.remove_vm_content), _activity.getString(R.string.remove_and_do_not_keep_files), _activity.getString(R.string.remove_but_keep_files), _activity.getString(R.string.cancel), true, R.drawable.delete_24px, true, () -> { - View progressView = LayoutInflater.from(_activity).inflate(R.layout.dialog_progress_style, null); - TextView progress_text = progressView.findViewById(R.id.progress_text); - progress_text.setText(_activity.getString(R.string.just_a_moment)); - AlertDialog progressDialog = new MaterialAlertDialogBuilder(_activity, R.style.CenteredDialogTheme) - .setView(progressView) - .setCancelable(false) - .create(); + ProgressDialog progressDialog = new ProgressDialog(_activity); + progressDialog.setText(_activity.getString(R.string.just_a_moment)); progressDialog.show(); new Thread(() -> { isKeptSomeFiles = false; - deleteVm(_activity, _position, false); + boolean result = deleteVm(_activity, _position, false); _activity.runOnUiThread(() -> new Handler(Looper.getMainLooper()).postDelayed(() -> { - progressDialog.dismiss(); + progressDialog.reset(); MainActivity.refeshVMListNow(); + if (!result) { + DialogUtils.oopsDialog(_activity, _activity.getString(R.string.an_error_occurred_while_deleting_the_vm)); + } else if (isKeptSomeFiles) { + DialogUtils.oneDialog(_activity, _activity.getString(R.string.done), _activity.getString(R.string.the_vm_files_were_retained_because_they_are_still_being_used_elsewhere), R.drawable.check_24px); + } }, 500)); }).start(); }, () -> { - View progressView = LayoutInflater.from(_activity).inflate(R.layout.dialog_progress_style, null); - TextView progress_text = progressView.findViewById(R.id.progress_text); - progress_text.setText(_activity.getString(R.string.just_a_moment)); - AlertDialog progressDialog = new MaterialAlertDialogBuilder(_activity, R.style.CenteredDialogTheme) - .setView(progressView) - .setCancelable(false) - .create(); + ProgressDialog progressDialog = new ProgressDialog(_activity); + progressDialog.setText(_activity.getString(R.string.just_a_moment)); progressDialog.show(); new Thread(() -> { - deleteVm(_activity, _position, true); + boolean result = deleteVm(_activity, _position, true); _activity.runOnUiThread(() -> new Handler(Looper.getMainLooper()).postDelayed(() -> { - progressDialog.dismiss(); + progressDialog.reset(); MainActivity.refeshVMListNow(); + if (!result) DialogUtils.oopsDialog(_activity, _activity.getString(R.string.an_error_occurred_while_deleting_the_vm)); }, 500)); }).start(); }, @@ -307,34 +316,38 @@ public class VMManager { null); } - public static void deleteVm(Context context, int position, boolean isKeepFiles) { - if (!JSONUtils.isValidVmList()) return; + public static boolean deleteVm(Context context, int position, boolean isKeepFiles) { + if (!JSONUtils.isValidVmList()) return false; String vmList = FileUtils.readFromFile(context, new File(AppConfig.maindirpath + "roms-data.json")); JsonArray arr = JsonParser.parseString(vmList).getAsJsonArray(); - if (position < 0 || position > arr.size() - 1) return; + if (position < 0 || position > arr.size() - 1) return false; JsonObject obj = arr.get(position).getAsJsonObject(); String vmId = obj.has("vmID") ? obj.get("vmID").getAsString() : null; arr.remove(position); vmList = new Gson().toJson(arr); - if (vmId == null || vmId.isEmpty()) return; + if (vmId == null || vmId.isEmpty()) return false; + + boolean isCompleted; if (isKeepFiles) { - FileUtils.rename(AppConfig.vmFolder + vmId, "_" + vmId); - if (isVmFilesInUse(vmId, vmList)) { + isCompleted = hideVM(vmId); + if (isCompleted && isVmFilesInUse(vmId, vmList)) { vmList = vmList.replace(AppConfig.vmFolder + vmId, AppConfig.vmFolder + "_" + vmId); } } else { if (isVmFilesInUse(vmId, vmList)) { isKeptSomeFiles = true; - FileUtils.rename(AppConfig.vmFolder + vmId, "_" + vmId); - vmList = vmList.replace(AppConfig.vmFolder + vmId, AppConfig.vmFolder + "_" + vmId); + isCompleted = hideVM(vmId); + if (isCompleted) vmList = vmList.replace(AppConfig.vmFolder + vmId, AppConfig.vmFolder + "_" + vmId); } else { - FileUtils.delete(new File(AppConfig.vmFolder + vmId)); + isCompleted = FileUtils.delete(new File(AppConfig.vmFolder + vmId)); } } - FileUtils.writeToFile(AppConfig.maindirpath, "roms-data.json", vmList); + if (isCompleted) FileUtils.writeToFile(AppConfig.maindirpath, "roms-data.json", vmList); + + return isCompleted; } public static int restoreAll() { @@ -344,11 +357,11 @@ public class VMManager { if (vmFolders == null) return 0; List restoredVms = new ArrayList<>(); for (File f : vmFolders) { - if (f.getName().startsWith("_") && isFileExists(f.getAbsolutePath() + "/rom-data.json")) { + if (isVMHidden(f.getAbsolutePath()) && isFileExists(f.getAbsolutePath() + "/rom-data.json")) { String vmConfig = FileUtils.readAFile(f.getAbsolutePath() + "/rom-data.json"); if (JSONUtils.isValidFromString(vmConfig)) { - if (f.getName().startsWith("_")) - FileUtils.rename(f.getAbsolutePath(), f.getName().replace("_", "")); + if (isVMHidden(f.getAbsolutePath())) + unHideVM(f.getAbsolutePath()); arr.add(JsonParser.parseString(vmConfig)); restoredVms.add(f.getName().replaceAll("_", "")); } @@ -374,7 +387,7 @@ public class VMManager { if (vmFolders == null) return 0; for (File f : vmFolders) { if (!isVmFilesInUse(f.getName(), vmList)) { - if (f.getName().startsWith("_")) { + if (isVMHidden(f.getAbsolutePath())) { FileUtils.delete(new File(f.getAbsolutePath())); cleared++; } else if (!isFileExists(f.getAbsolutePath() + "/rom-data.json")) { @@ -490,9 +503,7 @@ public class VMManager { _startRepeat++; if (_startRepeat == _filelist.size()) { - if (restoredVMs > 0) { - FileUtils.writeToFile(AppConfig.maindirpath, "roms-data.json", arr.toString()); - } + FileUtils.writeToFile(AppConfig.maindirpath, "roms-data.json", restoredVMs > 0 ? arr.toString() : "[]"); } } } 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 127fec0..0488b53 100644 --- a/app/src/main/java/com/vectras/vm/main/MainActivity.java +++ b/app/src/main/java/com/vectras/vm/main/MainActivity.java @@ -6,7 +6,6 @@ import static com.vectras.vm.VectrasApp.getApp; import android.annotation.SuppressLint; import android.content.Intent; import android.content.res.Configuration; -import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; diff --git a/app/src/main/java/com/vectras/vm/main/romstore/RomStoreFragment.java b/app/src/main/java/com/vectras/vm/main/romstore/RomStoreFragment.java index 80f739f..624a839 100644 --- a/app/src/main/java/com/vectras/vm/main/romstore/RomStoreFragment.java +++ b/app/src/main/java/com/vectras/vm/main/romstore/RomStoreFragment.java @@ -139,6 +139,7 @@ public class RomStoreFragment extends Fragment { mAdapter.submitList(data); + SharedData.dataRomStore.clear(); SharedData.dataRomStore.addAll(dataRoms); romStoreCallToHomeListener.updateSearchStatus(true); } diff --git a/app/src/main/java/com/vectras/vm/main/softwarestore/SoftwareStoreFragment.java b/app/src/main/java/com/vectras/vm/main/softwarestore/SoftwareStoreFragment.java index 6a21c8d..6c69e04 100644 --- a/app/src/main/java/com/vectras/vm/main/softwarestore/SoftwareStoreFragment.java +++ b/app/src/main/java/com/vectras/vm/main/softwarestore/SoftwareStoreFragment.java @@ -140,6 +140,7 @@ public class SoftwareStoreFragment extends Fragment { mAdapter.submitList(data); + SharedData.dataSoftwareStore.clear(); SharedData.dataSoftwareStore.addAll(dataSoftware); softwareStoreCallToHomeListener.updateSearchStatus(true); } diff --git a/app/src/main/java/com/vectras/vm/utils/DialogUtils.java b/app/src/main/java/com/vectras/vm/utils/DialogUtils.java index 51810e8..ab3ff29 100644 --- a/app/src/main/java/com/vectras/vm/utils/DialogUtils.java +++ b/app/src/main/java/com/vectras/vm/utils/DialogUtils.java @@ -31,6 +31,10 @@ public class DialogUtils { oneDialog(context, title, message, context.getString(R.string.ok), iconid != -1, iconid, true, null, null); } + public static void oopsDialog(Context context, String message) { + oneDialog(context, context.getString(R.string.oops), message, R.drawable.error_96px); + } + public static void oneDialog(Context context, String _title, String _message, String _textPositiveButton, boolean _isicon, int _iconid, boolean _cancel, Runnable _onPositive, Runnable _onDismiss) { if (!isAllowShow(context)) return; diff --git a/app/src/main/java/com/vectras/vm/utils/ProgressDialog.java b/app/src/main/java/com/vectras/vm/utils/ProgressDialog.java new file mode 100644 index 0000000..e9cdf44 --- /dev/null +++ b/app/src/main/java/com/vectras/vm/utils/ProgressDialog.java @@ -0,0 +1,102 @@ +package com.vectras.vm.utils; + +import android.app.Activity; +import android.os.Build; +import android.view.LayoutInflater; + +import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.vectras.vm.R; +import com.vectras.vm.databinding.DialogProgressStyleBinding; + +public class ProgressDialog { + + private final Activity activity; + private AlertDialog dialog; + private DialogProgressStyleBinding binding; + private String text; + private Integer progress; + private Integer max; + private Boolean isIndeterminate; + + public ProgressDialog(Activity activity) { + this.activity = activity; + } + + public void show() { + if (DialogUtils.isAllowShow(activity)) { + if (dialog != null && dialog.isShowing()) return; + + binding = DialogProgressStyleBinding.inflate(LayoutInflater.from(activity)); + + if (text != null) binding.progressText.setText(text); + + if (progress != null) { + binding.progressBar.setIndeterminate(false); + + if (Build.VERSION.SDK_INT >= 24) { + binding.progressBar.setProgress(progress, true); + } else { + binding.progressBar.setProgress(progress); + } + } + + if (max != null) binding.progressBar.setMax(max); + + if (isIndeterminate != null) binding.progressBar.setIndeterminate(isIndeterminate); + + dialog = new MaterialAlertDialogBuilder(activity, R.style.CenteredDialogTheme) + .setView(binding.getRoot()) + .setCancelable(false) + .create(); + dialog.show(); + } + } + + public void setText(String text) { + this.text = text; + if (binding != null) binding.progressText.setText(text); + } + + public void setProgress(int progress) { + this.progress = Math.max(progress, 0); + if (binding != null) { + binding.progressBar.setIndeterminate(false); + + if (Build.VERSION.SDK_INT >= 24) { + binding.progressBar.setProgress(this.progress, true); + } else { + binding.progressBar.setProgress(this.progress); + } + } + } + + public void setMax(int max) { + this.max = Math.max(max, 0); + if (binding != null) binding.progressBar.setMax(this.max); + } + + public void setIndeterminate(boolean isIndeterminate) { + this.isIndeterminate = isIndeterminate; + if (binding != null) binding.progressBar.setIndeterminate(isIndeterminate); + } + + public boolean isShowing() { + return dialog != null && dialog.isShowing(); + } + + public void dismiss() { + DialogUtils.safeDismiss(activity, dialog); + binding = null; + dialog = null; + } + + public void reset() { + dismiss(); + text = null; + progress = null; + max = null; + isIndeterminate = null; + } +} diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index b936ebf..fa8ef69 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -269,7 +269,7 @@ Thư mục được chia sẻ không được sử dụng vì i386 không hỗ trợ nó. Dùng UEFI (chỉ x64) Xoá và không giữ các tệp - Xoá và ẫn giữ các tệp + Xoá và giữ các tệp Bạn có muốn xoá nó không? Bạn có thể chọn giữ hoặc không giữ các tệp. Chung Cá nhân hoá @@ -372,13 +372,13 @@ Cổng: Bạn có thể tắt máy ảo này bằng cách nhấp vào nút Dừng trong Trình giám sát Hệ thống. Để điều khiển nó, hãy kết nối với cổng này: Cần hành động - Dữ liệu về các máy ảo đã bị hỏng và cần được sửa chữa. + Danh sách máy ảo đã bị hỏng và cần được sửa chữa. Bắt đầu sửa Thiết bị của bạn không đủ dung lượng lưu trữ để thiết lập. Vui lòng giải phóng dung lượng và thử lại sau. Sao không thấy gì vậy nhỉ? Chạm để thử lại. Buộc làm mới - Màn hình VNC sẽ khởi động lại mỗi khi có thay đổi, điều này giúp khắc phục sự cố. + Màn hình VNC sẽ khởi động lại mỗi khi có thay đổi, điều này giúp khắc phục một số sự cố. Các cài đặt cho VNC. Đã nhập Tệp đã được nhập. @@ -409,10 +409,10 @@ Sửa Đang nhập… Đã xong - Vui lòng ở lại đây. + Đừng rời đi! Đang xuất… Các nhà phát triển - Các mạng xã hội của chúng tôi + Các mạng xã hội Thích Đã thích lượt xem @@ -424,8 +424,8 @@ Nếu cần, hãy bỏ qua bước tính toán kích thước khi nhập rom để quá trình nhập diễn ra nhanh hơn, nhưng tiến trình nhập sẽ không được hiển thị. Kiểm tra dư thừa tuần hoàn Chức năng này kiểm tra và phát hiện lỗi dữ liệu khi xuất rom, bạn có thể tắt nó đi để xuất nhanh hơn. - Không thể kết nối đến máy chủ Alpine Linux hoặc máy chủ đang tạm thời ngừng hoạt động, vui lòng kiểm tra kết nối mạng, thử kết nối với máy chủ phản chiếu khác hoặc thử lại sau vài giờ. - Đã xảy ra lỗi trong quá trình cài đặt tập tin hệ thống. Bạn có thể thử gỡ cài đặt và cài đặt lại ứng dụng này để khắc phục sự cố. + Thứ gì đó đã ngăn chúng ta kết nối đến máy chủ của Alpine Linux hoặc máy chủ đang ngủ rồi, vui lòng kiểm tra kết nối mạng, thử kết nối với máy chủ khác hoặc thử lại sau vài giờ. + Một con ốc đã rơi đâu mất khiến hệ thống không hoạt động được rồi! Bạn có thể thử gỡ cài đặt và cài đặt lại ứng dụng này để khắc phục sự cố. Đang sao chép tệp… Đĩa quang (chỉ iso) Tệp không hợp lệ @@ -433,21 +433,21 @@ Dùng VNC Đang cài đặt hệ thống… Đang kết nối… - Bạn muốn cài đặt bằng cái nào? - Tiêu chuẩn hoặc thủ công? + Bạn muốn cài đặt bằng cách nào? + Tiêu chuẩn hay thủ công? Tiêu chuẩn - Phần mềm được đề xuất và quá trình cài đặt sẽ tự động cài đặt mọi thứ. + Các gói được đề xuất và quá trình cài đặt sẽ tự động cài đặt mọi thứ. Thủ công - Nếu bạn muốn tự chọn tệp bootstrap để cài đặt phiên bản Qemu mong muốn. - Chọn chiếc gương gần bạn nhất để thiết lập nhanh hơn. + Nếu bạn muốn tự chọn tệp khởi động để cài đặt phiên bản Qemu mong muốn. + Chọn máy chủ gần bạn nhất để thiết lập nhanh hơn. Vui lòng không ngắt kết nối mạng. Quá trình này có thể mất vài phút. Có gì đó sai sai Quá trình cài đặt không thể hoàn tất và nhật ký lỗi được hiển thị bên dưới. Tùy chọn này hiện không khả dụng vì không thể kết nối với máy chủ. Vectras VM không thể hoạt động trên thiết bị này. - Không đủ không gian lưu trữ. + Nơi này quá chật chội rồi! Không còn đủ không gian lưu trữ. Không có nhập ký. - Gói phần mềm cần thiết chưa được cài đặt, bạn cần cài đặt nó trước khi sử dụng tính năng này. + Các gói cần thiết chưa được cài đặt, bạn cần cài đặt nó trước khi sử dụng tính năng này. Ứng dụng Termux:X11 chưa được cài đặt, bạn cần cài đặt ứng dụng này để tiếp tục. Màn hình X11 Các cài đặt cho màn hình X11. @@ -459,7 +459,7 @@ Chạy Qemu với Xterm Giúp chạy các lệnh Qemu dễ dàng hơn mà không cần chuyển đổi. Sửa nhanh - Hãy viết gì đó… + Hãy viết gì đó đi nào… Chuyển sang VNC Tùy chọn Đến cài đặt Termux:X11. @@ -469,20 +469,20 @@ Phần mềm & trình điều khiển Được chia sẻ bởi Cập nhật hệ thống - Đã có bản cập nhật hệ thống, hãy cập nhật ngay để có trải nghiệm đầy đủ. + Đã có bản cập nhật hệ thống, hãy cập nhật ngay để có trải nghiệm tốt hơn. Dùng SDL - Hãy bật tùy chọn này nếu bạn muốn sử dụng 3dfx và chơi các game cổ điển (chỉ dành cho x86_64 và i386). + Hãy bật tùy chọn này nếu bạn muốn sử dụng 3dfx và chơi các trò chơi điện tử cổ điển (chỉ dành cho x86_64 và i386). Tài liệu QEMU Bạn cần chuyển sang SDL để sử dụng 3dfx. Quản lý thiết bị Xem trong thư mục Thoát 3dfx - CPU của thiết bị bạn hỗ trợ tăng tốc cho 3dfx. Tuy nhiên, nó có thể không ổn định hoặc không hoạt động. + CPU của thiết bị bạn hỗ trợ tăng tốc cho 3dfx. Tuy nhiên, nó có hoạt động thật hay không thì hên xui. CPU của thiết bị của bạn không hỗ trợ tăng tốc cho 3dfx. 3dfx không có sẵn. Làm mới - Chúng tôi rất xin lỗi về sự cố đã xảy ra và ứng dụng bị sập trước đó. + Xin lỗi nhé! Cơn ngất xỉu đã đến quá bất ngờ. Hiện chuột ảo Ẩn chuột ảo Tệp khởi động này không hợp lệ. @@ -508,6 +508,8 @@ Thêm tệp không thành công, vui lòng thử lại sau. mục đã được dọn dẹp. Xem thùng rác + Đã 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. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 08613cc..ceeda05 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -517,6 +517,8 @@ Adding file failed, please try again later. items have been cleared. Show recycle bin + An error occurred while deleting the virtual machine. + The virtual machine\'s files were retained because they are still being used elsewhere. diff --git a/web/data/UpdateConfig.json b/web/data/UpdateConfig.json index 3dcf015..1a38280 100644 --- a/web/data/UpdateConfig.json +++ b/web/data/UpdateConfig.json @@ -5,11 +5,11 @@ "url": "https://github.com/xoureldeen/Vectras-VM-Android/releases", "Message": "

3.8.0

\nBugs fixed.", "cancellable": true, - "versionCodeBeta":"88", - "versionNameBeta":"3.8.4", - "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", + "versionCodeBeta":"89", + "versionNameBeta":"3.8.5", + "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", "sizeBeta": "45 MB", "urlBeta": "https://github.com/AnBui2004/Vectras-VM-Emu-Android/releases", - "MessageBeta": "

3.8.4

Bugs fixed.", + "MessageBeta": "

3.8.5

Bugs fixed.", "cancellableBeta": true }