diff --git a/app/src/main/java/com/vtosters/lite/R.java b/app/src/main/java/com/vtosters/lite/R.java index b4c5e9c75..bcfc8f5aa 100644 --- a/app/src/main/java/com/vtosters/lite/R.java +++ b/app/src/main/java/com/vtosters/lite/R.java @@ -15007,7 +15007,7 @@ public final class R { public static final int dockbar_save = 0x7f11123b; public static final int dockbar_selected_items = 0x7f11123c; public static final int dockbar_unselected_items = 0x7f11123d; - public static final int download_latest_vtl = 0x7f11123e; + public static final int checkforupdates = 0x7f11123e; public static final int other_fix_dynamic_colors = 0x7f11123f; public static final int other_reset_settings = 0x7f111240; public static final int other_reset_settings_summary = 0x7f111241; diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/OTADownloader.java b/app/src/main/java/ru/vtosters/lite/downloaders/OTADownloader.java new file mode 100644 index 000000000..6171d2e61 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/downloaders/OTADownloader.java @@ -0,0 +1,70 @@ +package ru.vtosters.lite.downloaders; + +import android.app.DownloadManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.Environment; + +import com.vk.core.f.FileUtils; + +import java.io.File; + +import ru.vtosters.lite.ui.activities.APKInstallActivity; +import ru.vtosters.lite.utils.Globals; + +public class OTADownloader { + + private static final BroadcastReceiver callback = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + var extras = intent.getExtras(); + if (extras == null + || !intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) return; + + var query = new DownloadManager.Query(); + var id = extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID); + query.setFilterById(id); + var manager = (DownloadManager) Globals.getContext().getSystemService(Context.DOWNLOAD_SERVICE); + var cursor = manager.query(query); + if (cursor.moveToFirst()) { + var status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)); + if (status == DownloadManager.STATUS_SUCCESSFUL) { + var downloadDir = new File(Environment.getExternalStorageDirectory(), "Download"); + if (!downloadDir.exists()) return; + var list = downloadDir.listFiles(); + if (list == null || list.length == 0) return; + var maxIndex = -1; + for (File file : list) { + var name = file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf("/") + 1); + if (file.isFile() + && name.matches("VTLite-\\d+\\.apk")) + maxIndex = Math.max(maxIndex, Integer.parseInt(name.replaceFirst(".+-(\\d+).+", "$1"))); + } + File apk = maxIndex == -1 ? new File(downloadDir, "VTLite.apk") : new File(downloadDir, "VTLite-" + maxIndex + ".apk"); + APKInstallActivity.installOta(context, FileUtils.h(apk)); + } else if (status == DownloadManager.STATUS_FAILED) { + // do something + } + } + } + }; + + public static void downloadBuild(String url) { + var context = Globals.getContext(); + url = "https://github.com/vtosters/lite/releases/download/v0.2.9-beta2/VTLite.apk"; + var uri = Uri.parse(url); + + context.registerReceiver(callback, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); + + var request = new DownloadManager.Request(uri); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + request.setTitle("VTLite.apk"); + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "VTLite.apk"); + + var manager = (DownloadManager) Globals.getContext().getSystemService(Context.DOWNLOAD_SERVICE); + manager.enqueue(request); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ui/activities/APKInstallActivity.java b/app/src/main/java/ru/vtosters/lite/ui/activities/APKInstallActivity.java new file mode 100644 index 000000000..5fddc903e --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/ui/activities/APKInstallActivity.java @@ -0,0 +1,80 @@ +package ru.vtosters.lite.ui.activities; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v7.app.AppCompatActivity; +import android.widget.Toast; + +public class APKInstallActivity extends AppCompatActivity { + public static final int REQUEST_INSTALL_PERMISSION = 1; + + public static final String EXTRA_APK_URI = "apk_uri"; + + private Uri mApkUri; + + public static void installOta(Context context, Uri apkUri) { + Intent intent = new Intent(context, APKInstallActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.putExtra(EXTRA_APK_URI, apkUri); + + context.startActivity(intent); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if (intent == null) { + Toast.makeText(this, "No intent", Toast.LENGTH_SHORT).show(); + finish(); + return; + } + + mApkUri = intent.getParcelableExtra(EXTRA_APK_URI); + if (mApkUri == null) { + Toast.makeText(this, "No apk uri", Toast.LENGTH_SHORT).show(); + finish(); + return; + } + + installApk(); + } + + private void installApk() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (!getPackageManager().canRequestPackageInstalls()) { + Intent permIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName())); + startActivityForResult(permIntent, REQUEST_INSTALL_PERMISSION); + return; + } + } + + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(mApkUri, "application/vnd.android.package-archive"); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + startActivity(intent); + } catch (Exception e) { + Toast.makeText(this, "Ошибка установки обновления " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + } + + finish(); + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == REQUEST_INSTALL_PERMISSION) + installApk(); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ui/dialogs/OTADialog.java b/app/src/main/java/ru/vtosters/lite/ui/dialogs/OTADialog.java new file mode 100644 index 000000000..75f4b51f2 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/ui/dialogs/OTADialog.java @@ -0,0 +1,46 @@ +package ru.vtosters.lite.ui.dialogs; + +import android.app.Activity; +import android.widget.Toast; + +import ru.vtosters.lite.downloaders.OTADownloader; +import ru.vtosters.lite.ui.vkui.ModalBottomSheetWrapper; +import ru.vtosters.lite.utils.OTAHelper; + +public class OTADialog implements OTAHelper.OTAListener { + + private Activity mActivity; + + private OTAHelper mHelper; + + public static OTADialog checkUpdates(Activity activity) { + return new OTADialog(activity); + } + + public OTADialog(Activity activity) { + mActivity = activity; + + mHelper = new OTAHelper(this); + mHelper.loadData(); + } + + @Override + public void onUpdateApplied() { + mActivity.runOnUiThread(() -> { + Toast.makeText(mActivity, "Обновления найдены", Toast.LENGTH_SHORT).show(); + new ModalBottomSheetWrapper(mActivity) + .setUpdateInfoView(mHelper.getNewVersionName(), mHelper.getUpdateDescription()) + .setPositiveButton("Обновить", () -> { + OTADownloader.downloadBuild(mHelper.getDownloadUrl()); + }) + .show(); + }); + } + + @Override + public void onUpdateCanceled() { + mActivity.runOnUiThread(() -> { + Toast.makeText(mActivity, "Обновлений не найдено", Toast.LENGTH_SHORT).show(); + }); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java b/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java index 5d1fb55a2..722afcbbf 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java +++ b/app/src/main/java/ru/vtosters/lite/ui/fragments/VTSettings.java @@ -56,6 +56,7 @@ import com.vtosters.lite.fragments.k.BlacklistFragment; import com.vtosters.lite.fragments.money.music.control.subscription.MusicSubscriptionControlFragment; import ru.vtosters.lite.ui.PreferencesUtil; +import ru.vtosters.lite.ui.dialogs.OTADialog; import ru.vtosters.lite.ui.fragments.dockbar.DockBarFragment; import ru.vtosters.lite.ui.fragments.dockbar.DockBarManager; import ru.vtosters.lite.ui.fragments.multiaccount.MultiAccountFragment; @@ -336,10 +337,20 @@ public class VTSettings extends MaterialPreferenceToolbarFragment { return false; }); - PreferencesUtil.addPreference(this, "", getString("download_latest_vtl"), "", "ic_download_24", preference -> { - getContext().startActivity(new Intent("android.intent.action.VIEW").setData(Uri.parse("https://github.com/vtosters/lite/releases/latest"))); + PreferencesUtil.addPreferenceCategory(this, getString("updates")); + + PreferencesUtil.addPreference(this, "", getString("checkforupdates"), "", "ic_download_24", preference -> { + OTADialog.checkUpdates(p()); return false; }); + + PreferencesUtil.addMaterialSwitchPreference(this, "checkupdates", getString("checkupdates"), "", "ic_recent_24", true, (preference, o) -> { + boolean value = (boolean) o; + + edit().putBoolean("checkupdates", value).commit(); + + return true; + }); } public static String getValAsString(String stringid, Boolean value) { diff --git a/app/src/main/java/ru/vtosters/lite/ui/vkui/ModalBottomSheetProxy.java b/app/src/main/java/ru/vtosters/lite/ui/vkui/ModalBottomSheetProxy.java deleted file mode 100644 index 44fee4e18..000000000 --- a/app/src/main/java/ru/vtosters/lite/ui/vkui/ModalBottomSheetProxy.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.vtosters.lite.ui.vkui; - -import android.app.Activity; -import android.view.View; - -import com.vk.core.dialogs.bottomsheet.ModalBottomSheet; - -public class ModalBottomSheetProxy { - public final ModalBottomSheet.a builder; - - public ModalBottomSheetProxy(Activity activity) { - builder = new ModalBottomSheet.a(activity); - } - - public void setTitle(String title) { - builder.b(title); - } - - - public void setClickableMessage(String text) { - builder.a(text, kostil -> { - }); - } - - public void setClickableMessage(String text, Runnable runnable) { - builder.a(text, listener -> runnable.run()); - } - - public void setPositiveButton(String text, Runnable runnable) { - builder.b(text, kostil -> runnable.run()); - } - - public void setSubtitle(String text) { - builder.b((CharSequence) text); - } - - public void setView(View view) { - builder.a(view); - } - - public ModalBottomSheet show() { - return builder.c(null); - } - - public ModalBottomSheet show(boolean isCancellable) { - var sheet = builder.c(null); - sheet.b(isCancellable); - return sheet; - } -} diff --git a/app/src/main/java/ru/vtosters/lite/ui/vkui/ModalBottomSheetWrapper.java b/app/src/main/java/ru/vtosters/lite/ui/vkui/ModalBottomSheetWrapper.java new file mode 100644 index 000000000..8fe60119b --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/ui/vkui/ModalBottomSheetWrapper.java @@ -0,0 +1,97 @@ +package ru.vtosters.lite.ui.vkui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Typeface; +import android.util.TypedValue; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.vk.core.dialogs.bottomsheet.ModalBottomSheet; + +import ru.vtosters.lite.utils.Globals; +import ru.vtosters.lite.utils.Themes; + +public class ModalBottomSheetWrapper { + public final ModalBottomSheet.a builder; + + public ModalBottomSheetWrapper(Activity activity) { + builder = new ModalBottomSheet.a(activity); + } + + public ModalBottomSheetWrapper setTitle(String title) { + builder.b(title); + return this; + } + + public ModalBottomSheetWrapper setUpdateInfoView(String version, String changelog) { + var context = Globals.getContext(); + var container = new LinearLayout(context); + var title = new TextView(context); + var changelogView = new TextView(context); + + container.setOrientation(LinearLayout.VERTICAL); + + title.setText("Новая версия " + version); + title.setTypeface(title.getTypeface(), Typeface.BOLD); + title.setSingleLine(true); + title.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); + title.setTextColor(Themes.getTextAttr()); + title.setTextSize(24.0f); + container.addView(title, new LinearLayout.LayoutParams(-1, -2)); + + changelogView.setPadding(0, Globals.convertDpToPixel(8), 0, 0); + changelogView.setText("Изменения: \n" + changelog); + changelogView.setTextColor(Themes.getTextAttr()); + container.addView(changelogView, new LinearLayout.LayoutParams(-1, -2)); + + builder.a(container); + + return this; + } + + // try to call a true function and you will understand my pain + public ModalBottomSheetWrapper setMessage( + String text + ) { + builder.a(text, listener -> {}); + return this; + } + + public ModalBottomSheetWrapper setClickableMessage( + String text, + Runnable callback + ) { + builder.a(text, listener -> callback.run()); + return this; + } + + public ModalBottomSheetWrapper setPositiveButton( + String text, + Runnable callback + ) { + builder.b(text, kostil -> callback.run()); + return this; + } + + public ModalBottomSheetWrapper setSubtitle(String text) { + builder.b((CharSequence) text); + return this; + } + + public ModalBottomSheetWrapper setView(View view) { + builder.a(view); + return this; + } + + public ModalBottomSheet show() { + return builder.c(null); + } + + public ModalBottomSheet show(boolean isCancellable) { + var sheet = builder.c(null); + sheet.b(isCancellable); + return sheet; + } +} diff --git a/app/src/main/java/ru/vtosters/lite/utils/Globals.java b/app/src/main/java/ru/vtosters/lite/utils/Globals.java index 5cc38cb6b..8d53c81e5 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/Globals.java +++ b/app/src/main/java/ru/vtosters/lite/utils/Globals.java @@ -1,6 +1,7 @@ package ru.vtosters.lite.utils; import static androidx.core.content.ContextCompat.getDrawable; +import static ru.vtosters.lite.utils.Preferences.checkupdates; import static ru.vtosters.lite.utils.Preferences.getLocale; import static ru.vtosters.lite.utils.Preferences.preferences; import static ru.vtosters.lite.utils.Themes.isDarkTheme; @@ -49,6 +50,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import ru.vtosters.lite.ui.dialogs.DisableBattery; +import ru.vtosters.lite.ui.dialogs.InstallGMS; +import ru.vtosters.lite.ui.dialogs.Start; +import ru.vtosters.lite.ui.dialogs.OTADialog; + public class Globals { private static final List activities = new ArrayList<>(); @@ -57,6 +63,14 @@ public class Globals { return preferences; } + public static void MainActivityInit(Activity activity) { + if(checkupdates()) + OTADialog.checkUpdates(activity); + Start.alert(activity); + InstallGMS.alert(activity); + DisableBattery.alert(activity); + } + public static void componentSwitcher(String component, Boolean enabled) { var packageManager = getContext().getPackageManager(); packageManager.setComponentEnabledSetting(new ComponentName(getPackageName(), getPackageName() + "." + component), enabled? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); diff --git a/app/src/main/java/ru/vtosters/lite/utils/OTAHelper.java b/app/src/main/java/ru/vtosters/lite/utils/OTAHelper.java new file mode 100644 index 000000000..a876334da --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/utils/OTAHelper.java @@ -0,0 +1,126 @@ +package ru.vtosters.lite.utils; + +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class OTAHelper { + + private static final String LATEST_RELEASE_URL = "https://api.github.com/repositories/473685743/releases/latest"; + private static final String LATEST_RELEASE_COMMIT_URL = "https://api.github.com/repositories/473685743/git/ref/tags/%s"; + + private OTAListener mListener; + + private final OkHttpClient mClient = new OkHttpClient(); + + private JSONObject mReleaseJson; + private JSONObject mCommitJson; + + public OTAHelper(OTAListener listener) { + this.mListener = listener; + } + + public void loadData() { + Request release = new Request.Builder() + .url(LATEST_RELEASE_URL) + .build(); + mClient.newCall(release).a(new Callback() { + @Override + public void a(Call call, IOException e) { + Log.d("OTAHelper", "Error while getting latest release info: " + e); + mListener.onUpdateCanceled(); + } + + @Override + public void a(Call call, Response response) throws IOException { + try { + setData(response); + } catch (JSONException | NullPointerException e) { + e.printStackTrace(); + } + + } + }); + } + + void setData(Response response) throws IOException, JSONException { + mReleaseJson = new JSONObject(response.body().string()); + + String tag = mReleaseJson.getString("tag_name"); + Request commit = new Request.Builder() + .url(String.format(LATEST_RELEASE_COMMIT_URL, tag)) + .build(); + mClient.newCall(commit).a(new Callback() { + @Override + public void a(Call call, IOException e) { + Log.d("OTAHelper", "Error while getting latest commit info: " + e); + mListener.onUpdateCanceled(); + } + + @Override + public void a(Call call, Response response) throws IOException { + try { + mCommitJson = new JSONObject(response.body().string()); + if (isNewVersion()) + mListener.onUpdateApplied(); + else mListener.onUpdateCanceled(); + } catch (NullPointerException | JSONException e) { + e.printStackTrace(); + mListener.onUpdateCanceled(); + } + } + }); + } + + public boolean isNewVersion() { + try { + String commitSHA = mCommitJson.getJSONObject("object").getString("sha"); + return !commitSHA.startsWith(About.getBuildNumber()); + } catch (Exception e) { + return false; + } + } + + public String getNewVersionName() { + try { + return mReleaseJson.getString("name"); + } catch (JSONException e) { + return null; + } + } + + public String getUpdateDescription() { + try { + return mReleaseJson.getString("body"); + } catch (JSONException e) { + return null; + } + } + + public String getDownloadUrl() { + try { + return mReleaseJson.getJSONArray("assets") + .getJSONObject(0) + .getString("browser_download_url"); + } catch (JSONException e) { + return null; + } + } + + public interface OTAListener { + + void onUpdateApplied(); + + void onUpdateCanceled(); + + } +} diff --git a/app/src/main/java/ru/vtosters/lite/utils/Preferences.java b/app/src/main/java/ru/vtosters/lite/utils/Preferences.java index 3935887d4..d9481362e 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/Preferences.java +++ b/app/src/main/java/ru/vtosters/lite/utils/Preferences.java @@ -361,6 +361,10 @@ public class Preferences { return getBoolValue("roundedmsgs", false); } + public static boolean checkupdates() { + return getBoolValue("checkupdates", true); + } + public static boolean disableanimstickers() { return getBoolValue("disableanimstickers", true); } diff --git a/smali/AndroidManifest.xml b/smali/AndroidManifest.xml index 8bd0404e5..c11c15108 100644 --- a/smali/AndroidManifest.xml +++ b/smali/AndroidManifest.xml @@ -75,6 +75,7 @@ + diff --git a/smali/res/values-ru/strings.xml b/smali/res/values-ru/strings.xml index cdfbbf8ab..731a9eb8f 100644 --- a/smali/res/values-ru/strings.xml +++ b/smali/res/values-ru/strings.xml @@ -4337,7 +4337,7 @@ Добавить новый аккаунт - Скачать последнюю версию + Проверить обновления Внимание! Данное меню предназначено только для тестирования работоспособности приложения. Если вы являетесь рядовым юзером, то вам не следует ничего не трогать в данных настройках (пожалуйста) @@ -4474,6 +4474,8 @@ Скрыть подписи в настройках Есть доступ + Авто обновления + Обновления diff --git a/smali/res/values/public.xml b/smali/res/values/public.xml index 9d41ff2eb..98cebb92c 100644 --- a/smali/res/values/public.xml +++ b/smali/res/values/public.xml @@ -14953,7 +14953,7 @@ - + diff --git a/smali/res/values/strings.xml b/smali/res/values/strings.xml index 1422c9e5c..df4a31456 100644 --- a/smali/res/values/strings.xml +++ b/smali/res/values/strings.xml @@ -4463,7 +4463,7 @@ May look like a fake leaving the online" Add new account - Download latest version + Check for updates Warning! These options are for testing this app only. If you are a regular user, please, don\'t use them @@ -4608,6 +4608,9 @@ May look like a fake leaving the online" Hide settings summaries Have access + Auto updates + Updates + diff --git a/smali/smali_classes3/com/vtosters/lite/MainActivity.smali b/smali/smali_classes3/com/vtosters/lite/MainActivity.smali index 1916f7027..5ec417aa7 100644 --- a/smali/smali_classes3/com/vtosters/lite/MainActivity.smali +++ b/smali/smali_classes3/com/vtosters/lite/MainActivity.smali @@ -566,11 +566,7 @@ .line 65 :cond_0 - invoke-static {p0}, Lru/vtosters/lite/ui/dialogs/Start;->alert(Landroid/app/Activity;)V - - invoke-static {p0}, Lru/vtosters/lite/ui/dialogs/InstallGMS;->alert(Landroid/app/Activity;)V - - invoke-static {p0}, Lru/vtosters/lite/ui/dialogs/DisableBattery;->alert(Landroid/app/Activity;)V + invoke-static {p0}, Lru/vtosters/lite/utils/Globals;->MainActivityInit(Landroid/app/Activity;)V invoke-virtual {p0}, Lcom/vtosters/lite/MainActivity;->getWindow()Landroid/view/Window;