Compare commits

...
Author SHA1 Message Date
gdlbo bf4cf2be53 adfree + free from server connection (#1481) 2023-09-28 16:10:30 +03:00
gdlbo aac86a9952 Update InstallGMSFragment.java
added inotia00 microg information
2023-09-28 11:09:40 +03:00
gdlbo ae00e72bac added more microg instructions 2023-09-27 23:55:56 +03:00
gdlbo fea984f6fd fixed null foaf crash app 2023-09-27 23:34:37 +03:00
gdlbo d870c2f984 Update VTSettings.java 2023-09-27 22:12:12 +03:00
gdlbo f7f43928cf cleanup and refactor code
also fixed ts merging
2023-09-25 14:44:27 +03:00
Vologhat 1accb45f12 Merge remote-tracking branch 'origin/main' 2023-09-24 18:43:40 +03:00
Vologhat d9f53920e5 optimize music cache logic 2023-09-24 18:39:12 +03:00
gdlbo 76d91332d7 added recommendation icons 2023-09-23 16:04:45 +03:00
gdlbo 728cbde683 added xiaomi battery dozing information 2023-09-23 14:58:13 +03:00
gdlbo 60c98cd998 some settings improves 2023-09-23 14:50:18 +03:00
gdlbo f1d106e0f0 added gms info in device info
installed microg/gms and push service
2023-09-23 11:17:43 +03:00
gdlbo c92194e90b app.revanced.android.gms support (#1474) 2023-09-23 10:53:05 +03:00
gdlbo 91683af807 deleted useless file 2023-09-23 01:50:43 +03:00
gdlbo c5c898aa4e added recommendations for app fixes + added more gmsless instructions 2023-09-23 01:22:13 +03:00
gdlbo 25634b2469 cache info/clean improvements 2023-09-22 23:27:08 +03:00
gdlbo b8e764d09d Update AboutAppFragment.smali 2023-09-15 19:31:09 +03:00
gdlbo 17ed32857a fixed information about cache settings 2023-09-15 19:05:34 +03:00
gdlbo d631d07e0f removed useless update toast 2023-09-15 19:03:14 +03:00
gdlbo 2ec4c69832 added "Data and storage" item to settings 2023-09-15 18:40:38 +03:00
abslike 6426c965c6 Fix button black text in bots 2023-09-15 18:38:27 +03:00
gdlbo 4f6d410185 stories disabled by default 2023-09-15 18:09:29 +03:00
gdlbo 18a899f439 updated and refactored vt settings 2023-09-15 18:08:28 +03:00
gdlbo 7e5b3fe233 superapp audio item fix 2023-09-14 12:08:16 +03:00
gdlbo 4abd0eec6c fix compile 2023-09-14 11:28:08 +03:00
gdlbo cf17ed64c7 fixed invert in cached playlist 2023-07-30 16:49:30 +03:00
gdlbo a2dd7c1e06 fixed strange code 2023-07-29 17:06:12 +03:00
gdlbo 021fe66bec invert cached tracks list 2023-07-29 16:48:30 +03:00
gdlbo 1b3896f4ca fix colors issue 2023-07-28 19:01:57 +03:00
gdlbo fc9ccf66d1 fix #1409 2023-07-28 18:49:11 +03:00
Vologhat d205f2894b rewrite StickersFragment.java
- move add tgs action to toolbar
- some optimizations
2023-07-14 12:53:57 +03:00
Vologhat 7afc04b2b2 fix tgs packs download 2023-07-14 12:48:45 +03:00
Vologhat 584ee96ee0 fix #1406 2023-07-14 12:37:50 +03:00
gdlbo 66f7ea1b79 Update VTVerifications.java 2023-07-11 21:47:53 +03:00
gdlbo 7b0a83458b Update README.md 2023-07-08 18:03:41 +03:00
gdlbo cdaf7909d4 fix old android crash accenting 2023-07-04 23:23:18 +03:00
gdlbo c70ab388c1 fixed account saving 2023-07-04 22:55:22 +03:00
gdlbo 50c66c2703 fix music metrics 2023-07-04 19:56:12 +03:00
gdlbo 357e520bb0 some code fixes 2023-07-04 18:57:06 +03:00
gdlbo 6379ce5741 cleanup code 2023-07-04 13:42:04 +03:00
Vologhat 02807739a1 fix #1394 2023-07-02 22:32:26 +03:00
gdlbo 40635ab75a fix #1388 2023-07-02 13:44:47 +03:00
gdlbo 32e1bfe6fd fix messages color accent 2023-07-02 00:04:10 +03:00
126 changed files with 2272 additions and 2001 deletions
+1 -1
View File
@@ -37,7 +37,7 @@
* Стабильные сборки находятся в [Releases](https://github.com/vtosters/lite/releases)
Зеркало на нашем [<img src="https://gitea.com/assets/img/logo.svg" align="center" width="20" height="20"/> Git](https://git.maki.su/gdlbo/lite)
Зеркало на нашем [<img src="https://gitea.com/assets/img/logo.svg" align="center" width="20" height="20"/> Git](https://git.maki.su/vtosters/lite)
### Быстрая навигация
+6
View File
@@ -84,6 +84,12 @@ android {
}
}
task testClasses {
doLast {
println 'This is a dummy testClasses task'
}
}
dependencies {
implementation 'com.microsoft.appcenter:appcenter-analytics:5.0.1'
implementation 'com.microsoft.appcenter:appcenter-crashes:5.0.1'
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,9 +10,9 @@ import com.vk.music.common.MusicPlaybackLaunchContext;
import java.util.ArrayList;
import java.util.List;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
import static ru.vtosters.hooks.other.Preferences.isValidSignature;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class LibVKXClient {
@SuppressLint("StaticFieldLeak")
@@ -80,11 +80,6 @@ public class LibVKXClient {
lambdaplay(list, musicTrack, iLibVkxService, null);
}
public static String asId(MusicTrack musicTrack) {
String id = musicTrack.e + "_" + musicTrack.d;
if (musicTrack.O != null && !musicTrack.O.equals("")) {
id += "_" + musicTrack.O;
}
return id;
}
public static String asId(MusicTrack musicTrack)
{ return musicTrack.y1(); }
}
@@ -36,10 +36,8 @@ public class TelegramStickersPack extends CustomStickersPack {
public HashMap<String, List<Integer>> emojis = new HashMap<>();
public TelegramStickersPack(String id) {
final var idLowerCase = id.toLowerCase();
this.id = idLowerCase;
this.hash = idLowerCase.hashCode();
title = id;
this.id=this.title=id;
this.hash=id.hashCode();
}
public JSONObject encodeEmojis() throws JSONException {
@@ -103,7 +101,7 @@ public class TelegramStickersPack extends CustomStickersPack {
@Override
public boolean equals(Object obj) {
return obj instanceof TelegramStickersPack && ((TelegramStickersPack) obj).id.equalsIgnoreCase(id);
return obj instanceof TelegramStickersPack && ((TelegramStickersPack) obj).id.equals(id);
}
@Override
@@ -8,6 +8,7 @@ import android.util.Log;
import com.aefyr.tsg.g2.sql.TelegramStickersDbHelper;
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber;
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersPackInfo;
import ru.vtosters.lite.ui.fragments.VTSettings;
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
import java.io.File;
@@ -152,6 +153,7 @@ public class TelegramStickersService {
private void notifyActivePacksListChanged() {
getGlobalContext().sendBroadcast(new Intent(StickersFragment.ACTION_RELOAD));
getGlobalContext().sendBroadcast(new Intent(VTSettings.ACTION_INVALIDATE_TGS_COUNT));
if (listeners.isEmpty())
return;
@@ -222,7 +224,7 @@ public class TelegramStickersService {
final TelegramStickersPack newPack = pack;
final boolean isUpdate = update;
currentlyDownloading.add(id.toLowerCase());
currentlyDownloading.add(id);
notificationsHelper.packStartedDownloading(newPack);
@@ -6,9 +6,9 @@ import com.vk.api.base.ApiRequest;
import com.vk.dto.music.MusicTrack;
import com.vk.log.L;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.Genius;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.hooks.other.Preferences;
import java.util.ArrayList;
@@ -23,8 +23,8 @@ import com.vtosters.lite.R;
import com.vtosters.lite.ViewUtils;
import com.vtosters.lite.ui.MusicErrorViewHelper;
import com.vtosters.lite.ui.holder.RecyclerHolder;
import ru.vtosters.lite.music.Genius;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.Genius;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -2,7 +2,7 @@ package ru.vtosters.hooks;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.catalog.inject.CatalogJsonInjector;
import ru.vtosters.hooks.music.injectors.CatalogJsonInjector;
public class CatalogsHook {
public static JSONObject injectCatalogGetAudio(JSONObject json) {
@@ -6,11 +6,12 @@ import android.content.Context;
import android.os.Build;
import com.google.firebase.iid.FirebaseInstanceIdReceiver;
import com.google.firebase.iid.FirebaseInstanceIdReceiver2;
import com.google.firebase.iid.FirebaseInstanceIdReceiver3;
import com.vtosters.lite.R;
import ru.vtosters.lite.utils.AndroidUtils;
public class GmsHook {
private static final boolean needToSpoof = !isGmsInstalled() && isFakeGmsInstalled();
private static final boolean needToSpoof = !isGmsInstalled() && (isFakeGmsInstalled() || isFakeGms2Installed());
public static boolean isGmsInstalled() {
try {
@@ -30,8 +31,17 @@ public class GmsHook {
}
} // Microg Google Market Services check
public static boolean isFakeGms2Installed() {
try {
AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo("app.revanced.android.gms", 0);
return true;
} catch (Exception unused) {
return false;
}
} // Microg Google Market Services check
public static boolean isAnyServicesInstalled() {
return isGmsInstalled() || isFakeGmsInstalled();
return isGmsInstalled() || isFakeGmsInstalled() || isFakeGms2Installed();
}
public static void fixGapps() {
@@ -45,7 +55,7 @@ public class GmsHook {
} // Music channels fix
public static String replaceGMSPackage(String str) {
return needToSpoof ? str.replaceAll("com.google", "com.mgoogle") : str;
return needToSpoof ? str.replaceAll("com.google", (isFakeGms2Installed() ? "app.revanced" : "com.mgoogle")) : str;
}
public static String getFirebaseInstanceIdReceiver() {
@@ -53,6 +63,6 @@ public class GmsHook {
}
public static Class getFirebaseInstanceIdReceiverClass() {
return needToSpoof ? FirebaseInstanceIdReceiver2.class : FirebaseInstanceIdReceiver.class;
return needToSpoof ? (isFakeGms2Installed() ? FirebaseInstanceIdReceiver3.class : FirebaseInstanceIdReceiver2.class) : FirebaseInstanceIdReceiver.class;
}
}
@@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import b.h.g.k.VKProgressDialog;
import com.aefyr.tsg.g2.TelegramStickersService;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
@@ -13,7 +14,6 @@ import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.ssfs.UsersList;
import ru.vtosters.lite.themes.ThemesManager;
import ru.vtosters.lite.ui.dialogs.DisableBattery;
import ru.vtosters.lite.ui.dialogs.InstallGMS;
import ru.vtosters.lite.ui.dialogs.OTADialog;
import ru.vtosters.lite.ui.dialogs.Start;
@@ -23,14 +23,12 @@ import ru.vtosters.lite.utils.NavigatorUtils;
import ru.vtosters.lite.utils.VTVerifications;
import static ru.vtosters.hooks.other.Preferences.checkupdates;
import static ru.vtosters.lite.ui.dialogs.ServerDialog.sendRequest;
import static ru.vtosters.lite.utils.CacheUtils.getInstance;
import static ru.vtosters.lite.utils.NewsFeedFiltersUtils.setupFilters;
public class MainActivityInjector {
public static void inject(Activity activity) {
SystemThemeChangerHook.themeOnStart(activity);
sendRequest();
UsersList.getUsersList();
VTVerifications.load(activity);
@@ -47,9 +45,8 @@ public class MainActivityInjector {
}
if (Preferences.isNewBuild()
&&!ThemesUtils.isMonetTheme()
&&ThemesManager.canApplyCustomAccent())
{
&& !ThemesUtils.isMonetTheme()
&& ThemesManager.canApplyCustomAccent()) {
Preferences.updateBuildNumber();
updateBinsAndTmpArchive(activity);
}
@@ -63,8 +60,9 @@ public class MainActivityInjector {
Start.alert(activity);
InstallGMS.alert(activity);
DisableBattery.alert(activity);
// VKIDProtection.alert(activity);
//needs to show selected tgs pack count in settings after cold launch
TelegramStickersService.getInstance(activity);
}
private static void updateBinsAndTmpArchive(Activity activity) {
@@ -1,16 +0,0 @@
package ru.vtosters.hooks;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.FileCacheImplementation;
import java.io.File;
public class MusicCacheFilesHook {
public static File getTrackFile(String trackId) {
return new File(FileCacheImplementation.getTrackFolder(trackId), "track.mp3");
}
public static boolean isTrackExist(String trackId) {
return CacheDatabaseDelegate.isCached(trackId);
}
}
@@ -23,7 +23,7 @@ public class VKUIHook {
}
private static void applyVKUIStyles(WebView webView) {
if (getBoolValue("VKUI_INJ", true) && !webView.getUrl().contains("static.vk.com/memories")) {
if (getBoolValue("VKUI_INJ", true) && webView != null && webView.getUrl() != null && !webView.getUrl().contains("static.vk.com/memories")) {
if (!WebViewColoringUtils.isLoaded) {
WebViewColoringUtils.load();
}
@@ -20,7 +20,7 @@ public class VerificationsHook {
return true;
}
if (!getBoolValue("VT_Verification", true)) {
if (!getBoolValue("VT_Verification", true) || getBoolValue("serverFeaturesDisable", false)) {
return false;
}
@@ -32,7 +32,7 @@ public class VerificationsHook {
return true;
}
if (!getBoolValue("VT_Fire", true)) {
if (!getBoolValue("VT_Fire", true) || getBoolValue("serverFeaturesDisable", false)) {
return false;
}
@@ -40,10 +40,6 @@ public class VerificationsHook {
}
public static boolean hasDeveloper(JSONObject jSONObject) {
if (!getBoolValue("VT_Dev", true)) {
return false;
}
return VTVerifications.isDeveloper(VTVerifications.getId(jSONObject));
}
@@ -1,4 +1,4 @@
package ru.vtosters.lite.music.hook;
package ru.vtosters.hooks.music;
import android.content.Context;
import com.vk.music.fragment.MusicFragment;
@@ -1,4 +1,4 @@
package ru.vtosters.lite.music.hook;
package ru.vtosters.hooks.music;
import android.os.RemoteException;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
@@ -8,45 +8,29 @@ import com.vk.music.bottomsheets.a.MusicAction;
import com.vk.music.common.MusicPlaybackLaunchContext;
import com.vtosters.lite.R;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.VKXUtils;
import java.util.ArrayList;
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.*;
import static ru.vtosters.hooks.other.Preferences.milkshake;
import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.isCached;
import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.isVkxCached;
import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected;
public class MusicBottomSheetHook {
public static ArrayList<MusicAction> hook(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
public static ArrayList<MusicAction> hook(final ArrayList<MusicAction> actions, final MusicTrack musicTrack) {
if (musicTrack.F1()) return actions;
var trackid = asId(musicTrack);
if (isVkxInstalled()) {
actions.add(getPlayInVKXAction());
}
final var trackId = asId(musicTrack);
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
if (isIntegrationEnabled()) {
if (isVkxCached(trackid)) {
actions.add(getRemoveCacheTrackVkxAction());
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackVkxAction());
}
if (VKXUtils.isVkxCached(trackId)) actions.add(getRemoveCacheTrackVkxAction());
else if (isNetworkConnected()) actions.add(addToCacheTrackVkxAction());
} else {
if (isCached(trackid)) {
actions.add(getRemoveCacheTrackAction());
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackAction());
}
if (MusicCacheImpl.isCachedTrack(trackId)) actions.add(getRemoveCacheTrackAction());
else if (isNetworkConnected()) actions.add(addToCacheTrackAction());
}
if (isNetworkConnected()) {
actions.add(downloadAsMp3Action());
}
if (isNetworkConnected()) actions.add(downloadAsMp3Action());
return actions;
}
@@ -54,9 +38,7 @@ public class MusicBottomSheetHook {
public static ArrayList<MusicAction> hookDownloadBTN(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
if (musicTrack.F1()) return actions;
var trackid = asId(musicTrack);
if (isCached(trackid)) {
if (MusicCacheImpl.isCachedTrack(musicTrack.y1())) {
actions.add(new MusicAction(
R.id.music_action_toggle_download,
milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24,
@@ -85,7 +67,7 @@ public class MusicBottomSheetHook {
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
if (isIntegrationEnabled()) {
if (isVkxCached(playlist.a, playlist.b)) {
if (VKXUtils.isVkxCached(playlist.a, playlist.b)) {
actions.add(getRemoveCacheTrackVkxAction());
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackVkxAction());
@@ -124,7 +106,7 @@ public class MusicBottomSheetHook {
}
if (actionId == R.id.remove_from_cache) {
CacheDatabaseDelegate.removeTrackFromCache(asId(track));
MusicCacheImpl.removeTrack(asId(track));
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
return true;
} else if (actionId == R.id.add_to_cache) {
@@ -0,0 +1,16 @@
package ru.vtosters.hooks.music;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.File;
public class MusicCacheFilesHook {
public static File getTrackFile(String trackId) {
return MusicCacheStorageUtils.getTrackFile(trackId);
}
public static boolean isTrackExist(String trackId) {
return MusicCacheImpl.isCachedTrack(trackId);
}
}
@@ -1,4 +1,4 @@
package ru.vtosters.lite.music.hook;
package ru.vtosters.hooks.music;
import android.content.Context;
import androidx.annotation.AttrRes;
@@ -1,4 +1,4 @@
package ru.vtosters.lite.music.catalog.inject;
package ru.vtosters.hooks.music.injectors;
import android.util.Log;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
@@ -12,7 +12,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
@@ -48,7 +48,7 @@ public class CatalogJsonInjector {
setDefaultAudioPage(oldItems, catalog);
}
if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
var noPlaylists = !json.has("playlists");
if (noPlaylists) {
@@ -91,7 +91,7 @@ public class CatalogJsonInjector {
fixDailyMix(blocks);
if (!isUsersCatalog || !CacheDatabaseDelegate.hasTracks() || LibVKXClient.isIntegrationEnabled()) {
if (!isUsersCatalog || MusicCacheImpl.isEmpty() || LibVKXClient.isIntegrationEnabled()) {
return; // early return if not users catalog or no tracks or integration enabled
}
@@ -1,4 +1,4 @@
package ru.vtosters.lite.music.cache.injectors;
package ru.vtosters.hooks.music.injectors;
import android.os.RemoteException;
import android.text.TextUtils;
@@ -12,7 +12,7 @@ import io.reactivex.ObservableOnSubscribe;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
@@ -30,7 +30,7 @@ public class PlaylistInjector {
}
public static boolean eligibleForOfflineCaching() {
return CacheDatabaseDelegate.hasTracks();
return !MusicCacheImpl.isEmpty();
}
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
@@ -1,4 +1,4 @@
package ru.vtosters.lite.music.cache.injectors;
package ru.vtosters.hooks.music.injectors;
import com.vk.dto.music.MusicTrack;
import com.vk.music.model.IpcPlayerModelImpl;
@@ -1,4 +1,4 @@
package ru.vtosters.lite.music.cache.injectors;
package ru.vtosters.hooks.music.injectors;
import android.util.Log;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
@@ -11,7 +11,7 @@ import io.reactivex.ObservableOnSubscribe;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
import ru.vtosters.lite.utils.AccountManagerUtils;
@@ -56,7 +56,7 @@ public class TracklistInjector {
}
});
} else {
if (!CacheDatabaseDelegate.hasTracks()) {
if (MusicCacheImpl.isEmpty()) {
observableEmitter.b(parser.c(getEmptyCatalog()));
} else {
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
@@ -135,7 +135,7 @@ public class TracklistInjector {
button.put("action", action);
button.put("block_id", "shuffle");
button.put("ref_items_count", CacheDatabaseDelegate.getTrackCount());
button.put("ref_items_count", MusicCacheImpl.getTracksCount());
button.put("ref_layout_name", "list");
button.put("ref_data_type", "music_audios");
@@ -158,7 +158,7 @@ public class TracklistInjector {
JSONArray blocks = firstSection.getJSONArray("blocks");
if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
boolean noPlaylists = hasNoPlaylists(blocks);
if (noPlaylists) {
@@ -263,7 +263,7 @@ public class Preferences {
}
public static boolean stories() {
return getBoolValue("stories", true);
return getBoolValue("stories", false);
}
public static boolean swipe() {
@@ -292,12 +292,12 @@ public class Preferences {
}
public static boolean isNewBuild() {
if(Preferences.getBoolValue("invalidate_theme_cache_each_update",true))
if (Preferences.getBoolValue("invalidate_theme_cache_each_update", true))
try {
return AndroidUtils.getGlobalContext()
.getPackageManager()
.getPackageInfo(AndroidUtils.getPackageName(), 0)
.lastUpdateTime != getPreferences().getLong("setupTime", 0L);
.getPackageManager()
.getPackageInfo(AndroidUtils.getPackageName(), 0)
.lastUpdateTime != getPreferences().getLong("setupTime", 0L);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
@@ -28,6 +28,7 @@ import com.vtosters.lite.data.ThemeTracker;
import ru.vtosters.lite.deviceinfo.OEMDetector;
import ru.vtosters.lite.themes.ThemesCore;
import ru.vtosters.lite.themes.ThemesHacks;
import ru.vtosters.lite.themes.ThemesManager;
import ru.vtosters.lite.ui.wallpapers.WallpapersHooks;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.lite.utils.WebViewColoringUtils;
@@ -59,7 +60,7 @@ public class ThemesUtils {
activity = LifecycleUtils.getCurrentActivity();
}
VKThemeHelper.theme(theme, activity, fl);
if (isMonetTheme()) ThemesCore.clear();
if (isMonetTheme() || ThemesManager.canApplyCustomAccent()) ThemesCore.clear();
if (restartActivity) activity.recreate();
ThemeTracker.a();
WebViewColoringUtils.isLoaded = false;
@@ -6,8 +6,8 @@ import com.vk.core.util.ToastUtils;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import com.vtosters.lite.R;
import ru.vtosters.hooks.MusicCacheFilesHook;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.hooks.music.MusicCacheFilesHook;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
import ru.vtosters.lite.music.downloader.AudioGet;
@@ -58,10 +58,10 @@ public class AudioDownloader {
}
public static void cacheTrack(MusicTrack track) {
var trackId = track.y1();
var trackId = LibVKXClient.asId(track);
if (CacheDatabaseDelegate.isCached(trackId)) {
CacheDatabaseDelegate.removeTrackFromCache(LibVKXClient.asId(track));
if (MusicCacheImpl.isCachedTrack(trackId)) {
MusicCacheImpl.removeTrack(trackId);
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
return;
}
@@ -34,43 +34,15 @@ import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class FoafBase {
private static final Pattern FOAF_REGEX = Pattern.compile("<ya:created dc:date=\"(.+?)\"");
private static final Pattern FOAF_REGEX_LAST_SEEN = Pattern.compile("<ya:lastLoggedIn dc:date=\"(.*)(((\\+|-)\\d\\d):(\\d\\d))\"");
private static final String API_VKNEXT = "https://api.vtosters.app/v1/getBypassedOnlineInfo?json=1&ids=";
private static final OkHttpClient client = VtOkHttpClient.getInstance();
public static long getLastSeen(long origtime, int id) throws ParseException {
var request = new okhttp3.Request.a()
.b(getLink(id))
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
String response = null;
try {
response = client.a(request).execute().a().g();
} catch (IOException e) {
Log.e("FoafBase", e.getMessage());
}
Matcher matcher = FOAF_REGEX_LAST_SEEN.matcher(response);
if (!matcher.find()) {
return origtime;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("Europe/Moscow"));
Date date = sdf.parse(Objects.requireNonNull(matcher.group(1)));
sdf.setTimeZone(TimeZone.getDefault());
return sdf.parse(sdf.format(date)).getTime() / 1000;
}
public static JSONObject getBypassedOnlineInfo(int id) throws JSONException {
var request = new okhttp3.Request.a()
.b(API_VKNEXT + id)
.a();
String response = null;
String response;
var dummy = new JSONObject();
dummy.put("last_seen", 0);
@@ -86,7 +58,6 @@ public class FoafBase {
}
public static JSONObject getBypassedOnlineInfo(String ids) {
var request = new okhttp3.Request.a()
.b(API_VKNEXT + ids)
.a();
@@ -135,7 +106,15 @@ public class FoafBase {
progressDialog.cancel();
Matcher matcher = FOAF_REGEX.matcher(str);
matcher.find();
String normalHumanDate = getNormalHumanDate(Objects.requireNonNull(matcher.group(1)));
String m = matcher.group(1);
if (m == null) {
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> makeText(getGlobalContext(), context.getString(R.string.foaferr), LENGTH_SHORT).show());
return;
}
String normalHumanDate = getNormalHumanDate(m);
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> new VkAlertDialog.Builder(context)
.setTitle(R.string.addinf)
@@ -147,17 +126,14 @@ public class FoafBase {
.show());
} catch (Exception e) {
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> makeText(getGlobalContext(), context.getString(R.string.foaferr), LENGTH_SHORT).show());
e.printStackTrace();
}
}
private static long daysPassedFromFoafDate(String str) {
try {
@SuppressLint("SimpleDateFormat")
Date parse = new SimpleDateFormat("yyyy-MM-dd").parse(str.split(" ")[1]);
return TimeUnit.DAYS.convert(new Date().getTime() - parse.getTime(), TimeUnit.MILLISECONDS);
} catch (ParseException e) {
e.printStackTrace();
return -1;
}
}
@@ -7,14 +7,13 @@ import androidx.annotation.Nullable;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.dto.music.MusicTrack;
import com.vtosters.lite.R;
import okhttp3.Callback;
import okhttp3.*;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
@@ -47,7 +46,7 @@ public class LastFMScrobbler {
return;
}
if (Preferences.autocache() && !CacheDatabaseDelegate.isCached(uid)) {
if (Preferences.autocache() && !MusicCacheImpl.isCachedTrack(uid)) {
if (LibVKXClient.isIntegrationEnabled()) {
LibVKXClient.getInstance().runOnService(service -> {
try {
@@ -1,158 +0,0 @@
package ru.vtosters.lite.music;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import java8.util.concurrent.CompletableFuture;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.converter.ts.FFMpeg;
import ru.vtosters.lite.music.converter.ts.TSMerger;
import ru.vtosters.lite.music.downloader.ThumbnailDownloader;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
import javax.crypto.NoSuchPaddingException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class M3UDownloader implements ITrackDownloader {
private static final OkHttpClient client = VtOkHttpClient.getInstance();
public static M3UDownloader getInstance() {
return Holder.INSTANCE;
}
public static String getTitle(MusicTrack track) {
String title = null;
if (track.f != null) {
title = track.f;
if (!track.g.isEmpty()) {
title += " (" + track.g + ")";
}
}
return title;
}
public void downloadTrack(MusicTrack track, File outDir, Callback callback, boolean cache) {
if (track.D.isEmpty()) {
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
callback.onFailure();
return;
}
try {
Request request = new Request.a().b(track.D).a();
String response = client.a(request).execute().a().g();
parse(response, outDir, callback, track, cache);
} catch (IOException e) {
e.printStackTrace();
callback.onFailure();
}
}
private void parse(String payload, File outDir, Callback callback, MusicTrack track, boolean cache) throws UnsupportedEncodingException {
if (cache) downloadThumbnails(track);
VKM3UParser parser = new VKM3UParser(track.D.substring(0, track.D.lastIndexOf("/") + 1), payload);
AtomicInteger progress = new AtomicInteger(0);
List<CompletableFuture<Void>> futures = createFutures(parser, progress, callback, track);
var resultTs = getResultTsFile(track);
var resultMp3 = getResultMp3File(outDir, cache, track);
callback.onProgress(5);
var future = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
future.join();
future.exceptionally(throwable -> {
callback.onFailure();
return null;
}).thenApply(v -> TSMerger.merge(getTsesDir(track), resultTs))
.thenApply(mergeResult -> mergeResult && FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track))
.thenApply(convertResult -> {
if (convertResult)
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / parser.mTransportStreams.size()));
else callback.onFailure();
return convertResult;
}).thenRun(() -> IOUtils.deleteRecursive(getTsesDir(track))).thenRun(() -> {
callback.onSuccess();
if (cache) CacheDatabaseDelegate.insertTrack(track);
});
}
private void downloadThumbnails(MusicTrack track) {
try {
ThumbnailDownloader.downloadThumbnails(track);
} catch (IOException e) {
e.printStackTrace();
}
}
private List<CompletableFuture<Void>> createFutures(VKM3UParser parser, AtomicInteger progress, Callback callback, MusicTrack track) {
List<CompletableFuture<Void>> futures = new ArrayList<>(parser.mTransportStreams.size());
var tses = parser.mTransportStreams;
for (var i = 0; i < tses.size(); ++i) {
final var ts = tses.get(i);
final var filename = i + ".ts";
futures.add(CompletableFuture.runAsync(() -> processTransportStream(parser, ts, filename, progress, tses.size(), callback, track)));
}
return futures;
}
private void processTransportStream(VKM3UParser parser, TransportStream ts, String filename, AtomicInteger progress, int size, Callback callback, MusicTrack track) {
try {
InputStream mediaIs = IOUtils.openStream(ts.getMediaSegmentUri());
byte[] content;
if (ts.needDecoding()) {
InputStream keyPubIs = IOUtils.openStream(ts.getKeyURL());
content = IOUtils.decodeStream(mediaIs, IOUtils.readFully(keyPubIs));
keyPubIs.close();
} else {
content = IOUtils.readFully(mediaIs);
}
File tsDump = new File(getTsesDir(track), filename);
IOUtils.writeToFile(tsDump, content);
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / size));
callback.onSizeReceived((long) content.length * size, parser.mHeapSize);
mediaIs.close();
} catch (IOException | NoSuchPaddingException | NoSuchAlgorithmException |
InvalidAlgorithmParameterException | InvalidKeyException e) {
callback.onFailure();
throw new RuntimeException(e);
}
}
private File getTsesDir(MusicTrack track) {
var filesDir = AndroidUtils.getGlobalContext().getFilesDir();
var tsesDir = new File(filesDir, String.valueOf(track.d));
tsesDir.mkdirs();
return tsesDir;
}
private File getResultTsFile(MusicTrack track) {
return new File(getTsesDir(track), "result.ts");
}
private File getResultMp3File(File outDir, boolean cache, MusicTrack track) {
var fileName = IOUtils.getValidFileName((cache ? "track" : track.C + " - " + getTitle(track)) + ".mp3");
return new File(outDir, fileName);
}
// Initialization-on-demand
private static class Holder {
private static final M3UDownloader INSTANCE = new M3UDownloader();
}
}
@@ -1,36 +0,0 @@
package ru.vtosters.lite.music;
import android.text.TextUtils;
public class TransportStream {
private final String mMediaSegmentUri;
private String mKeyUri;
private String mMediaSegmentName;
public TransportStream(String baseURI, String mediaSegmentName) {
this.mMediaSegmentUri = baseURI + mediaSegmentName;
}
public TransportStream(String keyURI, String baseURI, String mediaSegmentName) {
this.mKeyUri = keyURI;
this.mMediaSegmentUri = baseURI + mediaSegmentName;
this.mMediaSegmentName = mediaSegmentName;
}
public boolean needDecoding() {
return !TextUtils.isEmpty(mKeyUri);
}
public String getKeyURL() {
return mKeyUri;
}
public String getMediaSegmentUri() {
return this.mMediaSegmentUri;
}
public String getMediaSegmentName() {
return this.mMediaSegmentName;
}
}
@@ -1,126 +0,0 @@
package ru.vtosters.lite.music;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* The parser for extended M3U playlist format used by VK.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#ref-M3U">M3U</a>
**/
public class VKM3UParser {
//Playlist types
static public int PLAYLIST_TYPE_VOD = 0;
static public int PLAYLIST_TYPE_EVENT = 1;
private final String mData;
//region Extended M3U global extensions
public List<TransportStream> mTransportStreams = new ArrayList<>();
/**
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.1.2">EXT-X-VERSION</a>
**/
public int mVersion = -1;
/**
* The EXT-X-ALLOW-CACHE tag is used in protocol version 6 or lower.
* If the protocol version is upgraded by VK, we will not support that.
**/
public boolean mAllowCache = false;
/**
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.1">EXT-X-TARGETDURATION</a>
**/
public int mTargetDuration = -1;
/**
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.2">EXT-X-MEDIA-SEQUENCE</a>
**/
public int mMediaSequence = -1;
/**
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.3">EXT-X-DISCONTINUITY-SEQUENCE</a>
**/
public int mDiscontinuitySequence = -1;
//endregion
/**
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.5">EXT-X-PLAYLIST-TYPE</a>
**/
public int mPlaylistType = -1;
/**
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.6">EXT-X-I-FRAMES-ONLY</a>
**/
public boolean mIFramesOnly = false;
public long mHeapSize = 0;
public VKM3UParser(String baseUri, String data) {
mData = data;
init(baseUri);
}
public static boolean isTS(String str) {
return str.matches(".+\\.ts.*");
}
private void init(String baseUri) {
if (TextUtils.isEmpty(mData)) throw new NullPointerException("mData==null");
Scanner scanner = new Scanner(mData);
String line = scanner.nextLine();
if (!"#EXTM3U".equals(line))
throw new IllegalStateException(String.format("Unknown initial M3U tag: %s", line));
boolean aes128 = false;
String keyUri = "";
while (scanner.hasNextLine()) {
line = scanner.nextLine();
if ('#' == line.charAt(0)) {
parseTag(line);
if (line.startsWith("#EXT-X-KEY:") && line.contains("METHOD=AES-128")) {
aes128 = true;
keyUri = getKeyUri(line, baseUri);
}
} else if (isTS(line)) {
addTransportStream(aes128, keyUri, baseUri, line);
aes128 = false;
} else {
throw new RuntimeException(String.format("Failed to parse: %s", line));
}
mHeapSize += line.getBytes().length;
}
}
private void parseTag(String line) {
if (line.startsWith("#EXT-X-VERSION:"))
mVersion = Integer.parseInt(line.substring(15));
else if (line.startsWith("#EXT-X-ALLOW-CACHE:"))
mAllowCache = "YES".equalsIgnoreCase(line.substring(19));
else if (line.startsWith("#EXT-X-TARGETDURATION:"))
mTargetDuration = Integer.parseInt(line.substring(22));
else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE:"))
mMediaSequence = Integer.parseInt(line.substring(22));
else if (line.startsWith("#EXT-X-DISCONTINUITY-SEQUENCE:"))
mDiscontinuitySequence = Integer.parseInt(line.substring(30));
else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:"))
parsePlaylistType(line.substring(21));
else if (line.startsWith("#EXT-X-I-FRAMES-ONLY"))
mIFramesOnly = true;
}
private void parsePlaylistType(String type) {
switch (type) {
case "VOD" -> mPlaylistType = PLAYLIST_TYPE_VOD;
case "EVENT" -> mPlaylistType = PLAYLIST_TYPE_EVENT;
default -> throw new IllegalStateException(String.format("Unknown playlist type: %s", type));
}
}
private String getKeyUri(String line, String baseUri) {
String substr = line.substring(11);
substr = substr.substring(substr.indexOf('"') + 1, substr.lastIndexOf('"'));
//TODO: supposed endpoint
return substr.startsWith("http") ? substr : baseUri + substr;
}
private void addTransportStream(boolean aes128, String keyUri, String baseUri, String line) {
TransportStream ts = aes128
? new TransportStream(keyUri, baseUri, line)
: new TransportStream(baseUri, line);
mTransportStreams.add(ts);
}
}
@@ -1,343 +0,0 @@
package ru.vtosters.lite.music.cache;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
import com.vk.core.util.ToastUtils;
import com.vk.dto.music.MusicTrack;
import com.vtosters.lite.R;
import org.json.JSONObject;
import ru.vtosters.lite.utils.AndroidUtils;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import static ru.vtosters.lite.music.cache.FileCacheImplementation.getCacheDir;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
import static ru.vtosters.lite.utils.IOUtils.deleteRecursive;
public class CacheDatabaseDelegate {
private static final int DB_VERSION = 0x1;
private static final String DB_NAME = "vt_lite_cache.db";
private static final String TABLE_NAME = "tracks";
private static final String COLUMN_ID = "id";
private static final String COLUMN_TRACK_ID = "trackId";
private static final String COLUMN_ALBUM_ID = "albumId";
private static final String COLUMN_TITLE = "title";
private static final String COLUMN_SUBTITLE = "subtitle";
private static final String COLUMN_ARTIST = "artist";
private static final String COLUMN_ALBUM_TITLE = "albumTitle";
private static final String COLUMN_EXPLICIT = "explicit";
private static final String COLUMN_HAS_ARTWORK = "hasArtwork";
private static final String COLUMN_DURATION = "duration";
private static DbHelper helperInstance;
protected static DbHelper getHelper() {
if (helperInstance == null) helperInstance = new DbHelper(getGlobalContext());
return helperInstance;
}
// You MUST call this method ONLY AFTER TRACK IS DOWNLOADED!
public static void insertTrack(MusicTrack track) {
try {
var json = track.J();
var albumNode = json.optJSONObject("album");
var hasArtwork = false;
var albumId = -1;
var albumName = "";
if (albumNode != null) {
hasArtwork = albumNode.has("thumb");
albumId = albumNode.getInt("id");
albumName = albumNode.getString("title");
}
var formula = String.format(
"INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES ('%s', %s, '%s', '%s', '%s', '%s', %s, %s, %s)",
TABLE_NAME,
COLUMN_TRACK_ID, COLUMN_ALBUM_ID, COLUMN_TITLE, COLUMN_SUBTITLE, COLUMN_ARTIST, COLUMN_ALBUM_TITLE, COLUMN_EXPLICIT, COLUMN_DURATION, COLUMN_HAS_ARTWORK,
track.y1(),
albumId,
URLEncoder.encode(json.getString("title"), "UTF-8"),
URLEncoder.encode(json.optString("subtitle"), "UTF-8"),
URLEncoder.encode(json.getString("artist"), "UTF-8"),
URLEncoder.encode(albumName, "UTF-8"),
json.optInt("is_explicit"),
json.getInt("duration"),
hasArtwork ? 1 : 0
);
getHelper().getReadableDatabase().execSQL(formula);
} catch (Exception e) {
e.printStackTrace();
ToastUtils.a(AndroidUtils.getString(R.string.track_bd_insert_err) + " [" + e.getMessage() + "]", true);
}
}
public static void insertTracks(List<MusicTrack> tracks) {
for (var track : tracks) insertTrack(track);
}
public static void clear() {
getHelper().getReadableDatabase().execSQL(String.format("DELETE FROM %s", TABLE_NAME));
deleteRecursive(getCacheDir());
sendToast(AndroidUtils.getString(R.string.cached_tracks_deleted));
}
@NonNull
private static JSONObject reconstructFromDb(Cursor cursor) {
try {
var trackId = cursor.getString(getColumnIndex(cursor, COLUMN_TRACK_ID));
var splits = trackId.split("_");
var id = Integer.parseInt(splits[1]);
var ownerId = Integer.parseInt(splits[0]);
return new JSONObject()
.put("url", FileCacheImplementation.getServerPath())
.put("id", id)
.put("owner_id", ownerId)
.put("title", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_TITLE)), "UTF-8"))
.put("subtitle", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_SUBTITLE)), "UTF-8"))
.put("artist", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_ARTIST)), "UTF-8"))
.put("duration", cursor.getInt(getColumnIndex(cursor, COLUMN_DURATION)))
.put("is_explicit", cursor.getInt(getColumnIndex(cursor, COLUMN_EXPLICIT)))
.put("access_key", 2281488)
.putOpt("album", reconstructAlbumFromDb(cursor, trackId));
} catch (Exception e) {
e.printStackTrace();
return new JSONObject();
}
}
@Nullable
private static JSONObject reconstructAlbumFromDb(Cursor cursor, String trackId) {
try {
if (!FileCacheImplementation.getTrackThumbnail(trackId, 300).exists()
&& !FileCacheImplementation.getTrackThumbnail(trackId, 600).exists()
&& !FileCacheImplementation.getTrackThumbnail(trackId, 1200).exists()
) {
var ownerId = Integer.parseInt(trackId.split("_")[1]);
return new JSONObject()
.put("id", cursor.getInt(getColumnIndex(cursor, COLUMN_ALBUM_ID)))
.put("owner_id", ownerId)
.put("title", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_ALBUM_TITLE)), "UTF-8"))
.putOpt("thumb", reconstructAlbumThumbFromDb(cursor, trackId));
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Nullable
private static JSONObject reconstructAlbumThumbFromDb(Cursor cursor, String trackId) {
try {
if (cursor.getInt(getColumnIndex(cursor, COLUMN_HAS_ARTWORK)) != 0)
new JSONObject()
.put("height", 600)
.put("width", 600)
.putOpt("photo_300", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 300)))
.putOpt("photo_600", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 600)))
.putOpt("photo_1200", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 1200)));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static int getColumnIndex(Cursor cursor, String name) {
return cursor.getColumnIndexOrThrow(name);
}
public static boolean hasTracks() {
if (LibVKXClient.isIntegrationEnabled()) {
return true;
}
var formula = String.format(
"SELECT COUNT(%s) FROM %s",
COLUMN_ID,
TABLE_NAME
);
var cursor = getHelper().getReadableDatabase().rawQuery(formula, null);
if (cursor != null) {
cursor.moveToFirst();
int count = cursor.getInt(0);
cursor.close();
return count > 0;
}
return false;
}
public static List<MusicTrack> getTracks() {
return getTracks(String.format(
"SELECT * FROM %s",
TABLE_NAME
));
}
public static List<MusicTrack> getTracksAsPlaylist() {
return getTracks(String.format(
"SELECT * FROM %s WHERE %s > -1 GROUP BY %s HAVING COUNT(*) > 1 ORDER by id DESC",
TABLE_NAME,
COLUMN_ALBUM_ID,
COLUMN_ALBUM_ID
));
}
public static List<MusicTrack> getTracksByAlbum(String id) {
return getTracks(String.format(
"SELECT COUNT(%s) FROM %s WHERE %s = %s",
COLUMN_ID,
TABLE_NAME,
COLUMN_ALBUM_ID,
id
));
}
public static List<MusicTrack> getTracksByAlbumOne(String id) {
return getTracks(String.format(
"SELECT * FROM %s WHERE %s = %s LIMIT 1",
TABLE_NAME,
COLUMN_ALBUM_ID,
id
));
}
public static List<MusicTrack> getTracks(String query) {
List<MusicTrack> list = new ArrayList<>();
try (Cursor cursor = getHelper().getReadableDatabase().rawQuery(query, new String[0])) {
while (cursor.moveToNext()) {
list.add(new MusicTrack(reconstructFromDb(cursor)));
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
public static boolean isCached(String trackId) {
var formula = String.format(
"SELECT COUNT(%s) FROM %s WHERE %s = '%s'",
COLUMN_ID,
TABLE_NAME,
COLUMN_TRACK_ID,
trackId
);
try {
Cursor size = getHelper().getReadableDatabase().rawQuery(formula, null);
if (size != null) {
size.moveToFirst();
int count = size.getInt(0);
size.close();
return count != 0;
}
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public static boolean isVkxCached(int playlist_id, int owner_id) {
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
@Override
public Boolean run(ILibVkxService service) {
try {
return service.isPlaylistCached(playlist_id, owner_id);
} catch (RemoteException e) {
return false;
}
}
@Override
public Boolean defaultValue() {
return false;
}
});
}
public static boolean isVkxCached(String trackId) {
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
@Override
public Boolean run(ILibVkxService service) {
try {
var splits = trackId.split("_");
var id = Integer.parseInt(splits[1]);
var owner = Integer.parseInt(splits[0]);
return service.isTrackCached(id, owner);
} catch (RemoteException e) {
return false;
}
}
@Override
public Boolean defaultValue() {
return false;
}
});
}
public static void removeTrackFromCache(String trackId) {
deleteRecursive(FileCacheImplementation.getTrackFolder(trackId));
var formula = String.format(
"DELETE FROM %s WHERE %s = '%s'",
TABLE_NAME,
COLUMN_TRACK_ID,
trackId
);
getHelper().getWritableDatabase().execSQL(formula);
}
public static long getTrackCount() {
if (!LibVKXClient.isIntegrationEnabled())
return DatabaseUtils.queryNumEntries(getHelper().getReadableDatabase(), TABLE_NAME);
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<Long>() {
@Override
public Long run(ILibVkxService service) {
try {
return (long) service.getCache().size();
} catch (RemoteException e) {
e.printStackTrace();
return defaultValue();
}
}
@Override
public Long defaultValue() {
return 0L;
}
});
}
private static class DbHelper extends SQLiteOpenHelper {
public DbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public void onCreate(SQLiteDatabase db) {
var formula = String.format(
"CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s TEXT, %s INTEGER, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s INTEGER, %s INTEGER, %s INTEGER)",
TABLE_NAME,
COLUMN_ID, COLUMN_TRACK_ID, COLUMN_ALBUM_ID, COLUMN_TITLE, COLUMN_SUBTITLE, COLUMN_ARTIST, COLUMN_ALBUM_TITLE, COLUMN_EXPLICIT, COLUMN_DURATION, COLUMN_HAS_ARTWORK
);
db.execSQL(formula);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}
@@ -1,50 +0,0 @@
package ru.vtosters.lite.music.cache;
import android.net.Uri;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.hooks.MusicCacheFilesHook;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
import java.io.File;
public class FileCacheImplementation {
public static void clear() {
CacheDatabaseDelegate.clear();
IOUtils.deleteRecursive(getCacheDir(), false);
}
public static File getCacheDir() {
return AndroidUtils.getGlobalContext().getExternalFilesDir("vt_tracks");
}
public static File getTrackFolder(String trackId) {
var dir = new File(getCacheDir(), trackId);
dir.mkdirs();
return dir;
}
public static File getThumbnailsFolder(String trackId) {
var dir = new File(getTrackFolder(trackId), "thumbs");
dir.mkdirs();
return dir;
}
public static File getTrackFile(MusicTrack track) {
return MusicCacheFilesHook.getTrackFile(track.y1());
}
public static File getTrackThumbnail(String trackId, int factor) {
return new File(getThumbnailsFolder(trackId), "thumb_" + factor + ".png");
}
public static String getFileUri(File file) {
if (!file.exists()) return null;
return Uri.fromFile(file).toString();
}
public static String getServerPath() {
return "https://vtosters.app";
}
}
@@ -0,0 +1,85 @@
package ru.vtosters.lite.music.cache;
import android.content.Context;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
import java.util.List;
public class MusicCacheDbDelegate {
static synchronized private MusicCacheDb connectToDb(Context context) {
synchronized (MusicCacheDb.class) {
return new MusicCacheDb(context);
}
}
public static void addTrack(Context context,
String trackId,
String albumId,
String title,
String subtitle,
String artist,
String albumTitle,
boolean explicit,
int duration,
boolean hasArtwork) {
try (var db = connectToDb(context)) {
db.addTrack(
trackId,
albumId,
title,
subtitle,
artist,
albumTitle,
explicit,
duration,
hasArtwork);
}
}
public static void removeTrack(Context context, String trackId) {
try (var db = connectToDb(context)) {
db.deleteTrack(trackId);
}
}
public static List<MusicTrack> getAllTracks(Context context) {
try (var db = connectToDb(context)) {
return db.getAllTracks();
}
}
public static List<MusicTrack> getAlbumById(Context context, String albumId) {
try (var db = connectToDb(context)) {
return db.getAlbum(albumId);
}
}
public static List<MusicTrack> getFirstAlbumTrack(Context context, String albumId) {
try (var db = connectToDb(context)) {
return db.getFirstAlbumTrack(albumId);
}
}
public static List<MusicTrack> getPlaylist(Context context) {
try (var db = connectToDb(context)) {
return db.getPlaylist();
}
}
public static long getTracksCount(Context context) {
try (var db = connectToDb(context)) {
return db.getTracksCount();
}
}
public static boolean isCachedTrack(Context context, String trackId) {
try (var db = connectToDb(context)) {
return db.isCachedTrack(trackId);
}
}
public static void drop(Context context) {
context.deleteDatabase(MusicCacheDb.Constants.DB_NAME);
}
}
@@ -0,0 +1,83 @@
package ru.vtosters.lite.music.cache;
import android.os.RemoteException;
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.util.List;
public class MusicCacheImpl {
public static void addTrack(MusicTrack track) {
MusicCacheDbDelegate.addTrack(
AndroidUtils.getGlobalContext(),
track.y1(),
track.I != null ? track.I.getId() + "" : "-1",
track.f,
track.g,
track.C,
track.I != null ? track.I.getTitle() : "",
track.K,
track.h,
track.I != null && track.I.u1() != null);
}
public static void removeTrack(String trackId) {
MusicCacheDbDelegate.removeTrack(AndroidUtils.getGlobalContext(), trackId);
MusicCacheStorageUtils.removeTrackDirById(trackId);
}
public static List<MusicTrack> getAllTracks() {
return MusicCacheDbDelegate.getAllTracks(AndroidUtils.getGlobalContext());
}
public static List<MusicTrack> getAlbumById(String albumId) {
return MusicCacheDbDelegate.getAlbumById(AndroidUtils.getGlobalContext(), albumId);
}
public static List<MusicTrack> getFirstAlbumTrack(String albumId) {
return MusicCacheDbDelegate.getFirstAlbumTrack(AndroidUtils.getGlobalContext(), albumId);
}
public static List<MusicTrack> getPlaylist() {
return MusicCacheDbDelegate.getPlaylist(AndroidUtils.getGlobalContext());
}
public static long getTracksCount() {
return !LibVKXClient.isIntegrationEnabled()
? MusicCacheDbDelegate.getTracksCount(AndroidUtils.getGlobalContext())
: LibVKXClient.getInstance().runOnServiceSync(
new LibVKXClientImpl.LibVKXActionGeneric<Long>() {
@Override
public Long run(ILibVkxService service) {
try {
return (long) service.getCache().size();
} catch (RemoteException e) {
e.printStackTrace();
return defaultValue();
}
}
@Override
public Long defaultValue() {
return 0L;
}
});
}
public static boolean isCachedTrack(String trackId) {
return MusicCacheDbDelegate.isCachedTrack(AndroidUtils.getGlobalContext(), trackId);
}
public static boolean isEmpty() {
return !LibVKXClient.isIntegrationEnabled() && getTracksCount() == 0L;
}
public static void clear() {
MusicCacheDbDelegate.drop(AndroidUtils.getGlobalContext());
MusicCacheStorageUtils.clear();
}
}
@@ -0,0 +1,246 @@
package ru.vtosters.lite.music.cache.db;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.Nullable;
import com.vk.dto.music.AlbumLink;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Thumb;
import org.json.JSONObject;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
/**
* <p class="note"><strong>Note:</strong> the {@link AutoCloseable} interface was
* first added in the {@link android.os.Build.VERSION_CODES#Q} release for the {@link SQLiteOpenHelper} </p>
*/
public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //for pre-Q
public MusicCacheDb(Context context) {
super(context, Constants.DB_NAME, null, Constants.DV_VERSION);
}
private static MusicTrack fromCursor(Cursor cur) {
try {
int trackIdIndex = cur.getColumnIndex(Constants.COLUMN_TRACK_ID);
int titleIndex = cur.getColumnIndex(Constants.COLUMN_TITLE);
int subtitleIndex = cur.getColumnIndex(Constants.COLUMN_SUBTITLE);
int artistIndex = cur.getColumnIndex(Constants.COLUMN_ARTIST);
int durationIndex = cur.getColumnIndex(Constants.COLUMN_DURATION);
int explicitIndex = cur.getColumnIndex(Constants.COLUMN_EXPLICIT);
int albumIdIndex = cur.getColumnIndex(Constants.COLUMN_ALBUM_ID);
int albumTitleIndex = cur.getColumnIndex(Constants.COLUMN_ALBUM_TITLE);
if (trackIdIndex == -1 || titleIndex == -1 || subtitleIndex == -1 || artistIndex == -1 || durationIndex == -1 || explicitIndex == -1 || albumIdIndex == -1) {
return null;
}
String trackId = cur.getString(trackIdIndex);
String[] splits = trackId.split("_");
if (splits.length != 2) {
return null;
}
MusicTrack res = new MusicTrack();
res.J = Integer.MAX_VALUE + ""; //access_key
res.D = "https://vtosters.app"; //url
res.d = Integer.parseInt(splits[1]); //id
res.e = Integer.parseInt(splits[0]); //owner_id
res.f = URLDecoder.decode(cur.getString(titleIndex), "UTF-8"); //title
res.g = URLDecoder.decode(cur.getString(subtitleIndex), "UTF-8"); //subtitle
res.C = URLDecoder.decode(cur.getString(artistIndex), "UTF-8"); //artist
res.h = cur.getInt(durationIndex); //duration
res.K = cur.getInt(explicitIndex) > 0; //is_explicit
int albumId = Integer.parseInt(cur.getString(albumIdIndex));
String albumTitle = albumTitleIndex != -1 ? cur.getString(albumTitleIndex) : null;
JSONObject node = new JSONObject();
File f300 = MusicCacheStorageUtils.getTrackThumb(trackId, 300);
File f600 = MusicCacheStorageUtils.getTrackThumb(trackId, 600);
File f1200 = MusicCacheStorageUtils.getTrackThumb(trackId, 1200);
if (f300.exists()) node.putOpt("photo_300", Uri.fromFile(f300).toString());
if (f600.exists()) node.putOpt("photo_600", Uri.fromFile(f600).toString());
if (f1200.exists()) node.putOpt("photo_1200", Uri.fromFile(f1200).toString());
res.I = new AlbumLink(
albumId,
res.e,
res.J,
albumTitle,
node.length() > 0
? new Thumb(node.put("width", 600)
.put("height", 600))
: null
); //album
return res;
} catch (Throwable ignored) {
// ignored
}
return null;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(Constants.CREATE_QUERY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < newVersion) {
List<MusicTrack> tracks = getTracksWithCursor(db.query(Constants.TABLE_NAME, null, null, null, null, null, null));
db.execSQL(Constants.DROP_QUERY);
onCreate(db);
for (MusicTrack track : tracks) {
ContentValues vals = new ContentValues();
vals.put(Constants.COLUMN_TRACK_ID, track.y1());
vals.put(Constants.COLUMN_ALBUM_ID, track.I != null ? track.I.getId() + "" : "-1");
vals.put(Constants.COLUMN_TITLE, track.f);
vals.put(Constants.COLUMN_SUBTITLE, track.g);
vals.put(Constants.COLUMN_ARTIST, track.C);
vals.put(Constants.COLUMN_ALBUM_TITLE, track.I != null ? track.I.getTitle() : "");
vals.put(Constants.COLUMN_EXPLICIT, Boolean.compare(track.K, true));
vals.put(Constants.COLUMN_DURATION, track.h);
vals.put(Constants.COLUMN_HAS_ARTWORK, track.I != null && track.I.u1() != null);
db.insert(Constants.TABLE_NAME, null, vals);
}
}
}
public void addTrack(String trackId, String albumId, String title, String subtitle, String artist, String albumTitle, boolean explicit, int duration, boolean hasArtwork) {
ContentValues vals = new ContentValues();
vals.put(Constants.COLUMN_TRACK_ID, trackId);
vals.put(Constants.COLUMN_ALBUM_ID, albumId);
vals.put(Constants.COLUMN_TITLE, title);
vals.put(Constants.COLUMN_SUBTITLE, subtitle);
vals.put(Constants.COLUMN_ARTIST, artist);
vals.put(Constants.COLUMN_ALBUM_TITLE, albumTitle);
vals.put(Constants.COLUMN_EXPLICIT, Boolean.compare(explicit, true));
vals.put(Constants.COLUMN_DURATION, duration);
vals.put(Constants.COLUMN_HAS_ARTWORK, Boolean.compare(hasArtwork, true));
long row = getWritableDatabase().insert(Constants.TABLE_NAME, null, vals);
if (AndroidUtils.isDebuggable()) Log.d("MusicCacheDb", "addTrack(): " + row);
}
public void deleteTrack(String trackId) {
getWritableDatabase().delete(Constants.TABLE_NAME, Constants.COLUMN_TRACK_ID + " = ?", new String[]{trackId});
}
public boolean isCachedTrack(String trackId) {
try (var cur = getReadableDatabase().query(
Constants.TABLE_NAME,
new String[]{Constants.COLUMN_TRACK_ID},
Constants.COLUMN_TRACK_ID + " = ?",
new String[]{trackId},
null,
null,
null,
"0,1")) {
return cur.moveToFirst();
}
}
public long getTracksCount() {
return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME);
}
public List<MusicTrack> getAllTracks() {
return getTracksWithCursor(getReadableDatabase().query(Constants.TABLE_NAME, null, null, null, null, null, null));
}
public List<MusicTrack> getAlbum(String albumId) {
return getAlbumTracks(albumId, null);
}
public List<MusicTrack> getFirstAlbumTrack(String albumId) {
return getAlbumTracks(albumId, "0,1");
}
private List<MusicTrack> getAlbumTracks(String albumId, @Nullable String limit) {
return getTracksWithCursor(getReadableDatabase().query(
Constants.TABLE_NAME,
null,
Constants.COLUMN_ALBUM_ID + " = ?",
new String[]{albumId},
null,
null,
null,
limit));
}
public List<MusicTrack> getPlaylist() {
return getTracksWithCursor(getWritableDatabase().query(
Constants.TABLE_NAME,
null,
Constants.COLUMN_ALBUM_ID + " > -1",
null,
Constants.COLUMN_ALBUM_ID,
"count(*) > 1",
Constants.COLUMN_ID + " desc"));
}
private List<MusicTrack> getTracksWithCursor(Cursor cur) {
List<MusicTrack> res = new ArrayList<>();
if (cur != null && cur.moveToFirst()) {
try (cur) {
do {
MusicTrack track = fromCursor(cur);
if (track != null) res.add(track);
} while (cur.moveToNext());
}
}
return res;
}
@Retention(RetentionPolicy.SOURCE)
public @interface Constants {
int DV_VERSION = 0x2;
String DB_NAME = "vt_lite_cache.db";
String TABLE_NAME = "tracks";
//columns
String COLUMN_ID = "id";
String COLUMN_TRACK_ID = "trackId";
String COLUMN_ALBUM_ID = "albumId";
String COLUMN_TITLE = "title";
String COLUMN_SUBTITLE = "subtitle";
String COLUMN_ARTIST = "artist";
String COLUMN_ALBUM_TITLE = "albumTitle";
String COLUMN_EXPLICIT = "explicit";
String COLUMN_DURATION = "duration";
String COLUMN_HAS_ARTWORK = "hasArtwork";
//queries
String CREATE_QUERY = "create table " + TABLE_NAME + "("
+ COLUMN_ID + " INTEGER PRIMARY KEY autoincrement,"
+ COLUMN_TRACK_ID + " text not null,"
+ COLUMN_ALBUM_ID + " text not null,"
+ COLUMN_TITLE + " text not null,"
+ COLUMN_SUBTITLE + " text not null,"
+ COLUMN_ARTIST + " text not null,"
+ COLUMN_ALBUM_TITLE + " text not null,"
+ COLUMN_EXPLICIT + " int not null,"
+ COLUMN_DURATION + " int not null,"
+ COLUMN_HAS_ARTWORK + " int not null"
+ ")";
String DROP_QUERY = "drop table if exists " + TABLE_NAME;
}
}
@@ -1,13 +1,14 @@
package ru.vtosters.lite.music.cache.helpers;
import android.net.Uri;
import com.vk.dto.music.Playlist;
import com.vtosters.lite.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.FileCacheImplementation;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.util.ArrayList;
import java.util.List;
@@ -44,8 +45,8 @@ public class PlaylistHelper {
if (!photo.isEmpty())
jPhoto.put("height", 600)
.put("width", 600)
.putOpt("photo_300", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 300)))
.putOpt("photo_600", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 600)));
.putOpt("photo_300", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 300)).toString())
.putOpt("photo_600", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 600)).toString());
var obj = new JSONObject()
.put("id", -2)
.put("owner_id", albumId)
@@ -68,7 +69,7 @@ public class PlaylistHelper {
public static List<Playlist> getAlbumPlaylists() {
List<Playlist> list = new ArrayList<>();
for (var track : CacheDatabaseDelegate.getTracksAsPlaylist()) {
for (var track : MusicCacheImpl.getPlaylist()) {
var albumLink = track.I;
var thumb = albumLink.u1();
list.add(createAlbum(
@@ -6,12 +6,14 @@ import com.vk.dto.music.MusicTrack;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.FileCacheImplementation;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.File;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TracklistHelper {
@@ -20,22 +22,33 @@ public class TracklistHelper {
}
public static List<MusicTrack> getTracks() {
return CacheDatabaseDelegate.getTracks();
List<MusicTrack> tracks = MusicCacheImpl.getAllTracks();
boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false);
if (!doNotinvertOrder) {
Collections.reverse(tracks);
}
return tracks;
}
public static List<MusicTrack> getTracks(String id) {
return CacheDatabaseDelegate.getTracksByAlbum(id);
return MusicCacheImpl.getAlbumById(id);
}
public static List<MusicTrack> getTracksOne(String id) {
return CacheDatabaseDelegate.getTracksByAlbumOne(id);
return MusicCacheImpl.getFirstAlbumTrack(id);
}
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
var arr = new JSONArray();
for (MusicTrack track : tracks) {
arr.put(track.y1());
}
return arr;
}
@@ -43,7 +56,7 @@ public class TracklistHelper {
var arr = new JSONArray();
for (MusicTrack track : tracks) {
var json = track.J();
var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track));
var folder = MusicCacheStorageUtils.getThumbDirById(LibVKXClient.asId(track));
try {
addCachedThumbnails(json, folder);
} catch (JSONException | MalformedURLException e) {
@@ -58,7 +71,7 @@ public class TracklistHelper {
List<MusicTrack> tracks = new ArrayList<>();
for (MusicTrack track : list) {
var json = track.J();
var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track));
var folder = MusicCacheStorageUtils.getThumbDirById(LibVKXClient.asId(track));
try {
addCachedThumbnails(json, folder);
} catch (JSONException | MalformedURLException e) {
@@ -5,7 +5,7 @@ import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.vtosters.lite.R;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.music.Callback;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.utils.AndroidUtils;
public class MusicCallbackBuilder {
@@ -1,23 +1,11 @@
package ru.vtosters.lite.music.converter.ts;
import android.util.Log;
import com.arthenica.ffmpegkit.FFmpegKit;
import com.arthenica.ffmpegkit.ReturnCode;
import com.vk.core.util.ToastUtils;
import com.vk.dto.music.Artist;
import com.vk.dto.music.MusicTrack;
import com.vtosters.lite.R;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.stream.Collectors;
public class FFMpeg {
private static final int BUFFER_SIZE = 2048;
/**
* @param in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger}
@@ -25,79 +13,47 @@ public class FFMpeg {
* @param track Track to be converted
* @return True if success, else false
*/
public static boolean convert(File in, String out, MusicTrack track) {
public static void convert(File in, String out, MusicTrack track) throws Throwable {
// convert .ts file to .mp3 file via ffmpeg
var sb = new StringBuilder();
sb.append("-y -i \"").append(in.getAbsolutePath()).append("\"");
sb.append(" -map 0 -dn ");
//sb.append(" -loglevel error");
//sb.append(" -hide_banner");
sb.append(" -write_id3v2 1");
String title = null;
if (track.f != null) {
title = track.f;
if (!track.g.isEmpty()) {
title += " (" + track.g + ")";
}
}
if (title != null) {
sb.append(" -metadata title=\"").append(IOUtils.getValidFileName(title)).append("\"");
}
if (track.L != null) {
sb.append(" -metadata artist=\"").append(track.L.stream().map(Artist::w1).collect(Collectors.joining(", "))).append("\"");
} else if (track.C != null) {
sb.append(" -metadata artist=\"").append(track.C).append("\"");
}
if (track.I != null) {
sb.append(" -metadata album=\"").append(track.I.getTitle()).append("\"");
}
sb.append(" -c copy \"").append(out).append("\"");
var command = sb.toString();
var session = FFmpegKit.execute(command);
var rc = session.getReturnCode();
if (ReturnCode.isSuccess(rc)) {
return true;
}
Log.e("FFMpeg", "FFmpegKit failed with return code " + rc);
return false;
var cmd = " -y -i \"" + in.getAbsolutePath() + '"'
+ " -map 0 -dn"
+ " -loglevel error"
+ " -hide_banner"
+ " -write_id3v2 1"
// +" -metadata title=\""+IOUtils.getValidFileName(M3UDownloader.getTitle(track))+'"'
// +" -metadata artist=\""+normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", ")))+'"'
// +" -metadata album=\""+normalizeMetadata(track.I.getTitle())+'"'
+ " -c copy \"" + out + '"';
var ses = FFmpegKit.execute(cmd);
if (ses.getReturnCode().isValueCancel()) throw new RuntimeException(ses.getLogsAsString());
}
private static void copyAssets() {
try {
String[] files = AndroidUtils.getGlobalContext().getAssets().list("ffmpeg");
for (String file : files) {
if (file.endsWith(".so")) {
InputStream in = AndroidUtils.getGlobalContext().getAssets().open("ffmpeg" + File.separator + file);
FileOutputStream out = new FileOutputStream(AndroidUtils.getGlobalContext().getFilesDir() + File.separator + file);
byte[] buffer = new byte[BUFFER_SIZE];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
out.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
private static String normalizeMetadata(String in) {
return in.replace('"', '‟');
}
private static void checkFFMpegLibs() {
var lib = new File(AndroidUtils.getGlobalContext().getFilesDir(), "libffmpegkit_abidetect.so");
if (!lib.exists()) {
ToastUtils.a(R.string.ffmpegkit_copy);
copyAssets();
}
}
// private static void copyAssets()
// throws IOException
// {
// try
// {
// for (var filename:AndroidUtils.getGlobalContext().getAssets().list("ffmpeg"))
// if(filename.endsWith(".so"))
// IOUtils.writeToFile(
// new File(AndroidUtils.getGlobalContext().getFilesDir(),filename),
// IOUtils.readFully(AndroidUtils.getGlobalContext().getAssets().open("ffmpeg"+File.separator+filename)));
// }
// catch(IOException e)
// { throw e; }
// }
//
// private static void checkFFMpegLibs()
// {
// var lib=new File(AndroidUtils.getGlobalContext().getFilesDir(),"libffmpegkit_abidetect.so");
// if(!lib.exists())
// {
// ToastUtils.a(R.string.ffmpegkit_copy);
// copyAssets();
// }
// }
}
@@ -9,66 +9,62 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Comparator;
public class TSMerger {
/**
* Merge multiple .ts files into one .ts file
*
* @param in Directory with .ts files
* @return True if success, else false
*/
public static boolean merge(File in, File out) {
var tsesList = in.listFiles();
public static void merge(File in, File out) {
File[] tsesList = in.listFiles();
if (tsesList == null) {
return false;
return;
}
var mergedByteArrayStream = new ByteArrayOutputStream();
ByteArrayOutputStream mergedByteArrayStream = new ByteArrayOutputStream();
sortTsesList(tsesList);
for (var file : tsesList) {
for (File file : tsesList) {
if (file.getName().endsWith(".ts")) {
try {
mergedByteArrayStream.write(IOUtils.readFully(file));
} catch (IOException e) {
e.printStackTrace();
return false;
return;
}
}
}
return writeOutputFile(out, mergedByteArrayStream);
writeOutputFile(out, mergedByteArrayStream);
}
private static void sortTsesList(File[] tsesList) {
Arrays.sort(tsesList, (ts1, ts2) -> {
String ts1Name = ts1.getName();
String ts2Name = ts2.getName();
int i1 = Integer.parseInt(ts1Name.substring(0, ts1Name.indexOf('.', 1)));
int i2 = Integer.parseInt(ts2Name.substring(0, ts2Name.indexOf('.', 1)));
return Integer.compare(i1, i2);
});
Arrays.sort(tsesList, Comparator.comparingInt(TSMerger::getFileIndex));
}
private static boolean writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) {
private static int getFileIndex(File file) {
String fileName = file.getName();
int dotIndex = fileName.indexOf('.', 1);
return Integer.parseInt(fileName.substring(0, dotIndex));
}
private static void writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) {
byte[] bytes = mergedByteArrayStream.toByteArray();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
Files.write(out.toPath(), mergedByteArrayStream.toByteArray());
} catch (IOException e) {
e.printStackTrace();
return false;
Files.write(out.toPath(), bytes);
} catch (IOException ignored) {
// ignored
}
} else {
try (FileOutputStream fos = new FileOutputStream(out)) {
fos.write(mergedByteArrayStream.toByteArray());
} catch (IOException e) {
e.printStackTrace();
return false;
fos.write(bytes);
} catch (IOException ignored) {
// ignored
}
}
return true;
}
}
@@ -0,0 +1,177 @@
package ru.vtosters.lite.music.downloader;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.exoplayer2.source.hls.playlist.e;
import com.google.android.exoplayer2.source.hls.playlist.f;
import com.google.android.exoplayer2.source.hls.playlist.f.a;
import com.vk.dto.music.MusicTrack;
import java8.util.concurrent.CompletableFuture;
import java8.util.concurrent.CompletionException;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.converter.ts.FFMpeg;
import ru.vtosters.lite.music.converter.ts.TSMerger;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class M3UDownloader
implements ITrackDownloader {
public static M3UDownloader getInstance() {
return Holder.INSTANCE;
}
static void parse(
File outDir,
Callback callback,
MusicTrack track,
boolean cache)
throws IOException {
var parser = new com.google.android.exoplayer2.source.hls.playlist.h(e.a(track.D));
var baseUri = track.D.substring(0, track.D.lastIndexOf("/") + 1);
var segments = ((f) parser.a(Uri.parse(baseUri), IOUtils.openStream(track.D))).o;
var progress = new AtomicInteger();
callback.onProgress(5);
var resultTs = getResultTsFile(track);
var resultMp3 = getResultMp3File(outDir, cache, track);
try {
CompletableFuture.allOf(createFutures(baseUri, segments, progress, callback, track))
.thenRun(() ->
{
if (cache)
try {
ThumbnailDownloader.downloadThumbnails(track);
} catch (IOException e) {
throw new RuntimeException("Failed to download thumbs", e);
}
})
.thenRun(() ->
{
try {
TSMerger.merge(getTsesDir(track), resultTs);
} catch (Throwable e) {
throw new RuntimeException("Failed to merge ts files", e);
}
})
.thenRun(() ->
{
try {
FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track);
} catch (Throwable e) {
throw new RuntimeException("FFmpeg error", e);
}
})
.thenRun(() -> callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / segments.size())))
.thenRun(() -> MusicCacheImpl.addTrack(track))
.whenComplete((unused, tr) -> {
if (tr != null) {
IOUtils.deleteRecursive(MusicCacheStorageUtils.getTrackDirById(track.y1()));
callback.onFailure();
} else callback.onSuccess();
IOUtils.deleteRecursive(getTsesDir(track));
})
.join();
} catch (CompletionException e) {
Log.e("M3UDownloader", "Failed to download track", e.getCause());
}
}
@SuppressWarnings("rawtypes")
static CompletableFuture[] createFutures(
String baseUri,
List<a> segments,
AtomicInteger progress,
Callback callback,
MusicTrack track) {
var futures = new CompletableFuture[segments.size()];
for (var i = 0; i < segments.size(); ++i) {
var segment = segments.get(i);
var filename = i + ".ts";
futures[i] = CompletableFuture.runAsync(() -> processSegment(baseUri, segment, filename, progress, segments.size(), callback, track));
}
return futures;
}
private static void processSegment(
String baseUri,
a segment,
String filename,
AtomicInteger progress,
int size,
Callback callback,
MusicTrack track) {
try {
var buff = IOUtils.readFully(IOUtils.openStream(baseUri + segment.a/*url*/));
if (!TextUtils.isEmpty(segment.g)) {
var cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
var cipherKey = new SecretKeySpec(IOUtils.readFully(IOUtils.openStream(segment.g)), "AES");
var cipherBytes = segment.h.getBytes();
//if IV doesn't pass, it must be created manually and filled with zeros
if (cipherBytes.length != 16) Arrays.fill((cipherBytes = new byte[16]), (byte) 0x0);
var cipherIv = new IvParameterSpec(cipherBytes);
cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIv);
buff = cipher.doFinal(buff);
}
IOUtils.writeToFile(new File(getTsesDir(track), filename), buff);
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / size));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String getTitle(MusicTrack track) {
return track.f + (!TextUtils.isEmpty(track.g) ? +'(' + track.g + ')' : null);
}
static File getTsesDir(MusicTrack track) {
var filesDir = AndroidUtils.getGlobalContext().getFilesDir();
var tsesDir = new File(filesDir, String.valueOf(track.d));
tsesDir.mkdirs();
return tsesDir;
}
static File getResultTsFile(MusicTrack track) {
return new File(getTsesDir(track), "result.ts");
}
static File getResultMp3File(File outDir, boolean cache, MusicTrack track) {
return new File(outDir, IOUtils.getValidFileName((cache ? "track" : track.C + " - " + getTitle(track)) + ".mp3"));
}
@Override
public void downloadTrack(
MusicTrack track,
File outDir,
Callback callback,
boolean cache) {
if (track.D.isEmpty()) {
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
callback.onFailure();
return;
}
try {
parse(outDir, callback, track, cache);
} catch (IOException e) {
callback.onFailure();
Log.e("M3UDownloader", e + "");
throw new RuntimeException(e);
}
}
// Initialization-on-demand
private static class Holder {
private static final M3UDownloader INSTANCE = new M3UDownloader();
}
}
@@ -70,7 +70,7 @@
// }
//
// @Override
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.Callback callback) {
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.interfaces.Callback callback) {
//
// }
//}
@@ -2,7 +2,7 @@ package ru.vtosters.lite.music.downloader;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.music.Callback;
import ru.vtosters.lite.music.interfaces.Callback;
import java.io.File;
import java.util.List;
@@ -10,16 +10,12 @@ import java.util.concurrent.atomic.AtomicInteger;
public class PlaylistDownloader {
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
File outDir = new File(path);
var outDir = new File(path);
if (!outDir.exists())
if (outDir.mkdirs())
Log.v("PlaylistDownloader", "Directory created");
else
Log.e("PlaylistDownloader", "Directory creation failed");
var downloadedTracks = new AtomicInteger(0);
for (MusicTrack musicTrack : playlist) {
if (outDir.mkdirs()) Log.v("PlaylistDownloader", "Directory created");
else Log.e("PlaylistDownloader", "Directory creation failed");
var downloadedTracks = new AtomicInteger();
for (var musicTrack : playlist)
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
@Override
public void onProgress(int progress) {
@@ -27,8 +23,7 @@ public class PlaylistDownloader {
@Override
public void onSuccess() {
var currentProgress = downloadedTracks.incrementAndGet();
callback.onProgress(currentProgress);
callback.onProgress(downloadedTracks.incrementAndGet());
}
@Override
@@ -40,13 +35,11 @@ public class PlaylistDownloader {
public void onSizeReceived(long size, long header) {
}
});
}
}
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback) {
var downloadedTracks = new AtomicInteger(0);
for (MusicTrack musicTrack : playlist) {
var downloadedTracks = new AtomicInteger();
for (var musicTrack : playlist)
TrackDownloader.cacheTrack(musicTrack, new Callback() {
@Override
public void onProgress(int progress) {
@@ -54,8 +47,7 @@ public class PlaylistDownloader {
@Override
public void onSuccess() {
var currentProgress = downloadedTracks.incrementAndGet();
callback.onProgress(currentProgress);
callback.onProgress(downloadedTracks.incrementAndGet());
}
@Override
@@ -67,6 +59,5 @@ public class PlaylistDownloader {
public void onSizeReceived(long size, long header) {
}
});
}
}
}
@@ -2,45 +2,42 @@ package ru.vtosters.lite.music.downloader;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.music.cache.FileCacheImplementation;
import org.json.JSONArray;
import org.json.JSONObject;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.IOException;
import java.net.URL;
public class ThumbnailDownloader {
public static void downloadThumbnails(MusicTrack track)
throws IOException {
var obj = track.J();
var trackId = LibVKXClient.asId(track);
if (!obj.has("album")) return;
var album = obj.optJSONObject("album");
if (!album.has("thumb")) return;
var thumb = album.optJSONObject("thumb");
if (thumb.has("sizes")) {
var sizes = thumb.optJSONArray("sizes");
for (int i = 0; i < sizes.length(); i++) {
var size = sizes.optJSONObject(i);
var width = size.optInt("width");
var src = size.optString("src");
downloadThumbnail(src, width, trackId);
public static void downloadThumbnails(MusicTrack track) throws IOException {
JSONObject json = track.J();
JSONObject album = json.optJSONObject("album");
if (album == null) return;
JSONObject thumb = album.optJSONObject("thumb");
if (thumb == null) return;
JSONArray sizes = thumb.optJSONArray("sizes");
String trackId = LibVKXClient.asId(track);
if (sizes == null) {
JSONArray names = thumb.names();
if (names == null) return;
for (int i = 0; i < names.length(); ++i) {
String name = names.optString(i);
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
}
} else {
var names = thumb.names();
for (int i = 0; i < names.length(); i++) {
var name = names.optString(i);
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
for (int i = 0; i < sizes.length(); ++i) {
JSONObject size = sizes.optJSONObject(i);
int width = size.optInt("width");
String src = size.optString("src");
downloadThumbnail(src, width, trackId);
}
}
}
private static void downloadThumbnail(String url, int res, String trackId)
throws IOException {
var data = IOUtils.readFully(new URL(url).openStream());
IOUtils.writeToFile(FileCacheImplementation.getTrackThumbnail(trackId, res), data);
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
}
}
@@ -4,11 +4,10 @@ import android.util.Log;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.music.Callback;
import ru.vtosters.lite.music.M3UDownloader;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.FileCacheImplementation;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.File;
@@ -24,7 +23,7 @@ public class TrackDownloader {
}
public static void cacheTrack(MusicTrack track, Callback callback) {
if (CacheDatabaseDelegate.isCached(track.y1())) {
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
callback.onSuccess();
return;
} else if (track.D.isEmpty()) {
@@ -33,7 +32,7 @@ public class TrackDownloader {
return;
}
var path = FileCacheImplementation.getTrackFolder(LibVKXClient.asId(track)).getAbsolutePath();
var path = MusicCacheStorageUtils.getTrackDirById(LibVKXClient.asId(track)).getAbsolutePath();
download(track, path, callback, M3UDownloader.getInstance(), true);
}
@@ -1,4 +1,4 @@
package ru.vtosters.lite.music;
package ru.vtosters.lite.music.interfaces;
public interface Callback {
void onProgress(int progress);
@@ -1,7 +1,6 @@
package ru.vtosters.lite.music.interfaces;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.music.Callback;
import java.io.File;
@@ -11,15 +11,15 @@ public class ImBanner {
private static final String TAG = "ImBanner";
public static JSONObject convertToBanner(JSONObject original) {
final String PEER = "peer";
final String ID = "id";
final String PICTURE = "picture";
final String TEXT = "text";
final String LINK = "link";
final String LINK_TEXT = "link_text";
final String NAME = "name";
final String BUTTONS = "buttons";
final String ICON = "icon";
String PEER = "peer";
String ID = "id";
String PICTURE = "picture";
String TEXT = "text";
String LINK = "link";
String LINK_TEXT = "link_text";
String NAME = "name";
String BUTTONS = "buttons";
String ICON = "icon";
Optional<JSONObject> user = Optional.ofNullable(original.optJSONObject(PEER));
int userId = user.map(u -> u.optInt(ID)).orElse(0);
@@ -26,20 +26,20 @@ public class ProfileButtons {
newItem = orig.getJSONArray("buttons");
}
if (!VTVerifications.haveDonateButton() && id == AccountManagerUtils.getUserId()) {
var map = new HashMap<>();
map.put("20", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/20.png");
map.put("40", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/40.png");
map.put("80", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/80.png");
newItem.put(generateJsonObject(
AndroidUtils.getString(com.vtosters.lite.R.string.donate_to_vtl),
"https://vk.com/vtosters_official",
"2D81E0",
"open_url",
map
));
}
// if (!VTVerifications.haveDonateButton() && id == AccountManagerUtils.getUserId()) {
// var map = new HashMap<>();
// map.put("20", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/20.png");
// map.put("40", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/40.png");
// map.put("80", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/80.png");
//
// newItem.put(generateJsonObject(
// AndroidUtils.getString(com.vtosters.lite.R.string.donate_to_vtl),
// "https://vk.com/vtosters_official",
// "2D81E0",
// "open_url",
// map
// ));
// }
if (!orig.has("type") && AccountManagerUtils.isVKTester()) {
var map = new HashMap<>();
@@ -17,6 +17,8 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
public class UsersList {
private static final String TAG = "UsersList";
private static final List<Integer> descriptionsList = new ArrayList<>();
@@ -25,7 +27,7 @@ public class UsersList {
public static void getUsersList() {
var prefs = AndroidUtils.getGlobalContext().getSharedPreferences("vt_another_data", 0);
if ((!NetworkUtils.isNetworkConnected() || NetworkUtils.isInternetSlow()) && prefs.contains("ids")) {
if ((!NetworkUtils.isNetworkConnected() || NetworkUtils.isInternetSlow()) && prefs.contains("ids") || getBoolValue("serverFeaturesDisable", false)) {
return;
}
@@ -166,26 +166,25 @@ public class ThemesCore {
}
public static boolean hasThemedAttr(Context context, int attrID) {
if (ThemesUtils.isMonetTheme()) {
if (Preferences.getBoolValue("logColors", false)) {
try {
Log.d(TAG, "Requesting color by attr " + context.getResources().getResourceName(attrID));
} catch (Exception ignored) {
}
if (Preferences.getBoolValue("logColors", false)) {
try {
Log.d(TAG, "Requesting color by attr " + context.getResources().getResourceName(attrID));
} catch (Exception ignored) {
}
}
if (ThemesUtils.isMonetTheme() || ThemesManager.canApplyCustomAccent()) {
if (isCachedAccents()) {
if (ThemesUtils.isDarkTheme()) {
return (themedColors.get(attrID) != 0 && ACCENT_THEME_ONLY_DARK.get(attrID, true) && (
ThemesUtils.isMilkshake() ?
ACCENT_THEME_ONLY_MILK_DARK.get(attrID, true) :
ACCENT_THEME_ONLY_NOMILK_DARK.get(attrID, true)));
} else {
return (themedColors.get(attrID) != 0 && ACCENT_THEME_ONLY_LIGHT.get(attrID, true) && (
ThemesUtils.isMilkshake() ?
ACCENT_THEME_ONLY_MILK_LIGHT.get(attrID, true) :
ACCENT_THEME_ONLY_NOMILK_LIGHT.get(attrID, true)));
}
boolean isDark = ThemesUtils.isDarkTheme();
boolean isMilkshake = ThemesUtils.isMilkshake();
SparseBooleanArray themeOnlyMap = isDark ? ACCENT_THEME_ONLY_DARK : ACCENT_THEME_ONLY_LIGHT;
SparseBooleanArray milkshakeMap = isDark ? ACCENT_THEME_ONLY_MILK_DARK : ACCENT_THEME_ONLY_MILK_LIGHT;
SparseBooleanArray noMilkshakeMap = isDark ? ACCENT_THEME_ONLY_NOMILK_DARK : ACCENT_THEME_ONLY_NOMILK_LIGHT;
return (themedColors.get(attrID) != 0 && themeOnlyMap.get(attrID, true) && (
isMilkshake ?
milkshakeMap.get(attrID, true) :
noMilkshakeMap.get(attrID, true)));
} else {
return themedColors.get(attrID) != 0;
}
@@ -194,7 +193,6 @@ public class ThemesCore {
}
}
public static void clear() {
cachedAccents = false;
themedColors.clear();
@@ -23,7 +23,7 @@ public class ThemesHacks {
}
public static int getHackedColor(@NonNull Context context, @ColorRes int color) {
if (ThemesUtils.isMonetTheme()) {
if (ThemesUtils.isMonetTheme() || ThemesManager.canApplyCustomAccent()) {
if (Preferences.getBoolValue("logColors", false)) {
try {
Log.d("ThemesCore", "Requesting color by color " + context.getResources().getResourceName(color));
@@ -3,7 +3,6 @@ package ru.vtosters.lite.themes;
import android.app.Application;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
@@ -20,7 +19,7 @@ import java.util.zip.ZipOutputStream;
public class ThemesManager {
private final static int[] ACCENT_COLORS = {
private static final int[] ACCENT_COLORS = {
R.color.vk_sky_300,
R.color.sky_300,
R.color.viewer_retry_button_text_color,
@@ -96,7 +95,7 @@ public class ThemesManager {
R.color.header_blue_opacity40
};
// modify it if "doNotCompress" in apktool.yml has been edited
private final static String[] DO_NOT_COMPRESS = {
private static final String[] DO_NOT_COMPRESS = {
".png",
".jpg",
".mp3",
@@ -124,14 +123,13 @@ public class ThemesManager {
ResourcesLoader.load(app, modApk.getAbsolutePath(), false);
}
} catch (Exception e) {
Log.e("ThemesManager", e.getMessage());
e.printStackTrace();
}
}
// validate modded apk before loading
private static void validateModApk()
throws IOException {
final var apk = new ZipFile(modApk);
private static void validateModApk() throws IOException {
var apk = new ZipFile(modApk);
apk.close();
validated = true;
@@ -153,23 +151,18 @@ public class ThemesManager {
return validated && modApk.exists() && ThemesUtils.getReservedAccent() != Color.TRANSPARENT;
}
public static void generateModApk(int accentColor)
throws Throwable {
try (
final var apk = new ZipFile(baseApkPath)
) {
final var arscEntry = apk.getEntry("resources.arsc");
final var arscBis = new BufferedInputStream(apk.getInputStream(arscEntry));
final var arsc = BinaryResourceFile.fromInputStream(arscBis);
public static void generateModApk(int accentColor) throws Throwable {
try (var apk = new ZipFile(baseApkPath)) {
var arscEntry = apk.getEntry("resources.arsc");
var arscBis = new BufferedInputStream(apk.getInputStream(arscEntry));
var arsc = BinaryResourceFile.fromInputStream(arscBis);
ArscEditor.changeColors(arsc, ACCENT_COLORS, accentColor);
try (
final var zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(modApk)))
) {
final var arscBuff = arsc.toByteArray();
final var newArscEntry = new ZipEntry("resources.arsc");
try (var zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(modApk)))) {
var arscBuff = arsc.toByteArray();
var newArscEntry = new ZipEntry("resources.arsc");
final var crc = new CRC32();
var crc = new CRC32();
crc.update(arscBuff);
newArscEntry.setSize(arscBuff.length);
@@ -180,10 +173,10 @@ public class ThemesManager {
zos.write(arscBuff);
zos.closeEntry();
final var entries = apk.entries();
var entries = apk.entries();
while (entries.hasMoreElements()) {
final var entry = entries.nextElement();
final var name = entry.getName();
var entry = entries.nextElement();
var name = entry.getName();
if (!name.startsWith("res/") && !name.startsWith("assets/") || name.equals("resources.arsc"))
continue;
@@ -196,8 +189,8 @@ public class ThemesManager {
}
}
final var entryBis = new BufferedInputStream(apk.getInputStream(entry));
final var newEntry = new ZipEntry(name);
var entryBis = new BufferedInputStream(apk.getInputStream(entry));
var newEntry = new ZipEntry(name);
if (doNotCompress) {
newEntry.setMethod(ZipEntry.STORED);
@@ -1,59 +1,42 @@
package ru.vtosters.lite.themes.loaders;
import android.annotation.SuppressLint;
import android.content.res.AssetManager;
import ru.vtosters.lite.utils.ReflectionUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressLint("PrivateApi")
public class AssetManagerHelper {
// methods
private final static Method getAssetPathMtd;
private final static Method getApkAssetsMtd;
private final static Method addAssetPathMtd;
private final static Method addAssetPathAsSharedLibraryMtd;
private final static Method addOverlayPathMtd;
static {
try {
getAssetPathMtd = ReflectionUtils.findMethod(Class.forName("android.content.res.ApkAssets"), "getAssetPath");
getApkAssetsMtd = ReflectionUtils.findMethod(AssetManager.class, "getApkAssets");
addAssetPathMtd = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPathAsSharedLibraryMtd = AssetManager.class.getMethod("addAssetPathAsSharedLibrary", String.class);
addOverlayPathMtd = AssetManager.class.getMethod("addOverlayPath", String.class);
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new RuntimeException();
}
}
public static String getApkPath(Object assetManager, String packagename)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Object[] apkAssetsArr = (Object[]) getAssetPathMtd.invoke(assetManager);
public static String getApkPath(Object assetManager, String packagename) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException {
Object[] apkAssetsArr = (Object[]) ReflectionUtils.findMethod(Class.forName("android.content.res.ApkAssets"), "getAssetPath").invoke(assetManager);
// By default, base apk is the last element of array if is not, for execution speed, we should iterate array from the end to the beginning
for (int i = apkAssetsArr.length - 1; i >= 0; i--) {
final String pathname = (String) getApkAssetsMtd.invoke(apkAssetsArr[i]);
if (pathname.contains(packagename)) return pathname;
String pathname = (String) ReflectionUtils.findMethod(AssetManager.class, "getApkAssets").invoke(apkAssetsArr[i]);
if (pathname != null && pathname.contains(packagename)) return pathname;
}
throw new NullPointerException();
}
//region Uses for adding own assets or getting existing cookies
public static int addAssetPath(AssetManager manager, String path)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
return (int) addAssetPathMtd.invoke(manager, path);
public static void addAssetPath(AssetManager manager, String path) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
manager.getClass().getMethod("addAssetPath", String.class).invoke(manager, path);
}
public static int addAssetPathAsSharedLibrary(AssetManager manager, String path)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
return (int) addAssetPathAsSharedLibraryMtd.invoke(manager, path);
public static int addAssetPathAsSharedLibrary(AssetManager manager, String path) {
try {
return (int) AssetManager.class.getMethod("addAssetPathAsSharedLibrary", String.class).invoke(manager, path);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public static int addOverlayPath(AssetManager manager, String path)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
return (int) addOverlayPathMtd.invoke(manager, path);
public static int addOverlayPath(AssetManager manager, String path) {
try {
return (int) AssetManager.class.getMethod("addOverlayPath", String.class).invoke(manager, path);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
//endregion
}
@@ -1,5 +1,6 @@
package ru.vtosters.lite.themes.loaders;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.AssetManager;
@@ -14,16 +15,17 @@ import ru.vtosters.lite.utils.ReflectionUtils;
import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@SuppressLint("PrivateApi")
public class ResourcesLoader {
private static Collection<WeakReference<Resources>> sResourceReferences = null;
private static Object sCurrentActivityThread = null;
private static AssetManager sAssetManager = null;
// methods
private static Method ensureStringBlocksMtd = null;
@@ -42,7 +44,7 @@ public class ResourcesLoader {
static {
try {
// API version 8 has PackageInfo, 10 has LoadedApk. Needs to check 9
final Class<?> activityThreadClz = Class.forName("android.app.ActivityThread");
Class<?> activityThreadClz = Class.forName("android.app.ActivityThread");
Class<?> loadedApkClz;
try {
loadedApkClz = Class.forName("android.app.LoadedApk");
@@ -87,43 +89,40 @@ public class ResourcesLoader {
}
}
public static void init(Context context)
throws Exception {
public static void init(Context context) throws Exception {
sCurrentActivityThread = ReflectionUtils.getActivityThread(context, null);
//pre-N
// Find the singleton instance of ResourcesManager
final Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");
final Method mGetInstance = ReflectionUtils.findMethod(resourcesManagerClass, "getInstance");
final Object resourcesManager = mGetInstance.invoke(null);
Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");
Method mGetInstance = ReflectionUtils.findMethod(resourcesManagerClass, "getInstance");
Object resourcesManager = mGetInstance.invoke(null);
try {
Field fMActiveResources = ReflectionUtils.findField(resourcesManagerClass, "mActiveResources");
final ArrayMap<?, WeakReference<Resources>> activeResources19 =
ArrayMap<?, WeakReference<Resources>> activeResources19 =
(ArrayMap<?, WeakReference<Resources>>) fMActiveResources.get(resourcesManager);
sResourceReferences = activeResources19.values();
} catch (NoSuchFieldException ignore) {
// N moved the resources to mResourceReferences
final Field mResourceReferences = ReflectionUtils.findField(resourcesManagerClass, "mResourceReferences");
Field mResourceReferences = ReflectionUtils.findField(resourcesManagerClass, "mResourceReferences");
sResourceReferences = (Collection<WeakReference<Resources>>) mResourceReferences.get(resourcesManager);
}
}
public static void load(Context context, String resPath, boolean reinject)
throws Exception {
final ApplicationInfo appInfo = context.getApplicationInfo();
public static void load(Context context, String resPath, boolean reinject) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException, NoSuchMethodException {
ApplicationInfo appInfo = context.getApplicationInfo();
final Field[] packagesFlds = Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1
Field[] packagesFlds = Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1
? new Field[]{packagesFld, resourcePackagesFld}
: new Field[]{packagesFld};
for (var fld : packagesFlds) {
final var value = fld.get(sCurrentActivityThread);
var value = fld.get(sCurrentActivityThread);
for (var entry
: ((Map<String, WeakReference<?>>) value).entrySet()) {
final var loadedApk = entry.getValue().get();
for (var entry : ((Map<String, WeakReference<?>>) value).entrySet()) {
var loadedApk = entry.getValue().get();
if (loadedApk == null) continue;
final var resDirPath = (String) resDirFld.get(loadedApk);
var resDirPath = (String) resDirFld.get(loadedApk);
if (appInfo.sourceDir.equals(resDirPath))
resDirFld.set(loadedApk, resPath);
}
@@ -134,10 +133,10 @@ public class ResourcesLoader {
return;
}
sAssetManager = AssetManager.class.newInstance();
// Create a new AssetManager instance and point it to the resources installed under
if (AssetManagerHelper.addAssetPath(sAssetManager, resPath) == 0)
throw new IllegalStateException("Could not create new AssetManager");
AssetManager sAssetManager = AssetManager.class.newInstance();
AssetManagerHelper.addAssetPath(sAssetManager, resPath);
// Add SharedLibraries to AssetManager for resolve system resources not found issue
// This influence SharedLibrary Package ID
@@ -157,7 +156,7 @@ public class ResourcesLoader {
ensureStringBlocksMtd.invoke(sAssetManager);
}
for (var wr : sResourceReferences) {
final var resources = wr.get();
var resources = wr.get();
if (resources == null) continue;
// Set the AssetManager of the Resources instance to our brand new one
@@ -166,9 +165,9 @@ public class ResourcesLoader {
assetsFld.set(resources, sAssetManager);
} catch (Throwable ignore) {
// N
final var resourceImpl = resourcesImplFld.get(resources);
var resourceImpl = resourcesImplFld.get(resources);
// for Huawei HwResourcesImpl
final Field implAssets = ReflectionUtils.findField(resourceImpl, "mAssets");
Field implAssets = ReflectionUtils.findField(resourceImpl, "mAssets");
implAssets.set(resourceImpl, sAssetManager);
}
@@ -196,13 +195,13 @@ public class ResourcesLoader {
private static void installResourceInsuranceHacks(Context context, String patchedResApkPath) {
try {
final Object activityThread = ReflectionUtils.getActivityThread(context, null);
final Field mHField = ReflectionUtils.findField(activityThread, "mH");
final Handler mH = (Handler) mHField.get(activityThread);
final Field mCallbackField = ReflectionUtils.findField(Handler.class, "mCallback");
final Handler.Callback originCallback = (Handler.Callback) mCallbackField.get(mH);
Object activityThread = ReflectionUtils.getActivityThread(context, null);
Field mHField = ReflectionUtils.findField(activityThread, "mH");
Handler mH = (Handler) mHField.get(activityThread);
Field mCallbackField = ReflectionUtils.findField(Handler.class, "mCallback");
Handler.Callback originCallback = (Handler.Callback) mCallbackField.get(mH);
if (!(originCallback instanceof ResourceInsuranceHandlerCallback)) {
final ResourceInsuranceHandlerCallback hackCallback = new ResourceInsuranceHandlerCallback(
ResourceInsuranceHandlerCallback hackCallback = new ResourceInsuranceHandlerCallback(
context, patchedResApkPath, originCallback, mH.getClass());
mCallbackField.set(mH, hackCallback);
} else {
@@ -250,9 +249,9 @@ public class ResourcesLoader {
Log.w("ResourcesLoader", "try to clear typedArray cache!");
// Clear typedArray cache.
try {
final Field typedArrayPoolField = ReflectionUtils.findField(Resources.class, "mTypedArrayPool");
final Object origTypedArrayPool = typedArrayPoolField.get(resources);
final Method acquireMethod = ReflectionUtils.findMethod(origTypedArrayPool, "acquire");
Field typedArrayPoolField = ReflectionUtils.findField(Resources.class, "mTypedArrayPool");
Object origTypedArrayPool = typedArrayPoolField.get(resources);
Method acquireMethod = ReflectionUtils.findMethod(origTypedArrayPool, "acquire");
while (true) {
if (acquireMethod.invoke(origTypedArrayPool) == null) {
break;
@@ -268,9 +267,8 @@ public class ResourcesLoader {
applicationInfo.sharedLibraryFiles != null;
}
private static final class ResourceInsuranceHandlerCallback implements Handler.Callback {
private static class ResourceInsuranceHandlerCallback implements Handler.Callback {
private static final String LAUNCH_ACTIVITY_LIFECYCLE_ITEM_CLASSNAME = "android.app.servertransaction.LaunchActivityItem";
private final Context mContext;
private final String mPatchResApkPath;
private final Handler.Callback mOriginalCallback;
@@ -319,48 +317,56 @@ public class ResourcesLoader {
}
@SuppressWarnings("unchecked")
private boolean hackMessage(Message msg) {
boolean shouldReInjectPatchedResources = false;
// A method to check if the message requires re-injecting patched resources
private boolean shouldReInjectPatchedResources(Message msg) {
if (!isPatchedResModifiedAfterLastLoad(mPatchResApkPath)) {
shouldReInjectPatchedResources = false;
} else {
if (msg.what == LAUNCH_ACTIVITY || msg.what == RELAUNCH_ACTIVITY) {
shouldReInjectPatchedResources = true;
} else if (msg.what == EXECUTE_TRANSACTION) {
do {
if (mSkipInterceptExecuteTransaction) {
break;
}
final Object transaction = msg.obj;
if (transaction == null) {
Log.w("ResourcesLoader", "transaction is null, skip rest insurance logic.");
break;
}
if (mGetCallbacksMethod == null) {
try {
mGetCallbacksMethod = ReflectionUtils.findMethod(transaction, "getCallbacks");
} catch (Throwable ignored) {
// Ignored.
}
}
if (mGetCallbacksMethod == null) {
Log.e("ResourcesLoader", "fail to find getLifecycleStateRequest method, skip rest insurance logic.");
mSkipInterceptExecuteTransaction = true;
break;
}
try {
final List<Object> req = (List<Object>) mGetCallbacksMethod.invoke(transaction);
if (req != null && req.size() > 0) {
final Object cb = req.get(0);
shouldReInjectPatchedResources = cb != null && cb.getClass().getName().equals(LAUNCH_ACTIVITY_LIFECYCLE_ITEM_CLASSNAME);
}
} catch (Throwable ignored) {
Log.e("ResourcesLoader", "fail to call getLifecycleStateRequest method, skip rest insurance logic.");
}
} while (false);
return false;
}
if (msg.what == LAUNCH_ACTIVITY || msg.what == RELAUNCH_ACTIVITY) {
return true;
}
// If the message is executing a transaction, check the callbacks
if (msg.what == EXECUTE_TRANSACTION) {
// Skip the logic if the method is not found or an error occurs
if (mSkipInterceptExecuteTransaction) {
return false;
}
// Get the transaction object from the message
Object transaction = msg.obj;
if (transaction == null) {
Log.w("ResourcesLoader", "transaction is null, skip rest insurance logic.");
return false;
}
// Get the getCallbacks method from the transaction class
if (mGetCallbacksMethod == null) {
try {
mGetCallbacksMethod = ReflectionUtils.findMethod(transaction, "getCallbacks");
} catch (Throwable ignored) {
// Ignored.
}
}
if (mGetCallbacksMethod == null) {
Log.e("ResourcesLoader", "fail to find getCallbacks method, skip rest insurance logic.");
mSkipInterceptExecuteTransaction = true;
return false;
}
try {
List<Object> callbacks = (List<Object>) mGetCallbacksMethod.invoke(transaction);
if (callbacks != null && !callbacks.isEmpty()) {
Object cb = callbacks.get(0);
return cb != null && cb.getClass().getName().equals(LAUNCH_ACTIVITY_LIFECYCLE_ITEM_CLASSNAME);
}
} catch (Throwable ignored) {
Log.e("ResourcesLoader", "fail to call getCallbacks method, skip rest insurance logic.");
}
}
if (shouldReInjectPatchedResources) {
return false;
}
// A method to hack the message and re-inject patched resources if needed
@SuppressWarnings("unchecked")
private boolean hackMessage(Message msg) {
if (shouldReInjectPatchedResources(msg)) {
try {
load(mContext, mPatchResApkPath, true);
} catch (Throwable thr) {
@@ -23,8 +23,8 @@ import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class PalettesManager {
private final static File PALETTES_DIR = new File(Environment.getExternalStorageDirectory(), "VTL/palettes");
private final static String ASSETS_DIR = "vtl_themes/palettes/";
private static final File PALETTES_DIR = new File(Environment.getExternalStorageDirectory(), "VTL/palettes");
private static final String ASSETS_DIR = "vtl_themes/palettes/";
private static PalettesManager sInstance;
@@ -51,17 +51,17 @@ public class PalettesManager {
return;
}
final var palettes = PALETTES_DIR.listFiles((dir, name) -> name.endsWith(".json"));
var palettes = PALETTES_DIR.listFiles((dir, name) -> name.endsWith(".json"));
if (palettes == null || palettes.length == 0)
copyPaletteFromAssets();
parsePalettes();
}
private boolean copyPaletteFromAssets() {
private void copyPaletteFromAssets() {
try {
final var manager = AndroidUtils.getGlobalContext().getAssets();
final var assets = manager.list(ASSETS_DIR);
if (assets == null || assets.length == 0) return false;
var manager = AndroidUtils.getGlobalContext().getAssets();
var assets = manager.list(ASSETS_DIR);
if (assets == null || assets.length == 0) return;
for (var asset : assets) {
try {
IOUtils.copy(manager.open(ASSETS_DIR + asset), new File(PALETTES_DIR, asset));
@@ -69,11 +69,9 @@ public class PalettesManager {
e.printStackTrace();
}
}
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
private void parsePalettes() {
@@ -8,15 +8,15 @@ public class ArscEditor {
public static void changeColors(BinaryResourceFile arsc, int[] colorIds, int color) {
for (var chunk : arsc.getChunks()) {
final var resTableChunk = (ResourceTableChunk) chunk;
var resTableChunk = (ResourceTableChunk) chunk;
for (var packageChunk : resTableChunk.getPackages()) {
for (var typeChunk : packageChunk.getTypeChunks("color")) {
for (var entry : typeChunk.getEntries().entrySet()) {
final var value = entry.getValue();
var value = entry.getValue();
if (value.isComplex()) continue;
final var binValue = value.value();
var binValue = value.value();
if (!isColorResourceValue(binValue)) continue;
final var resId = packageChunk.getId() << 24 | typeChunk.getId() << 16 | entry.getKey();
var resId = packageChunk.getId() << 24 | typeChunk.getId() << 16 | entry.getKey();
for (var colodId : colorIds) {
if (resId == colodId) {
binValue.data = color;
@@ -11,6 +11,7 @@ import android.widget.TextView;
import com.vk.core.drawable.RecoloredDrawable;
import ru.vtosters.hooks.other.ThemesUtils;
import ru.vtosters.lite.themes.ColorReferences;
import ru.vtosters.lite.themes.ThemesManager;
import ru.vtosters.lite.utils.AndroidUtils;
public class RecolorUtils {
@@ -43,7 +44,7 @@ public class RecolorUtils {
}
public static int recolorHexColor(int i) {
if (!ThemesUtils.isMonetTheme()) return i;
if (!ThemesUtils.isMonetTheme() || !ThemesManager.canApplyCustomAccent()) return i;
var accented = ColorReferences.isAccentedColor(i);
var mutedaccented = ColorReferences.isMutedAccentedColor(i);
return (accented || mutedaccented) ? (accented ? ThemesUtils.getAccentColor() : ThemesUtils.getMutedAccentColor()) : i;
@@ -301,7 +301,7 @@ public class PreferenceFragmentUtils {
return addPreference(screen, key, title, null, null, preference -> {
LinearLayout linearLayout = new LinearLayout(screen.getContext());
final EditText editText = new EditText(screen.getContext());
EditText editText = new EditText(screen.getContext());
editText.setText(PreferenceManager.getDefaultSharedPreferences(screen.getContext()).getString(key, ""));
editText.setHint(title);
editText.setTextColor(ThemesUtils.getTextAttr());
@@ -36,7 +36,7 @@ public class CategorizedAdapter<T> extends RecyclerView.Adapter<CategorizedAdapt
private final List<T> mSelectedItems;
private final List<T> mDisabledItems;
private final List<T> mUnmovedItems;
final private List<T> mExceptions = new ArrayList<>();
private final List<T> mExceptions = new ArrayList<>();
private final IViewHolderBinder mBinder;
// Min and max counts
@@ -50,7 +50,8 @@ public class CategorizedAdapter<T> extends RecyclerView.Adapter<CategorizedAdapt
this.mBinder = binder;
}
public void setExceptions(T... exceptions) {
@SafeVarargs
public final void setExceptions(T... exceptions) {
mExceptions.clear();
mExceptions.addAll(Arrays.asList(exceptions));
}
@@ -186,11 +187,16 @@ public class CategorizedAdapter<T> extends RecyclerView.Adapter<CategorizedAdapt
&& mSelectedItems.size() == mMinSelectedItems))
return false;
if (getItemViewType(toPosition) == CATEGORY_TITLE) {
if (mExceptions.size() > 0) {
if (!mExceptions.isEmpty()) {
boolean except = false;
int q = 0;
for (var ex : mExceptions)
if (mSelectedItems.contains(ex)) ++q;
if (q == 1) return false;
for (var ex : mExceptions) {
var i = mSelectedItems.indexOf(ex);
if (i == -1) continue;
if (i + 1 == fromPosition) except = true;
++q;
}
if (except && q == 1) return false;
}
mDisabledItems.add(0, mSelectedItems.remove(fromPosition - 1));
} else Collections.swap(mSelectedItems, fromPosition - 1, toPosition - 1);
@@ -218,7 +224,6 @@ public class CategorizedAdapter<T> extends RecyclerView.Adapter<CategorizedAdapt
}
public static class CategorizedViewHolder extends RecyclerView.ViewHolder {
private final LinearLayout mMovingItemContainer;
private final ImageView mIcon;
private final TextView mName;
@@ -18,7 +18,6 @@ import ru.vtosters.hooks.other.ThemesUtils;
import ru.vtosters.lite.utils.AndroidUtils;
public class MessagesPreviewAdapter extends RecyclerView.Adapter<MessagesPreviewAdapter.MessagePreviewViewHolder> {
private final String[] mMessages;
public MessagesPreviewAdapter(String... messages) {
@@ -35,9 +34,9 @@ public class MessagesPreviewAdapter extends RecyclerView.Adapter<MessagesPreview
public MessagePreviewViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
Context context = viewGroup.getContext();
final var inflater = LayoutInflater.from(context);
var inflater = LayoutInflater.from(context);
final var bubbleFluidLayout = (BubbleFluidLayout) inflater.inflate(R.layout.vkim_msg_list_item_msg_from_user, viewGroup, false);
var bubbleFluidLayout = (BubbleFluidLayout) inflater.inflate(R.layout.vkim_msg_list_item_msg_from_user, viewGroup, false);
bubbleFluidLayout.setPadding(0, AndroidUtils.dp2px(4), 0, 0);
bubbleFluidLayout.setGravity(viewType);
@@ -47,7 +46,7 @@ public class MessagesPreviewAdapter extends RecyclerView.Adapter<MessagesPreview
bubbleFluidLayout.findViewById(R.id.status).setVisibility(View.GONE);
bubbleFluidLayout.findViewById(R.id.status_space).setVisibility(View.GONE);
final var messageBubbleView = (MsgBubbleView) bubbleFluidLayout.findViewById(R.id.bubble);
var messageBubbleView = (MsgBubbleView) bubbleFluidLayout.findViewById(R.id.bubble);
messageBubbleView.setContentPaddingLeft(AndroidUtils.dp2px(10));
messageBubbleView.setContentPaddingTop(AndroidUtils.dp2px(4));
messageBubbleView.setContentPaddingRight(AndroidUtils.dp2px(4));
@@ -59,10 +58,10 @@ public class MessagesPreviewAdapter extends RecyclerView.Adapter<MessagesPreview
}
bubbleFluidLayout.setLayoutDirection(viewType);
final var msgTextView = inflater.inflate(R.layout.vkim_msg_part_text, viewGroup, false);
var msgTextView = inflater.inflate(R.layout.vkim_msg_part_text, viewGroup, false);
msgTextView.setId(R.id.msg_text);
final var msgBubbleView = (MsgBubbleView) bubbleFluidLayout.findViewById(R.id.bubble);
var msgBubbleView = (MsgBubbleView) bubbleFluidLayout.findViewById(R.id.bubble);
msgBubbleView.setContentView(msgTextView);
return new MessagePreviewViewHolder(bubbleFluidLayout);
@@ -1,6 +1,7 @@
package ru.vtosters.lite.ui.adapters;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,6 +29,7 @@ import ru.vtosters.hooks.SwitchHook;
import ru.vtosters.lite.tgs.TGPref;
import ru.vtosters.lite.tgs.TGRoot;
import ru.vtosters.lite.ui.components.IItemMovingListener;
import ru.vtosters.lite.ui.fragments.VTSettings;
import java.io.File;
import java.util.ArrayList;
@@ -138,7 +140,11 @@ public class StickerPackAdapter extends RecyclerView.Adapter<StickerPackAdapter.
});
mName.setText(pack.title);
mSwitch.setChecked(pack.enabled);
mSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> sService.setPackEnabled(pack, isChecked, true));
mSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
sService.setPackEnabled(pack, isChecked, false);
buttonView.getContext().sendBroadcast(new Intent(VTSettings.ACTION_INVALIDATE_TGS_COUNT));
});
mStickersCount.setText(pack.stickersCount + " " + mStickersCount.getContext().getString(R.string.stickerscount));
SwitchHook.setSwitchCompatColors(mSwitch, mSwitch.getContext());
@@ -39,11 +39,11 @@ public class IconManager {
);
public static List<String> icons() {
return Preferences.hasVerification() && Preferences.isValidSignature() ? sIconsPlusNames : sIconsNames; // delete later sdk check
return sIconsPlusNames; // delete later sdk check
}
public static List<String> iconsValues() {
return Preferences.hasVerification() && Preferences.isValidSignature() ? sIconsPlus : sIcons; // delete later sdk check
return sIconsPlus; // delete later sdk check
}
public static void switchComponent(String icon, String appName) {
@@ -55,7 +55,7 @@ public class IconManager {
Log.d("IconManager", "iconSwitcher: icon = " + icon + ", appname = " + appName);
List<String> iconsList = (Preferences.hasVerification() && Preferences.isValidSignature()) ? sIconsPlus : sIcons;
List<String> iconsList = sIconsPlus;
List<String> labelsList = sLabels;
// check if icon is exist
@@ -1,39 +0,0 @@
package ru.vtosters.lite.ui.dialogs;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.deviceinfo.OEMDetector;
public class DisableBattery {
public static void alert(Activity activity) {
if (!(OEMDetector.isOEM() && Build.VERSION.SDK_INT >= 23 && Preferences.getBoolValue("showDoze", true)))
return;
final String packName = activity.getPackageName();
final PowerManager manager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
if (manager.isIgnoringBatteryOptimizations(packName)) return;
new VkAlertDialog.Builder(activity)
.setTitle(R.string.batteryissuetitle)
.setMessage(R.string.batteryissuesumm)
.setCancelable(false)
.setPositiveButton(R.string.batteryissuebtn1, (dialogInterface, i) -> {
@SuppressLint("BatteryLife")
var intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.parse("package:" + packName));
activity.startActivity(intent);
})
.setNeutralButton(R.string.batteryissuebtn2,
(dialogInterface, i) -> Preferences.getPreferences().edit().putBoolean("showDoze", false).apply()
)
.show();
}
}
@@ -1,9 +1,11 @@
package ru.vtosters.lite.ui.dialogs;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.ThemesUtils;
import ru.vtosters.lite.downloaders.OTADownloader;
@@ -16,22 +18,25 @@ public class OTADialog extends ModalBottomSheetWrapper<OTADialog> implements OTA
private final OTAUtils mHelper;
public OTADialog(Activity activity) {
public OTADialog(Activity activity, boolean isManualCheck) {
super(activity);
mActivity = activity;
mHelper = new OTAUtils(this);
mHelper.loadData();
mHelper.loadData(isManualCheck);
}
public static void checkUpdates(Activity activity) {
new OTADialog(activity);
new OTADialog(activity, false);
}
public static void checkUpdatesManual(Activity activity) {
new OTADialog(activity, true);
}
@Override
public void onUpdateApplied() {
public void onUpdateApplied(boolean isManualCheck) {
mActivity.runOnUiThread(() -> {
// Toast.makeText(mActivity, "Обновления найдены", Toast.LENGTH_SHORT).show();
setTitle(mActivity.getString(R.string.newversion) + " " + mHelper.getNewVersionName())
.setView(makeUpdateInfoView(mHelper.getUpdateDescription()))
.setPositiveButton(mActivity.getString(R.string.updateanddownload), () -> {
@@ -41,6 +46,7 @@ public class OTADialog extends ModalBottomSheetWrapper<OTADialog> implements OTA
});
}
@SuppressLint("SetTextI18n")
private View makeUpdateInfoView(String changelog) {
LinearLayout container = new LinearLayout(mActivity);
TextView changelogView = new TextView(mActivity);
@@ -55,9 +61,18 @@ public class OTADialog extends ModalBottomSheetWrapper<OTADialog> implements OTA
}
@Override
public void onUpdateCanceled() {
// mActivity.runOnUiThread(() -> {
// // Toast.makeText(mActivity, "Обновлений не найдено", Toast.LENGTH_SHORT).show();
// });
public void onUpdateLatest(boolean isManualCheck) {
if (isManualCheck) {
mActivity.runOnUiThread(() -> {
Toast.makeText(mActivity, "У вас установлена последняя версия приложения", Toast.LENGTH_SHORT).show();
});
}
}
@Override
public void onUpdateError() {
mActivity.runOnUiThread(() -> {
Toast.makeText(mActivity, "Ошибка проверки обновления", Toast.LENGTH_SHORT).show();
});
}
}
@@ -1,108 +0,0 @@
package ru.vtosters.lite.ui.dialogs;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import com.vk.core.dialogs.alert.VkAlertDialog;
import okhttp3.*;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.utils.LifecycleUtils;
import java.io.IOException;
import java.util.Random;
import static android.content.ContentValues.TAG;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
import static ru.vtosters.hooks.other.Preferences.hasVerification;
public class ServerDialog {
private static final OkHttpClient client = VtOkHttpClient.getInstance();
public static Boolean showAlert;
private static String title;
private static String message;
private static String positiveButton;
private static String neutralButton;
private static String link;
private static String key;
private static Boolean cancelable;
private static Boolean isNotForVerified;
public static void sendRequest() {
if (getBoolValue("dialogrecomm", false)) return;
if (!getBoolValue("isRoamingState", false)) {
Request request = new Request.a()
.b("https://vtosters.app/dialog.json")
.a();
client.a(request).a(new Callback() {
@Override
public void a(Call call, IOException e) {
Log.d(TAG, e.getMessage());
showAlert = false;
}
@Override
public void a(Call call, Response response) {
try {
getResponse(response.a().g());
} catch (Exception e) {
showAlert = false;
}
}
});
}
}
private static Boolean showForNotVerified() {
int random = new Random().nextInt(6);
if (isNotForVerified) {
return !hasVerification() && random != 0;
}
return true;
}
private static void getResponse(String body) throws Exception {
JSONObject jSONObject = new JSONObject(body);
if (jSONObject.getString("title").equals("")) {
showAlert = false;
return;
}
title = jSONObject.getString("title");
message = jSONObject.getString("message");
positiveButton = jSONObject.getString("positiveButton");
neutralButton = jSONObject.getString("neutralButton");
link = jSONObject.getString("link");
key = jSONObject.getString("key");
cancelable = jSONObject.getBoolean("cancelable");
isNotForVerified = jSONObject.getBoolean("isNotForVerified");
showAlert = true;
Activity activity = LifecycleUtils.getCurrentActivity();
if (getBoolValue(key, true) && showForNotVerified() && showAlert) {
activity.runOnUiThread(() -> {
new VkAlertDialog.Builder(activity)
.setTitle(title)
.setMessage(message)
.setCancelable(cancelable)
.setPositiveButton(positiveButton,
(dialogInterface, i) -> Preferences.getPreferences().edit().putBoolean(key, false).apply())
.setNeutralButton(neutralButton, (dialogInterface, i) -> {
Preferences.getPreferences().edit().putBoolean(key, false).apply();
Intent intent = new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(link));
activity.startActivity(intent);
})
.show();
});
}
}
}
@@ -93,6 +93,7 @@ public class DataSettingsFragment extends TrackedMaterialPreferenceToolbarFragme
FileUtils.l();
AutoPlayCacheHolder.d.a();
MediaStorage.a();
IOUtils.deleteRecursive(AndroidUtils.getGlobalContext().getCacheDir());
updateCacheSize();
})
.setNegativeButton(requireContext().getString(com.vtosters.lite.R.string.cancel), (dialogInterface, i) -> dialogInterface.dismiss())
@@ -125,6 +126,7 @@ public class DataSettingsFragment extends TrackedMaterialPreferenceToolbarFragme
ImEngineExt.a(ImEngine1.a());
AutoPlayCacheHolder.d.a();
MediaStorage.a();
IOUtils.deleteRecursive(AndroidUtils.getGlobalContext().getCacheDir());
}
case 1 -> {
SharedPreferences prefs2 = getContext().getSharedPreferences("stickers_storage", Context.MODE_PRIVATE);
@@ -299,6 +301,20 @@ public class DataSettingsFragment extends TrackedMaterialPreferenceToolbarFragme
}
);
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"serverFeaturesDisable",
"Отключить всю работу с сервером",
"Отключает галочки и все дополнительные функции связанные с получением данных с сервера ВТостерс",
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("serverFeaturesDisable", (boolean) o).apply();
LifecycleUtils.restartApplicationWithTimer();
return true;
}
);
findPreference("analyticsDisabled").setVisible(Preferences.isValidSignature());
}
@@ -92,13 +92,6 @@ public class IconsFragment extends TrackedMaterialPreferenceToolbarFragment {
this.addPreferencesFromResource(R.xml.empty);
if (!Preferences.hasVerification() && !Preferences.getBoolValue("dialogrecomm", false)) {
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", requireContext().getString(R.string.icons_warning), requireContext().getString(R.string.icons_warning_info), R.drawable.ic_about_outline_28, preference -> {
getContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://vtosters.app/donate/")));
return false;
});
}
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), requireContext().getString(R.string.icons_title));
for (var i = 0; i < IconManager.icons().size(); i++) {
@@ -128,37 +121,5 @@ public class IconsFragment extends TrackedMaterialPreferenceToolbarFragment {
return false;
});
}
if (!Preferences.hasVerification() && !Preferences.getBoolValue("dialogrecomm", false)) {
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), requireContext().getString(R.string.unavailable_icons));
for (var i = 2; i < IconManager.sIconsPlusNames.size(); i++) {
if (IconManager.sIconsPlusNames.get(i) == null || IconManager.sIconsPlus.get(i) == null) return;
String iconname = IconManager.sIconsPlusNames.get(i);
String icon = IconManager.sIconsPlus.get(i);
int iconRes;
if (icon.equals("vt")) {
iconRes = R.mipmap.vt_launcher_round;
} else if (icon.contains("standard")) {
iconRes = R.mipmap.ic_launcher_round;
} else {
iconRes = AndroidUtils.getIdentifier("ic_launcher_" + icon, "mipmap");
}
Drawable drawable = ResourcesCompat.getDrawable(getResources(), iconRes, null);
if (drawable == null) {
drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.ic_bug_outline_28, null);
}
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), icon, iconname, "", drawable, preference -> {
AndroidUtils.sendToast(requireContext().getString(R.string.unavailable_icon_warning));
return false;
});
}
}
}
}
@@ -1,6 +1,9 @@
package ru.vtosters.lite.ui.fragments;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vtosters.lite.R;
public class InstallGMSFragment extends TrackedMaterialPreferenceToolbarFragment {
@@ -8,6 +11,24 @@ public class InstallGMSFragment extends TrackedMaterialPreferenceToolbarFragment
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
addPreferencesFromResource(R.xml.preferences_gms);
findPreference("microg").setOnPreferenceClickListener((preference) -> {
String[] items = {"Уменьшенный MicroG (ReVanced)", "Альтернативный MicroG (ReVanced)", "Полноценный MicroG (Замена GMS)"};
new VkAlertDialog.Builder(requireActivity()).setItems(items, (dialog, which) -> {
switch (which) {
case 0 ->
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://github.com/TeamVanced/VancedMicroG/releases/latest/download/microg.apk")));
case 1 ->
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://github.com/inotia00/mMicroG/releases/latest/download/microg.apk")));
case 2 ->
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://github.com/microg/GmsCore/releases")));
}
dismiss();
}).show();
return true;
});
}
@Override
@@ -24,7 +24,7 @@ import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.downloaders.VideoDownloader;
import ru.vtosters.lite.music.LastFMScrobbler;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.proxy.ProxyUtils;
import ru.vtosters.lite.ui.adapters.ImagineArrayAdapter;
import ru.vtosters.lite.utils.AccountManagerUtils;
@@ -108,9 +108,9 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
findPreference("lastfm_enabled").setEnabled(false);
}
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(R.string.cached_tracks_counter), CacheDatabaseDelegate.getTrackCount()));
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(R.string.cached_tracks_counter), MusicCacheImpl.getTracksCount()));
findPreference("cached_tracks").setOnPreferenceClickListener(preference -> {
if (CacheDatabaseDelegate.getTrackCount() == 0) {
if (MusicCacheImpl.isEmpty()) {
AndroidUtils.sendToast(requireContext().getString(R.string.no_cache_error));
} else {
delcache(requireContext());
@@ -123,6 +123,8 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
findPreference("invertCachedTracks").setVisible(!MusicCacheImpl.isEmpty());
if (!LibVKXClient.isVkxInstalled()) {
findPreference("vkx_sett").setVisible(false);
}
@@ -215,7 +217,7 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
.setTitle(R.string.warning)
.setMessage(R.string.cached_tracks_remove_confirm)
.setPositiveButton(R.string.yes,
(dialog, which) -> executor.submit(CacheDatabaseDelegate::clear))
(dialog, which) -> executor.submit(MusicCacheImpl::clear))
.setNeutralButton(R.string.no,
(dialog, which) -> dialog.cancel())
.show();
@@ -16,6 +16,8 @@ import ru.vtosters.lite.ssfs.UsersList;
import ru.vtosters.lite.ui.activities.VKAdminTokenActivity;
import ru.vtosters.lite.utils.*;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
private static final int VK_ADMIN_TOKEN_REQUEST_CODE = 1;
@@ -65,7 +67,7 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
findPreference("microgsettings").setOnPreferenceClickListener(preference -> {
try {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setComponent(new ComponentName("com.mgoogle.android.gms", "org.microg.gms.ui.SettingsActivity"));
intent.setComponent(new ComponentName((GmsHook.isFakeGms2Installed() ? "app.revanced.android.gms" : "com.mgoogle.android.gms"), "org.microg.gms.ui.SettingsActivity"));
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
@@ -103,13 +105,6 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
findPreference("datasettings").setOnPreferenceClickListener(preference -> {
NavigatorUtils.switchFragment(requireContext(), DataSettingsFragment.class);
return true;
});
findPreference("dialogrecomm").setVisible(!Preferences.hasVerification());
findPreference("updateverifdata").setOnPreferenceClickListener(preference -> {
UsersList.getUsersList();
VTVerifications.load(requireContext());
@@ -117,6 +112,10 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
findPreference("VT_Verification").setVisible(!getBoolValue("serverFeaturesDisable", false));
findPreference("VT_Fire").setVisible(!getBoolValue("serverFeaturesDisable", false));
findPreference("updateverifdata").setVisible(!getBoolValue("serverFeaturesDisable", false));
var vkAdminTokenPref = findPreference("vk_admin_token");
vkAdminTokenPref.setVisible(Preferences.getPreferences().getBoolean("new_music_downloading_way", false));
if (vkAdminTokenPref.isVisible()) {
@@ -6,6 +6,7 @@ import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import com.vtosters.lite.R;
import ru.vtosters.hooks.GmsHook;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.deviceinfo.OEMDetector;
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
@@ -46,6 +47,11 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
boolean isEMUI = OEMDetector.isEMUI();
boolean hasMiuiIncrCode = OEMDetector.hasMiuiIncrCode();
boolean hasGMS = GmsHook.isGmsInstalled();
boolean hasVancedMicroG = GmsHook.isFakeGmsInstalled();
boolean hasReVancedMicroG = GmsHook.isFakeGms2Installed();
String GMSPackageName = hasGMS ? "com.google.android.gms" : hasVancedMicroG ? "com.mgoogle.android.gms" : hasReVancedMicroG ? "app.revanced.android.gms" : "no gms or microg";
boolean isValidSignature = Preferences.isValidSignature();
boolean isTablet = AndroidUtils.isTablet();
boolean isDebuggable = AndroidUtils.isDebuggable();
@@ -76,6 +82,12 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
return false;
});
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Google Market Services package", GMSPackageName, 0, preference -> {
((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", GMSPackageName));
AndroidUtils.sendToast(requireContext().getString(R.string.copied_to_clipboard));
return false;
});
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Valid Signature", "Value: " + isValidSignature, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isTablet", "Value: " + isTablet, 0, null);
@@ -90,6 +102,12 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
return false;
});
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledGoogleGMS", "Value: " + hasGMS, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledVancedMicroG", "Value: " + hasVancedMicroG, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledReVancedMicroG", "Value: " + hasReVancedMicroG, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Product Name", productName, 0, preference -> {
((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", productName));
AndroidUtils.sendToast(requireContext().getString(R.string.copied_to_clipboard));
@@ -13,7 +13,6 @@ import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.hooks.other.ThemesUtils;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.themes.ThemesCore;
import ru.vtosters.lite.themes.ThemesManager;
import ru.vtosters.lite.themes.palettes.PalettesManager;
import ru.vtosters.lite.ui.components.DockBarEditorManager;
@@ -1,16 +1,24 @@
package ru.vtosters.lite.ui.fragments;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.preference.Preference;
import com.aefyr.tsg.g2.TelegramStickersPack;
import com.aefyr.tsg.g2.TelegramStickersService;
import com.vk.about.AboutAppFragment;
import com.vk.balance.BalanceFragment;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.identity.fragments.IdentityListFragment;
import com.vk.medianative.MediaImageEncoder;
import com.vk.notifications.settings.NotificationsSettingsFragment;
@@ -35,6 +43,7 @@ import ru.vtosters.lite.BuildConfig;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.deviceinfo.OEMDetector;
import ru.vtosters.lite.ssfs.Utils;
import ru.vtosters.lite.themes.utils.RecolorUtils;
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
import ru.vtosters.lite.ui.dialogs.OTADialog;
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
@@ -42,7 +51,17 @@ import ru.vtosters.lite.utils.*;
import java.util.Locale;
public class VTSettings extends TrackedMaterialPreferenceToolbarFragment implements TelegramStickersService.StickersEventsListener {
public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
public static String ACTION_INVALIDATE_TGS_COUNT = "com.vtosters.lite.intent.action.INVALIDATE_TGS_COUNT";
BroadcastReceiver mTgsReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_INVALIDATE_TGS_COUNT.equals(intent.getAction())) {
findPreference("tgs_stickers").setSummary(getTGSsumm());
}
}
};
public static String getValAsString(@StringRes int strRes, Boolean value) {
if (value) {
@@ -76,46 +95,21 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
ThemesUtils.setTheme(isDarkTheme ? ThemesUtils.getDarkTheme() : ThemesUtils.getLightTheme(), requireActivity(), true);
}
@Override
public void onPackAdded(TelegramStickersPack pack, int atIndex) {
}
@Override
public void onPackRemoved(TelegramStickersPack pack, int atIndex) {
}
@Override
public void onPackChanged(TelegramStickersPack pack, int atIndex) {
}
@Override
public void onPackDownloadError(TelegramStickersPack pack, Exception error) {
}
@Override
public void onActivePacksListChanged() {
findPreference("tgs_stickers").setSummary(getTGSsumm());
}
@Override
public void onInactivePacksListChanged() {
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@SuppressWarnings("ConstantConditions")
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
TelegramStickersService.getInstance(requireContext()).addStickersEventsListener(this);
requireContext().registerReceiver(mTgsReceiver, new IntentFilter(ACTION_INVALIDATE_TGS_COUNT));
this.addPreferencesFromResource(R.xml.empty);
AndroidUtils.checkLinksVerified(this.requireActivity());
PowerManager manager = (PowerManager) requireActivity().getSystemService(Context.POWER_SERVICE);
boolean isLinksUnverified = AndroidUtils.isLinksUnverified(requireActivity());
boolean isDozingAvailable = Build.VERSION.SDK_INT >= 23 && !manager.isIgnoringBatteryOptimizations(AndroidUtils.getPackageName());
boolean areNotificationsDisabled = !AndroidUtils.areNotificationsEnabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
boolean isGMSNotInstalled = !GmsHook.isAnyServicesInstalled();
if (AccountManagerUtils.isLogin()) {
Preference accountSwitcher = PreferenceFragmentUtils.addPreference(
@@ -128,6 +122,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
try {
VKAuth.a("logout", false);
} catch (Exception ignored) {
// ignored
}
Intent intent = new Intent(requireContext(), MainActivity.class)
@@ -147,18 +142,20 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
});
});
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.vtssfs),
getSSFSsumm(),
R.drawable.ic_link_circle_outline_28,
preference -> {
VKUIwrapper.setLink(Utils.getVKUILink());
NavigatorUtils.switchFragment(requireContext(), VKUIwrapper.class);
return false;
}
);
if (Preferences.hasSpecialVerif()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.vtssfs),
getSSFSsumm(),
R.drawable.ic_link_circle_outline_28,
preference -> {
VKUIwrapper.setLink(Utils.getVKUILink());
NavigatorUtils.switchFragment(requireContext(), VKUIwrapper.class);
return false;
}
);
}
}
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), R.string.appearance_theme_use_system);
@@ -175,8 +172,8 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
AndroidUtils.sendToast(AndroidUtils.getString("systemtheme_enabled"));
return false;
}
final MaterialSwitchPreference switchPreference = (MaterialSwitchPreference) preference;
final boolean isDarkTheme = !switchPreference.isChecked();
MaterialSwitchPreference switchPreference = (MaterialSwitchPreference) preference;
boolean isDarkTheme = !switchPreference.isChecked();
switchTheme(isDarkTheme);
return true;
}
@@ -191,27 +188,89 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
R.drawable.ic_palette_outline_28,
Preferences.systemtheme(),
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("system_theme", (boolean) o).commit();
Preferences.getPreferences().edit().putBoolean("system_theme", (boolean) o).apply();
SystemThemeChangerHook.onThemeChanged(requireActivity().getResources().getConfiguration());
return true;
}
);
}
if (!GmsHook.isAnyServicesInstalled()) {
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), R.string.gmsname);
if ((isLinksUnverified || isDozingAvailable || areNotificationsDisabled || isGMSNotInstalled) && !Preferences.getBoolValue("dialogrecomm", false)) {
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Рекомендации");
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.installgms),
"",
R.drawable.ic_about_outline_28,
preference -> {
NavigatorUtils.switchFragment(requireContext(), InstallGMSFragment.class);
return false;
}
);
if (isGMSNotInstalled) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.installgms),
"Отсутствие этих сервисов приводит к поломке фоновых уведомлений и проблемам работы компонентов приложения",
RecolorUtils.recolorDrawable(R.drawable.ic_logo_google_28, ThemesUtils.getColor(R.color.red)),
preference -> {
NavigatorUtils.switchFragment(requireContext(), InstallGMSFragment.class);
return false;
}
);
}
if (isLinksUnverified) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Не выбраны ссылки для открытия приложением",
"Это помешает открытию внешних ссылок для их открытия с помощью этого приложения\n\nВ некоторых случаях необходимо отключить открытие ссылок официальным приложениям ВКонтакте",
RecolorUtils.recolorDrawable(R.drawable.ic_linked_outline_28, ThemesUtils.getColor(R.color.red)),
preference -> {
try {
Intent intent = new Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, Uri.parse("package:" + AndroidUtils.getPackageName()));
requireActivity().startActivity(intent);
} catch (Throwable t1) {
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + AndroidUtils.getPackageName()));
requireActivity().startActivity(intent);
} catch (Throwable ignored) {
// ignored
}
}
return false;
}
);
}
if (isDozingAvailable) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Отключите экономию батареи",
"Экономия батареи мешает получению фоновых сообщений, уведомлений и работе музыки" + (OEMDetector.isMIUI() ? "\n\nДля устройств Xiaomi необходимо вручную включить в настройках системы пункт Автозапуск данному приложению" : ""),
RecolorUtils.recolorDrawable(R.drawable.ic_filter_outline_28, ThemesUtils.getColor(R.color.red)),
preference -> {
@SuppressLint("BatteryLife")
var intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.parse("package:" + AndroidUtils.getPackageName()));
requireActivity().startActivity(intent);
return false;
}
);
}
if (areNotificationsDisabled) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Включите уведомления",
"Без включенных уведомлений не получится получать сообщения и многое другое",
RecolorUtils.recolorDrawable(R.drawable.ic_notifications_outline_28, ThemesUtils.getColor(R.color.red)),
preference -> {
@SuppressLint("BatteryLife")
var intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(Settings.EXTRA_APP_PACKAGE, AndroidUtils.getPackageName());
requireActivity().startActivity(intent);
return false;
}
);
}
}
if (Preferences.devmenu()) {
@@ -456,7 +515,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
getPreferenceScreen(),
"",
requireContext().getString(R.string.vtlinterface),
getValAsString(R.string.showstories, Preferences.stories()),
getValAsString(R.string.show_superapps_title, Preferences.superapp()),
R.drawable.ic_interface_outline_28,
preference -> {
NavigatorUtils.switchFragment(requireContext(), InterfaceFragment.class);
@@ -464,6 +523,18 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"currcache",
AndroidUtils.getString(R.string.datasettings_title),
getString(com.vtosters.lite.R.string.cache_size_summ) + ": " + CacheUtils.humanReadableByteCountBin(IOUtils.getDirSize(AndroidUtils.getGlobalContext().getCacheDir())),
R.drawable.ic_document_outline_28,
preference -> {
NavigatorUtils.switchFragment(requireContext(), DataSettingsFragment.class);
return false;
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
@@ -496,14 +567,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
AndroidUtils.getString("currentLanguage") + " " + AndroidUtils.upString(Locale.getDefault().getDisplayLanguage()),
R.drawable.ic_globe_outline_28,
preference -> {
try {
Intent intent = new Intent("android.settings.APP_LOCALE_SETTINGS", Uri.parse("package:" + AndroidUtils.getPackageName()));
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
AndroidUtils.sendToast("Ваша система не поддерживает смену языка приложения");
}
startActivity(new Intent("android.settings.APP_LOCALE_SETTINGS", Uri.parse("package:" + AndroidUtils.getPackageName())));
return false;
}
);
@@ -535,18 +599,57 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
}
);
if (AndroidUtils.isAdbOrDeveloperOptionsEnabled(requireContext().getContentResolver())) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.opencommit),
"",
R.drawable.ic_link_outline_28,
preference -> {
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(AboutHook.getCommitLink())));
return false;
}
);
}
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.opencommit),
"",
R.drawable.ic_link_outline_28,
"Наши чаты",
"Активные чаты по обсуждению модификации и многому другому",
R.drawable.ic_message_outline_28,
preference -> {
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(AboutHook.getCommitLink())));
var args = new Bundle();
var dialog = new AboutInformation();
args.putString("vk_link", "https://vk.me/join/jKE1bIZczHTF2mLkuoiQcd0qIBYgHuGFPRA=");
args.putString("tg_link", "https://t.me/vtosterschat");
dialog.setArguments(args);
dialog.show(requireFragmentManager(), "");
return false;
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Наши сообщества",
"Самая свежая информация об обновлениях и не только!",
R.drawable.users_3_outline_28,
preference -> {
var args = new Bundle();
var dialog = new AboutInformation();
args.putString("vk_link", "https://vk.com/vtosters_official");
args.putString("tg_link", "https://t.me/vtosters");
dialog.setArguments(args);
dialog.show(requireFragmentManager(), "");
return false;
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
@@ -582,16 +685,27 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
"",
R.drawable.ic_download_outline_28,
preference -> {
OTADialog.checkUpdates(getActivity());
OTADialog.checkUpdatesManual(getActivity());
return false;
}
);
}
}
@Override
public void onDestroy() {
requireContext().unregisterReceiver(mTgsReceiver);
super.onDestroy();
}
@Override
public void onResume() {
super.onResume();
updateCacheSize();
}
private void updateCacheSize() {
findPreference("currcache").setSummary(getString(com.vtosters.lite.R.string.cache_size_summ) + ": " + CacheUtils.humanReadableByteCountBin(IOUtils.getDirSize(AndroidUtils.getGlobalContext().getCacheDir())));
}
@Override
@@ -599,9 +713,24 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
return R.string.notification_settings;
}
@Override
public void onDestroyView() {
super.onDestroyView();
TelegramStickersService.getInstance(requireContext()).removeStickersEventsListener(this);
public static class AboutInformation extends DialogFragment {
@Override
public AlertDialog onCreateDialog(Bundle savedInstanceState) {
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(requireActivity());
String[] items = {AndroidUtils.getString(R.string.app_name_alter), "Telegram"};
builder.setItems(items, (dialog, which) -> {
switch (which) {
case 0 ->
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(getArguments().getString("vk_link"))));
case 1 ->
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(getArguments().getString("tg_link"))));
}
dismiss();
});
return builder.create();
}
}
}
@@ -20,7 +20,6 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.aefyr.tsg.g2.TelegramStickersService;
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.ThemesUtils;
@@ -36,55 +35,52 @@ import java.io.File;
public class StickersFragment extends BaseToolbarFragment {
public final static String ACTION_RELOAD = "com.vtosters.lite.action.RELOAD_TGS_LIST";
private final static int TYPE_DIRECT = 0, TYPE_SOCKS = 2;
public int from;
public int to;
private TelegramStickersGrabber mGrabber;
private TelegramStickersService mService;
private StickerPackAdapter mAdapter;
private boolean movePending = false;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
final static int TYPE_DIRECT = 0;
final static int TYPE_SOCKS = 2;
TelegramStickersGrabber mGrabber;
StickerPackAdapter mAdapter;
boolean movePending = false;
int from, to;
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@SuppressLint("NotifyDataSetChanged")
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_RELOAD)) {
if (mAdapter != null) {
try {
if (movePending) {
movePending = false;
mAdapter.notifyItemMoved(from, to);
from = -1;
to = -1;
} else {
mAdapter.notifyDataSetChanged();
}
} catch (Exception e) {
e.printStackTrace();
}
}
if (!intent.getAction().equals(ACTION_RELOAD) || mAdapter == null) return;
try {
if (movePending) {
movePending = false;
mAdapter.notifyItemMoved(from, to);
from = to = -1;
} else mAdapter.notifyDataSetChanged();
} catch (Exception e) {
e.printStackTrace();
}
}
};
@Override
protected void onCreateMenu(Menu menu) {
MenuItem item = menu.add(0, 0, 0, "");
item.setIcon(ThemesUtils.recolorDrawable(AndroidUtils.getGlobalContext().getDrawable(R.drawable.ic_settings_outline_28)))
menu.add(0, 0, 0, "")
.setIcon(ThemesUtils.recolorDrawable(requireContext().getDrawable(R.drawable.ic_add_outline_28)))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
menu.add(0, 1, 0, "")
.setIcon(R.drawable.ic_settings_outline_28)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
super.onCreateMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem arg0) {
openMenu(null);
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case 0 -> addTgsPack();
case 1 -> openMenu(null);
}
return true;
}
private void openMenu(String toast) {
if (toast != null) {
Toast.makeText(requireContext(), toast, Toast.LENGTH_SHORT).show();
}
if (toast != null) Toast.makeText(requireContext(), toast, Toast.LENGTH_SHORT).show();
NavigatorUtils.switchFragment(requireContext(), StickersPreferencesFragment.class);
}
@@ -94,40 +90,28 @@ public class StickersFragment extends BaseToolbarFragment {
requireContext().registerReceiver(mReceiver, new IntentFilter(ACTION_RELOAD));
mGrabber = new TelegramStickersGrabber(TGPref.getTGBotKey());
mService = TelegramStickersService.getInstance(requireContext());
}
@Override
public View onCreateContent(@NonNull LayoutInflater inflater, @Nullable Bundle bundle) {
setTitle(R.string.vtltgs);
FrameLayout layout = new FrameLayout(requireContext());
final var container = new FrameLayout(requireContext());
final var recycler = new RecyclerView(requireContext());
mAdapter = new StickerPackAdapter();
RecyclerView mRecycler = new RecyclerView(requireContext());
recycler.setAdapter(mAdapter);
StickerTouchHelperCallback callback = new StickerTouchHelperCallback(mAdapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(mRecycler);
final var callback = new StickerTouchHelperCallback(mAdapter);
final var touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recycler);
mRecycler.setAdapter(mAdapter);
mRecycler.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
layout.addView(mRecycler, new ViewGroup.LayoutParams(-1, -1));
recycler.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
container.addView(recycler, new ViewGroup.LayoutParams(-1, -1));
FloatingActionButton mAddStickerPack = new FloatingActionButton(requireContext());
mAddStickerPack.setImageResource(R.drawable.ic_add_24);
mAddStickerPack.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
mAddStickerPack.setOnClickListener(v2 -> fabClick());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(-2, -2);
params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
params.setMargins(0, 0, 0, AndroidUtils.dp2px(12f));
layout.addView(mAddStickerPack, params);
return layout;
return container;
}
@Override
public void onViewCreated(View arg0, Bundle arg1) {
super.onViewCreated(arg0, arg1);
@@ -139,63 +123,62 @@ public class StickersFragment extends BaseToolbarFragment {
requireContext().unregisterReceiver(mReceiver);
}
private void fabClick() {
int method = TGPref.getTGConnectMethod();
if (method == -1) {
openMenu(requireContext().getString(R.string.stickersconnection));
return;
}
private void addTgsPack() {
final var method = TGPref.getTGConnectMethod();
if (method != -1) {
initGrabber();
switch (method) {
case TYPE_SOCKS -> {
if (TGPref.getTGProxyIP() == null) openMenu(requireContext().getString(R.string.stickersproxy1));
}
case TYPE_DIRECT -> {
initGrabber();
final Runnable lambda = () ->
{
final var container = new LinearLayout(requireContext());
initGrabber();
if (method == TYPE_SOCKS) {
if (TGPref.getTGProxyIP() == null) {
openMenu(requireContext().getString(R.string.stickersproxy1));
final var input = new EditText(requireContext());
final var inputLP = new ViewGroup.MarginLayoutParams(-1, -2);
inputLP.setMargins(
AndroidUtils.dp2px(24.f),
0,
AndroidUtils.dp2px(24.f),
0);
container.addView(input, inputLP);
new VkAlertDialog.Builder(requireContext())
.setTitle(R.string.stickershelp1)
// .setMessage(R.string.stickershelp2)
.setView(container)
.setPositiveButton(android.R.string.ok, (dialog, which) ->
{
var packId = input.getText().toString();
if (packId.startsWith("https://"))
packId = packId.substring(packId.lastIndexOf('/') + 1);
TelegramStickersService.getInstance(requireContext())
.requestPackDownload(
packId,
new File(requireContext().getFilesDir(), "VT-Stickers/" + packId));
})
.setNeutralButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
.show();
};
if (TGPref.getTGBotKey() == null) enterBotKey(() -> checkApiKey(lambda));
else checkApiKey(lambda);
}
}
} else if (method == TYPE_DIRECT) {
final Runnable callback = () -> {
LinearLayout linearLayout = new LinearLayout(requireContext());
final EditText editText = new EditText(requireContext());
editText.setHintTextColor(ThemesUtils.getSTextAttr());
editText.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
linearLayout.addView(editText);
editText.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) editText.getLayoutParams());
margin.setMargins(AndroidUtils.dp2px(24f), 0, AndroidUtils.dp2px(24f), 0);
editText.setLayoutParams(margin);
new VkAlertDialog.Builder(requireContext())
.setTitle(requireContext().getString(R.string.stickershelp1))
.setMessage(requireContext().getString(R.string.stickershelp2))
.setView(linearLayout)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
String pack = editText.getText().toString();
if (!pack.startsWith("https://")) {
pack = "https://" + pack;
} else if (!pack.contains("addstickers")) {
AndroidUtils.sendToast(AndroidUtils.getString("invalid_pack_link"));
}
pack = parsePack(pack);
mService.requestPackDownload(pack, new File(requireContext().getFilesDir(), new File("VT-Stickers", pack).getAbsolutePath()));
})
.setNeutralButton(android.R.string.cancel,
(dialog, which) -> dialog.dismiss()
)
.show();
};
if (TGPref.getTGBotKey() == null) enterBotKey(() -> checkApiKey(callback));
else checkApiKey(callback);
}
} else openMenu(requireContext().getString(R.string.stickersconnection));
}
private void initGrabber() {
int method = TGPref.getTGConnectMethod();
if (method == TYPE_DIRECT) {
TelegramStickersGrabber.USE_PROXY = false;
mGrabber.resetProxy();
} else if (method == TYPE_SOCKS) {
if (TGPref.getTGProxyIP() != null) {
switch (TGPref.getTGConnectMethod()) {
case TYPE_DIRECT -> {
TelegramStickersGrabber.USE_PROXY = false;
mGrabber.resetProxy();
}
case TYPE_SOCKS -> {
if (TGPref.getTGProxyIP() == null) break;
TelegramStickersGrabber.USE_PROXY = true;
TelegramStickersGrabber.PROXY_IP = TGPref.getTGProxyIP();
TelegramStickersGrabber.PROXY_PORT = TGPref.getTGProxyPort();
@@ -210,42 +193,41 @@ public class StickersFragment extends BaseToolbarFragment {
TelegramStickersGrabber.updateURLs();
}
private void enterBotKey(Runnable r) {
LinearLayout linearLayout = new LinearLayout(requireContext());
private void enterBotKey(Runnable callback) {
final var container = new LinearLayout(requireContext());
final EditText editText = new EditText(requireContext());
editText.setHintTextColor(ThemesUtils.getSTextAttr());
editText.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
linearLayout.addView(editText);
editText.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) editText.getLayoutParams());
margin.setMargins(AndroidUtils.dp2px(24f), 0, AndroidUtils.dp2px(24f), 0);
editText.setLayoutParams(margin);
final var input = new EditText(requireContext());
final var inputLP = new ViewGroup.MarginLayoutParams(-1, -2);
inputLP.setMargins(
AndroidUtils.dp2px(24.f),
0,
AndroidUtils.dp2px(24.f),
0);
container.addView(input, inputLP);
new VkAlertDialog.Builder(requireContext())
.setTitle(requireContext().getString(R.string.stickersapi5))
.setMessage(requireContext().getString(R.string.stickersapi6))
.setView(linearLayout)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
TGPref.setTGBotKey(editText.getText().toString());
if (r != null) r.run();
.setView(container)
.setPositiveButton(android.R.string.ok, (dialog, which) ->
{
TGPref.setTGBotKey(input.getText().toString());
if (callback != null) callback.run();
})
.setNegativeButton(android.R.string.cancel,
(dialog, which) -> dialog.dismiss()
)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
.setNeutralButton(requireContext().getString(R.string.stickersapi7),
(dialog, which) -> new VkAlertDialog.Builder(requireContext())
.setTitle(requireContext().getString(R.string.stickersapi8))
.setMessage(requireContext().getString(R.string.stickersapi9) +
requireContext().getString(R.string.stickersapi10))
.setNeutralButton(AndroidUtils.getString("open_bot"), (dl, i) -> {
var intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/botfather"));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
requireActivity().startActivity(intent);
})
.setPositiveButton(android.R.string.ok, null)
.show()
(dialog, which) ->
new VkAlertDialog.Builder(requireContext())
.setTitle(requireContext().getString(R.string.stickersapi8))
.setMessage(requireContext().getString(R.string.stickersapi9) +
requireContext().getString(R.string.stickersapi10))
.setNeutralButton(AndroidUtils.getString("open_bot"), (dl, i) -> {
final var intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/botfather"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
requireActivity().startActivity(intent);
})
.setPositiveButton(android.R.string.ok, null)
.show()
)
.show();
}
@@ -253,45 +235,35 @@ public class StickersFragment extends BaseToolbarFragment {
private void checkApiKey(Runnable callback) {
mGrabber.setBotApiKey(TGPref.getTGBotKey());
var context = requireContext();
final ProgressDialog progressDialog = new ProgressDialog(context);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setMessage(requireContext().getString(R.string.stickersapi1));
progressDialog.show();
final var dialog = new ProgressDialog(requireContext());
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dialog.setMessage(requireContext().getString(R.string.stickersapi1));
dialog.show();
mGrabber.checkKey(new TelegramStickersGrabber.KeyCheckListener() {
@Override
public void onKeyChecked(boolean ok) {
progressDialog.dismiss();
dialog.dismiss();
if (!ok) {
Toast.makeText(context, requireContext().getString(R.string.stickersapi2), Toast.LENGTH_SHORT).show();
Toast.makeText(requireContext(), requireContext().getString(R.string.stickersapi2), Toast.LENGTH_SHORT).show();
TGPref.setTGBotKey(null);
return;
}
mService.setBotKey(TGPref.getTGBotKey());
TelegramStickersService.getInstance(requireContext())
.setBotKey(TGPref.getTGBotKey());
if (callback != null) callback.run();
}
@Override
public void onNetError() {
progressDialog.dismiss();
new VkAlertDialog.Builder(context)
dialog.dismiss();
new VkAlertDialog.Builder(requireContext())
.setMessage(requireContext().getString(R.string.stickersapi3))
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(requireContext().getString(R.string.stickersapi4), (arg0, arg1) -> checkApiKey(callback))
.setPositiveButton(requireContext().getString(R.string.stickersapi4), (di, which) -> checkApiKey(callback))
.show();
}
});
}
private String parsePack(String pack) {
if (pack.startsWith("https://t.me/addstickers/")) {
pack = pack.substring(25);
} else if (pack.startsWith("https://telegram.me/addstickers/")) {
pack = pack.substring(32);
}
return pack;
}
}
@@ -37,16 +37,16 @@ public class AlphaView extends SliderViewBase implements ColorObserver {
@Override
protected Bitmap makeBitmap(int w, int h) {
final boolean isWide = w > h;
final int n = Math.max(w, h);
boolean isWide = w > h;
int n = Math.max(w, h);
int color = observableColor.getColor();
int[] colors = new int[n];
for (int i = 0; i < n; ++i) {
float alpha = isWide ? (float) i / n : 1 - (float) i / n;
colors[i] = color & 0xffffff | (int) (alpha * 0xff) << 24;
}
final int bmpWidth = isWide ? w : 1;
final int bmpHeight = isWide ? 1 : h;
int bmpWidth = isWide ? w : 1;
int bmpHeight = isWide ? 1 : h;
return Bitmap.createBitmap(colors, bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
}
@@ -18,13 +18,12 @@ class HexEdit {
new HexFilter()
};
public static void setUpListeners(final EditText hexEdit, final ObservableColor observableColor) {
public static void setUpListeners(EditText hexEdit, ObservableColor observableColor) {
class MultiObserver implements ColorObserver, TextWatcher {
@Override
public void updateColor(ObservableColor observableColor) {
final String colorString = formatColor(observableColor.getColor());
String colorString = formatColor(observableColor.getColor());
// Prevent onTextChanged getting called when we update text programmatically
hexEdit.removeTextChangedListener(this);
hexEdit.setText(colorString);
@@ -57,7 +56,7 @@ class HexEdit {
}
}
final MultiObserver multiObserver = new MultiObserver();
MultiObserver multiObserver = new MultiObserver();
hexEdit.addTextChangedListener(multiObserver);
observableColor.addObserver(multiObserver);
setShowAlphaDigits(hexEdit, true);
@@ -71,14 +70,13 @@ class HexEdit {
}
}
public static void setShowAlphaDigits(final EditText hexEdit, boolean showAlphaDigits) {
public static void setShowAlphaDigits(EditText hexEdit, boolean showAlphaDigits) {
hexEdit.setFilters(showAlphaDigits ? withAlphaDigits : withoutAlphaDigits);
hexEdit.setText(hexEdit.getText()); // trigger a reformat of text
}
private static class ColorPasteLengthFilter implements InputFilter {
private static final int MAX_LENGTH = 6;
private static final int PASTED_LEN = 8;
private final InputFilter sixDigitFilter = new InputFilter.LengthFilter(MAX_LENGTH);
@@ -86,8 +84,8 @@ class HexEdit {
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
// If 8 digits have been pasted, replacing all source, trim alpha digits.
// Otherwise standard LengthFilter behavior.
final int srcLength = end - start;
final int dstSelLength = dend - dstart;
int srcLength = end - start;
int dstSelLength = dend - dstart;
if (srcLength == PASTED_LEN && dstSelLength == dest.length()) {
// Discard alpha digits:
return source.subSequence(PASTED_LEN - MAX_LENGTH, PASTED_LEN);
@@ -98,7 +96,6 @@ class HexEdit {
}
private static class HexFilter implements InputFilter {
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
boolean state = true;
@@ -57,9 +57,9 @@ public class HueSatView extends SquareView implements ColorObserver {
float[] hsv = new float[]{0f, 0f, 1f};
for (int y = 0; y < radiusPx; ++y) {
for (int x = 0; x < radiusPx; ++x) {
final int i = x + y * radiusPx;
final float sat = satForPos(x, y, radiusPx);
final float arcBleed = 2f / radiusPx; // extend curved edge pixels just outside clip area.
int i = x + y * radiusPx;
float sat = satForPos(x, y, radiusPx);
float arcBleed = 2f / radiusPx; // extend curved edge pixels just outside clip area.
if (sat <= 1 + arcBleed) {
hsv[0] = hueForPos(x, y, radiusPx);
hsv[1] = sat;
@@ -71,35 +71,35 @@ public class HueSatView extends SquareView implements ColorObserver {
}
private static float hueForPos(float x, float y, float radiusPx) {
final double r = radiusPx - 1; // gives values 0...1 inclusive
final double dx = (r - x) / r;
final double dy = (r - y) / r;
final double angle = Math.atan2(dy, dx);
final double hue = 360 * angle / (Math.PI / 2);
double r = radiusPx - 1; // gives values 0...1 inclusive
double dx = (r - x) / r;
double dy = (r - y) / r;
double angle = Math.atan2(dy, dx);
double hue = 360 * angle / (Math.PI / 2);
return (float) hue;
}
private static float satForPos(float x, float y, float radiusPx) {
final double r = radiusPx - 1; // gives values 0...1 inclusive
final double dx = (r - x) / r;
final double dy = (r - y) / r;
final double sat = dx * dx + dy * dy; // leave it squared -- exaggerates pale colors
double r = radiusPx - 1; // gives values 0...1 inclusive
double dx = (r - x) / r;
double dy = (r - y) / r;
double sat = dx * dx + dy * dy; // leave it squared -- exaggerates pale colors
return (float) sat;
}
private static void setPointer(PointF pointer, float hue, float sat, float radiusPx) {
final float r = radiusPx - 1; // for values 0...1 inclusive
final double distance = r * Math.sqrt(sat);
final double angle = hue / 360 * Math.PI / 2;
final double dx = distance * Math.cos(angle);
final double dy = distance * Math.sin(angle);
float r = radiusPx - 1; // for values 0...1 inclusive
double distance = r * Math.sqrt(sat);
double angle = hue / 360 * Math.PI / 2;
double dx = distance * Math.cos(angle);
double dy = distance * Math.sin(angle);
pointer.set(r - (float) dx, r - (float) dy);
}
private int optimalBitmapSize() {
final int scale = 2;
final int maxBitmapSize = 128;
final DisplayMetrics dm = getResources().getDisplayMetrics();
int scale = 2;
int maxBitmapSize = 128;
DisplayMetrics dm = getResources().getDisplayMetrics();
return Math.min(maxBitmapSize, Math.min(dm.widthPixels, dm.heightPixels) / scale);
}
@@ -159,9 +159,9 @@ public class HueSatView extends SquareView implements ColorObserver {
private boolean clamp(PointF pointer, float x, float y, boolean rejectOutside) {
x = Math.min(x, w);
y = Math.min(y, h);
final float dx = w - x;
final float dy = h - y;
final float r = (float) Math.sqrt(dx * dx + dy * dy);
float dx = w - x;
float dy = h - y;
float r = (float) Math.sqrt(dx * dx + dy * dy);
boolean outside = r > w;
if (!outside || !rejectOutside) {
if (outside) {
@@ -6,7 +6,6 @@ import android.view.MotionEvent;
import android.view.View;
public abstract class SliderViewBase extends View {
private final Paint borderPaint;
private final Paint checkerPaint;
private final Rect viewRect = new Rect();
@@ -18,10 +18,10 @@ class SquareView extends View {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Constrain to square
final int w = MeasureSpec.getSize(widthMeasureSpec);
final int h = MeasureSpec.getSize(heightMeasureSpec);
final int modeW = MeasureSpec.getMode(widthMeasureSpec);
final int modeH = MeasureSpec.getMode(heightMeasureSpec);
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
int modeW = MeasureSpec.getMode(widthMeasureSpec);
int modeH = MeasureSpec.getMode(heightMeasureSpec);
int size = minSizePx;
if (modeW == MeasureSpec.UNSPECIFIED) {
size = h;
@@ -8,7 +8,6 @@ import android.graphics.RectF;
import ru.vtosters.lite.utils.AndroidUtils;
public class SwatchView extends SquareView implements ColorObserver {
private final Paint borderPaint;
private final Path borderPath;
private final Paint checkerPaint;
@@ -46,14 +45,14 @@ public class SwatchView extends SquareView implements ColorObserver {
}
private static void cornerArc(Path path, float cx, float cy, float r, float startAngle, float sweepAngle) {
final RectF ovalRect = new RectF(-r, -r, r, r);
RectF ovalRect = new RectF(-r, -r, r, r);
ovalRect.offset(cx, cy);
path.arcTo(ovalRect, startAngle, sweepAngle);
}
private static void mainArc(Path path, float viewSize, float margin, float startAngle, float sweepAngle) {
float r = viewSize + margin;
final RectF ovalRect = new RectF(-r, -r, r, r);
RectF ovalRect = new RectF(-r, -r, r, r);
ovalRect.offset(viewSize, viewSize);
path.arcTo(ovalRect, startAngle, sweepAngle);
}
@@ -77,23 +76,23 @@ public class SwatchView extends SquareView implements ColorObserver {
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// We expect to be stacked behind a square HueSatView and fill the top left corner.
final float inset = borderPaint.getStrokeWidth() / 2;
final float r = Math.min(w, h);
float inset = borderPaint.getStrokeWidth() / 2;
float r = Math.min(w, h);
// Trim the corners of the swatch is where it is radialMarginPx thick.
// find how long the outeredges are:
final float margin = radialMarginPx;
final float diagonal = r + 2 * margin;
final float opp = (float) Math.sqrt(diagonal * diagonal - r * r);
final float edgeLen = r - opp;
float margin = radialMarginPx;
float diagonal = r + 2 * margin;
float opp = (float) Math.sqrt(diagonal * diagonal - r * r);
float edgeLen = r - opp;
// Arc angles for drawing CCW
final float outerAngle = (float) Math.toDegrees(Math.atan2(opp, r));
final float startAngle = 270 - outerAngle;
float outerAngle = (float) Math.toDegrees(Math.atan2(opp, r));
float startAngle = 270 - outerAngle;
// Sweep angle for each half of the swatch:
final float sweepAngle = outerAngle - 45;
float sweepAngle = outerAngle - 45;
// Sweep angle for the smooth corners:
final float cornerSweepAngle = 90 - outerAngle;
float cornerSweepAngle = 90 - outerAngle;
// Outer border:
beginBorder(borderPath, inset, edgeLen, margin, cornerSweepAngle);
@@ -5,7 +5,6 @@ import android.graphics.Bitmap;
import android.graphics.Color;
public class ValueView extends SliderViewBase implements ColorObserver {
private ObservableColor observableColor = new ObservableColor(0);
public ValueView(Context context) {
@@ -37,8 +36,8 @@ public class ValueView extends SliderViewBase implements ColorObserver {
}
protected Bitmap makeBitmap(int w, int h) {
final boolean isWide = w > h;
final int n = Math.max(w, h);
boolean isWide = w > h;
int n = Math.max(w, h);
int[] colors = new int[n];
float[] hsv = new float[]{0, 0, 0};
@@ -48,8 +47,8 @@ public class ValueView extends SliderViewBase implements ColorObserver {
hsv[2] = isWide ? (float) i / n : 1 - (float) i / n;
colors[i] = Color.HSVToColor(hsv);
}
final int bmpWidth = isWide ? w : 1;
final int bmpHeight = isWide ? 1 : h;
int bmpWidth = isWide ? w : 1;
int bmpHeight = isWide ? 1 : h;
return Bitmap.createBitmap(colors, bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
}
@@ -11,8 +11,7 @@ import java.nio.ByteBuffer;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
enum ImageEffects {
Blur(true,
AndroidUtils.getArray(R.array.filter_types),
Blur(AndroidUtils.getArray(R.array.filter_types),
new String[]{"disabled", "low", "med", "high"},
getGlobalContext().getString(R.string.filter_blur)
) {
@@ -40,8 +39,7 @@ enum ImageEffects {
};
}
},
Dim(true,
AndroidUtils.getArray(R.array.filter_dim_types),
Dim(AndroidUtils.getArray(R.array.filter_dim_types),
new String[]{"disabled", "dim_black", "dim_white"},
getGlobalContext().getString(R.string.filter_dim)) {
@Override
@@ -66,8 +64,7 @@ enum ImageEffects {
};
}
},
Mosaic(true,
AndroidUtils.getArray(R.array.filter_types),
Mosaic(AndroidUtils.getArray(R.array.filter_types),
new String[]{"disabled", "low", "med", "high"},
getGlobalContext().getString(R.string.filter_mosaic)) {
@Override
@@ -94,7 +91,7 @@ enum ImageEffects {
};
}
},
Monochrome(false, null, null,
Monochrome(null, null,
getGlobalContext().getString(R.string.filter_monochrome)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
@@ -103,7 +100,7 @@ enum ImageEffects {
}
}
},
Invert(false, null, null,
Invert(null, null,
getGlobalContext().getString(R.string.filter_invert_colors)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
@@ -112,7 +109,7 @@ enum ImageEffects {
}
}
},
Sepia(false, null, null,
Sepia(null, null,
getGlobalContext().getString(R.string.filter_sepia)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
@@ -121,7 +118,7 @@ enum ImageEffects {
}
}
},
Emboss(false, null, null,
Emboss(null, null,
getGlobalContext().getString(R.string.filter_emboss)) {
@Override
public String getSummary() {
@@ -135,7 +132,7 @@ enum ImageEffects {
}
}
},
Engrave(false, null, null,
Engrave(null, null,
getGlobalContext().getString(R.string.filter_engrave)) {
@Override
public String getSummary() {
@@ -149,7 +146,7 @@ enum ImageEffects {
}
}
},
Flea(false, null, null,
Flea(null, null,
getGlobalContext().getString(R.string.filter_flea)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
@@ -158,7 +155,7 @@ enum ImageEffects {
}
}
},
Snow(false, null, null,
Snow(null, null,
getGlobalContext().getString(R.string.filter_snow)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
@@ -168,15 +165,13 @@ enum ImageEffects {
}
};
private final boolean free;
private final String title;
@Nullable
private final String[] entryValues;
@Nullable
private final String[] entries;
ImageEffects(boolean free, @Nullable String[] entries, @Nullable String[] entryValues, String title) {
this.free = free;
ImageEffects(@Nullable String[] entries, @Nullable String[] entryValues, String title) {
this.entries = entries;
this.entryValues = entryValues;
this.title = title;
@@ -207,10 +202,6 @@ enum ImageEffects {
return entryValues != null && entryValues.length > 0;
}
public boolean isFree() {
return free;
}
@Nullable
public String[] getEntries() {
return entries;
@@ -80,24 +80,7 @@ public class WallpaperMenuFragment extends MaterialPreferenceToolbarFragment {
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), requireContext().getString(R.string.vtlfilters));
if (!Preferences.hasVerification() && !Preferences.getBoolValue("dialogrecomm", false)) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.filters_warning),
requireContext().getString(R.string.icons_warning_info),
0,
preference -> {
requireContext().startActivity(
new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("https://vtosters.app/donate/"))
);
return false;
});
}
Arrays.stream(ImageEffects.values())
.filter(imageEffects -> imageEffects.isFree() || Preferences.hasVerification())
.forEach(it -> {
if (it.isList()) {
var pref = PreferenceFragmentUtils.addListPreference(
@@ -1,8 +1,9 @@
package ru.vtosters.lite.utils;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.verify.domain.DomainVerificationManager;
@@ -14,11 +15,10 @@ import android.provider.Settings;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import com.vk.core.dialogs.alert.VkAlertDialog;
import androidx.core.app.NotificationManagerCompat;
import com.vk.core.util.Screen;
import com.vk.core.util.ToastUtils;
import com.vtosters.lite.general.fragments.WebViewFragment;
import ru.vtosters.hooks.other.Preferences;
import java.io.File;
import java.io.FileOutputStream;
@@ -30,8 +30,6 @@ import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Random;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
public class AndroidUtils {
private static final String ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnm";
@@ -39,6 +37,12 @@ public class AndroidUtils {
return 0 != (AndroidUtils.getGlobalContext().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE);
}
public static boolean isAdbOrDeveloperOptionsEnabled(ContentResolver contentResolver) {
int adbEnabled = Settings.Global.getInt(contentResolver, Settings.Global.ADB_ENABLED, 0);
int developerOptionsEnabled = Settings.Global.getInt(contentResolver, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
return adbEnabled == 1 || developerOptionsEnabled == 1;
}
public static boolean isTablet() {
return Screen.l(getGlobalContext());
}
@@ -46,7 +50,7 @@ public class AndroidUtils {
@NonNull
public static Context getGlobalContext() {
try {
final Method getInitialApplicationMtd = ReflectionUtils.findMethod(Class.forName("android.app.AppGlobals"), "getInitialApplication");
Method getInitialApplicationMtd = ReflectionUtils.findMethod(Class.forName("android.app.AppGlobals"), "getInitialApplication");
return (Context) getInitialApplicationMtd.invoke(null);
} catch (Exception e) {
Log.d("GlobalContext", "Error while fetching context via refl");
@@ -117,8 +121,8 @@ public class AndroidUtils {
new WebViewFragment.g(url).l().m().h().j().a(activity);
}
public static void checkLinksVerified(Activity activity) {
if (Build.VERSION.SDK_INT < 31) return;
public static boolean isLinksUnverified(Activity activity) {
if (Build.VERSION.SDK_INT < 31) return false;
DomainVerificationManager manager = activity.getSystemService(DomainVerificationManager.class);
DomainVerificationUserState userState;
@@ -126,7 +130,7 @@ public class AndroidUtils {
try {
userState = manager.getDomainVerificationUserState(getPackageName());
} catch (PackageManager.NameNotFoundException ignore) {
return;
return false;
}
boolean hasUnverified = false;
@@ -138,32 +142,7 @@ public class AndroidUtils {
hasUnverified = stateValue != null && stateValue != DomainVerificationUserState.DOMAIN_STATE_VERIFIED && stateValue != DomainVerificationUserState.DOMAIN_STATE_SELECTED;
}
if (hasUnverified) {
if (getBoolValue("showUnverifDialog", true)) {
new VkAlertDialog.Builder(activity)
.setTitle(com.vtosters.lite.R.string.warning)
.setMessage(AndroidUtils.getString("app_open_by_default_settings"))
.setCancelable(false)
.setPositiveButton(com.vtosters.lite.R.string.social_graph_skip,
(dialogInterface, i) -> Preferences.getPreferences().edit().putBoolean("showUnverifDialog", false).apply()
)
.setNeutralButton(com.vtosters.lite.R.string.open_settings,
(dialogInterface, i) -> {
try {
Intent intent = new Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, Uri.parse("package:" + getPackageName()));
activity.startActivity(intent);
} catch (Throwable t1) {
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName()));
activity.startActivity(intent);
} catch (Throwable ignored) {
}
}
}
)
.show();
}
}
return hasUnverified;
}
public static String MD5(String s) {
@@ -185,9 +164,18 @@ public class AndroidUtils {
return null;
}
public static boolean areNotificationsEnabled() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) getGlobalContext().getSystemService(Context.NOTIFICATION_SERVICE);
return manager.areNotificationsEnabled();
} else {
return NotificationManagerCompat.from(getGlobalContext()).areNotificationsEnabled();
}
}
private static String getRandomString(int sizeOfRandomString) {
final Random random = new Random();
final StringBuilder sb = new StringBuilder(sizeOfRandomString);
Random random = new Random();
StringBuilder sb = new StringBuilder(sizeOfRandomString);
for (int i = 0; i < sizeOfRandomString; ++i)
sb.append(ALLOWED_CHARACTERS.charAt(random.nextInt(ALLOWED_CHARACTERS.length())));
return sb.toString();
@@ -221,7 +209,6 @@ public class AndroidUtils {
long lastUpdateTime = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).lastUpdateTime;
return firstInstallTime == lastUpdateTime;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return true;
}
}
@@ -26,7 +26,7 @@ public class CrashReporter {
}
public static void init(Activity activity) {
final var defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
var defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((thread, th) -> CrashReporter.start(defaultUncaughtExceptionHandler, thread, th, activity));
}
@@ -76,9 +76,8 @@ public class CrashReporter {
}
private static boolean isFoxbinInstalled() {
var pm = getGlobalContext().getPackageManager();
try {
pm.getPackageInfo("com.f0x1d.dogbin", 0);
getGlobalContext().getPackageManager().getPackageInfo("com.f0x1d.dogbin", 0);
return true;
} catch (Exception e) {
return false;
@@ -44,7 +44,7 @@ public class ExternalLinkParser {
parseVKVideo(videoFile, qualities, urls);
if (urls.size() > 0)
if (!urls.isEmpty())
if (getBoolValue("maxquality", false) || qualities.size() == 1)
startExternalVideo(context, urls.get(0));
else
@@ -1,39 +1,12 @@
package ru.vtosters.lite.utils;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class IOUtils {
public static final int BUFFER_SIZE = 8192;
public static byte[] decodeStream(InputStream encIs, byte[] aesKey)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IOException {
CipherInputStream cip = new CipherInputStream(encIs, getCipher(aesKey));
return readFully(cip);
}
public static Cipher getCipher(byte[] aesKey)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Key keySpec = new SecretKeySpec(aesKey, "AES");
byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x0);
IvParameterSpec ivps = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
return cipher;
}
public static int BUFFER_SIZE = 8192;
public static InputStream openStream(String url)
throws IOException {
@@ -51,11 +24,11 @@ public class IOUtils {
return new String(readFully(is), StandardCharsets.UTF_8);
}
public static byte[] readFully(final InputStream is)
public static byte[] readFully(InputStream is)
throws IOException {
final var bos = new ByteArrayOutputStream();
var bos = new ByteArrayOutputStream();
try (bos) {
final byte[] buff = new byte[BUFFER_SIZE];
byte[] buff = new byte[BUFFER_SIZE];
int len;
while ((len = is.read(buff)) > 0)
bos.write(buff, 0, len);
@@ -63,7 +36,7 @@ public class IOUtils {
}
}
public static byte[] readFully(final File in)
public static byte[] readFully(File in)
throws IOException {
return readFully(new FileInputStream(in));
}
@@ -80,25 +53,25 @@ public class IOUtils {
fos.close();
}
public static void copy(final InputStream is, final OutputStream os)
public static void copy(InputStream is, OutputStream os)
throws IOException {
final byte[] buff = new byte[BUFFER_SIZE];
byte[] buff = new byte[BUFFER_SIZE];
int len;
while ((len = is.read(buff)) > 0)
os.write(buff, 0, len);
}
public static void copy(final byte[] buffer, final File out)
public static void copy(byte[] buffer, File out)
throws IOException {
copy(new ByteArrayInputStream(buffer), new FileOutputStream(out));
}
public static void copy(final InputStream is, final File out)
public static void copy(InputStream is, File out)
throws IOException {
copy(is, new FileOutputStream(out));
}
public static void copy(final File in, final File out)
public static void copy(File in, File out)
throws IOException {
copy(new FileInputStream(in), new FileOutputStream(out));
}
@@ -119,7 +92,7 @@ public class IOUtils {
}
public static long getDirSize(File dir) {
final var arr = dir.listFiles();
var arr = dir.listFiles();
if (arr == null || arr.length == 0)
return 0;
@@ -242,13 +242,13 @@ public class NewsFeedFiltersUtils {
return false;
}
if (checkCopyright(post) && !isWhitelistedFilters(post)) {
if (checkCopyright(post)) {
if (dev())
Log.d("NewsfeedAdBlockV2", "Removed post " + post.optInt("post_id") + " from discover, Reason: copyright filters");
return false;
}
if (checkCaption(post) && !isWhitelistedFilters(post)) {
if (checkCaption(post)) {
if (dev())
Log.d("NewsfeedAdBlockV2", "Removed post " + post.optInt("post_id") + " from discover, Reason: caption filters");
return false;
@@ -448,13 +448,13 @@ public class NewsFeedFiltersUtils {
continue;
}
if (checkCopyright(list) && !isWhitelistedFilters(list)) {
if (checkCopyright(list)) {
if (dev())
Log.d("NewsfeedAdBlockV2", "Removed post " + list.optInt("post_id") + " from feed, Reason: copyright filters");
continue;
}
if (checkCaption(list) && !isWhitelistedFilters(list)) {
if (checkCaption(list)) {
if (dev())
Log.d("NewsfeedAdBlockV2", "Removed post " + list.optInt("post_id") + " from feed, Reason: caption filters");
continue;
@@ -22,7 +22,7 @@ public class OTAUtils {
this.mListener = listener;
}
public void loadData() {
public void loadData(boolean isManualCheck) {
Request release = new Request.a()
.b(LATEST_RELEASE_URL)
.a();
@@ -31,13 +31,13 @@ public class OTAUtils {
@Override
public void a(Call call, IOException e) {
Log.d("OTAHelper", "Error while getting latest release info: " + e);
mListener.onUpdateCanceled();
mListener.onUpdateError();
}
@Override
public void a(Call call, Response response) throws IOException {
try {
setData(response);
setData(response, isManualCheck);
} catch (JSONException | NullPointerException e) {
e.printStackTrace();
}
@@ -46,7 +46,7 @@ public class OTAUtils {
});
}
void setData(Response response) throws IOException, JSONException {
void setData(Response response, boolean isManualCheck) throws IOException, JSONException {
mReleaseJson = new JSONObject(response.a().g());
String tag = mReleaseJson.getString("tag_name");
@@ -58,7 +58,7 @@ public class OTAUtils {
@Override
public void a(Call call, IOException e) {
Log.d("OTAHelper", "Error while getting latest commit info: " + e);
mListener.onUpdateCanceled();
mListener.onUpdateError();
}
@Override
@@ -67,12 +67,12 @@ public class OTAUtils {
mCommitJson = new JSONObject(response.a().g());
mCommitSHA = mCommitJson.getJSONObject("object").getString("sha");
if (isNewVersion())
mListener.onUpdateApplied();
mListener.onUpdateApplied(isManualCheck);
else
mListener.onUpdateCanceled();
mListener.onUpdateLatest(isManualCheck);
} catch (NullPointerException | JSONException e) {
e.printStackTrace();
mListener.onUpdateCanceled();
mListener.onUpdateError();
}
}
});
@@ -118,9 +118,10 @@ public class OTAUtils {
public interface OTAListener {
void onUpdateApplied();
void onUpdateApplied(boolean isManualCheck);
void onUpdateCanceled();
void onUpdateError();
void onUpdateLatest(boolean isManualCheck);
}
}
@@ -19,8 +19,8 @@ public class SuperAppUtils {
}
public static JSONObject superapp(JSONObject json) throws JSONException {
var superApps = Preferences.getPreferences().getString("superapp_items",
"menu,miniapps,vkpay_slim,greeting,promo,holiday,weather,sport,games,informer,food,event,music,vk_run").split(",");
var superApps = Preferences.getPreferences().getString("superapp_items", "menu,miniapps,vkpay_slim,greeting,promo,holiday,weather,sport,games,informer,food,event,music,vk_run").split(",");
if (superApps.length == 0) return json;
var oldItems = json.optJSONArray("items");
@@ -31,7 +31,24 @@ public class SuperAppUtils {
var type = item.optString("type");
for (String superApp : superApps) {
if (type.equals(superApp))
newItems.put(item);
if (superApp.equals("menu")) { // superapp menu fix
String menuitems = "groups,audios,stickers,podcasts,shopping,lives,videos,games,vk_calls,vk_pay,ads_easy_promote,classifieds,events";
JSONObject menuItem = new JSONObject();
JSONArray menuItems = new JSONArray();
generateMenuItems(menuitems, menuItems);
JSONObject object = new JSONObject();
object.put("count", menuitems.split(",").length);
object.put("items", menuItems);
menuItem.put("type", "menu");
menuItem.put("object", object);
newItems.put(menuItem);
} else {
newItems.put(item);
}
}
}
for (int i = 0; i < superApps.length; i++) {
@@ -46,4 +63,16 @@ public class SuperAppUtils {
return json.putOpt("items", newItems);
}
private static void generateMenuItems(String str, JSONArray jsonArray) throws JSONException {
String[] strings = str.split(",");
for (String string : strings) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", string);
jsonArray.put(jsonObject);
}
}
}
@@ -7,7 +7,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import com.vk.usersstore.contentprovider.a.UsersDbHelper;
import com.vtosters.lite.auth.VKAuth;
@@ -17,6 +16,9 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import static android.os.Environment.DIRECTORY_DOWNLOADS;
import static android.os.Environment.getExternalStoragePublicDirectory;
public class VKAccountDB {
private static SQLiteOpenHelper getDatabase() {
return new UsersDbHelper(AndroidUtils.getGlobalContext());
@@ -70,11 +72,11 @@ public class VKAccountDB {
var db = getDatabase().getReadableDatabase();
var dateFormat = new SimpleDateFormat("yyyy-MM-dd.HH-mm-ss", Locale.getDefault());
var file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/VTLBackups/saved_accounts_" + dateFormat.format(new Date()) + ".vtl");
var dir = new File(getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS), "/VTLBackups/");
var file = new File(dir, "saved_accounts_" + dateFormat.format(new Date()) + ".vtl");
InputStream inputStream;
try {
inputStream = new FileInputStream(db.getPath());
try (InputStream inputStream = new FileInputStream(db.getPath())) {
dir.mkdirs();
FileOutputStream outputStream = new FileOutputStream(file);
byte[] buff = new byte[1024];
@@ -86,7 +88,6 @@ public class VKAccountDB {
AndroidUtils.sendToast(AndroidUtils.getString("accounts_saved_by_path") + " " + file.getPath());
} catch (IOException e) {
e.printStackTrace();
AndroidUtils.sendToast(AndroidUtils.getString("error_saving_file"));
}
}
@@ -12,10 +12,8 @@ import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
import static ru.vtosters.hooks.other.Preferences.hasVerification;
public class VTVerifications {
public static final List<Integer> sVerifications = new ArrayList<>();
@@ -33,7 +31,7 @@ public class VTVerifications {
return;
}
if ((!NetworkUtils.isNetworkConnected() && NetworkUtils.isInternetSlow() || getBoolValue("isRoamingState", false)) && prefs.contains("ids")) {
if ((!NetworkUtils.isNetworkConnected() && NetworkUtils.isInternetSlow() || getBoolValue("isRoamingState", false)) && prefs.contains("ids") || getBoolValue("serverFeaturesDisable", false)) {
parseJson(prefs.getString("ids", "[]"));
Log.d("VTVerifications", "load from memory. Roaming or Network issues");
isLoaded = true;
@@ -56,8 +54,7 @@ public class VTVerifications {
.apply();
isLoaded = true;
Log.d("VTVerifications", "load from network");
} catch (Exception e) {
e.printStackTrace();
} catch (Exception ignored) {
if (prefs.contains("ids")) {
parseJson(prefs.getString("ids", "[]"));
Log.d("VTVerifications", "load from memory. Something went wrong with parsing");
@@ -92,7 +89,7 @@ public class VTVerifications {
processIds(json.optJSONArray("404"), sDevelopers);
processIds(json.optJSONArray("1337"), sServiceAccounts);
} catch (JSONException e) {
e.printStackTrace();
// ignored
}
}
@@ -100,8 +97,13 @@ public class VTVerifications {
if (jsonIds == null || jsonIds.length() == 0)
return;
for (int i = 0; i < jsonIds.length(); i++)
member.add(jsonIds.optInt(i));
for (int i = 0; i < jsonIds.length(); i++) {
try {
member.add(jsonIds.optInt(i));
} catch (Exception ignored) {
// ignored
}
}
}
public static boolean isPrometheus(int id) {
@@ -125,7 +127,7 @@ public class VTVerifications {
return -id;
}
public static boolean haveDonateButton() {
return hasVerification() || new Random().nextInt(6) != 1;
}
// public static boolean haveDonateButton() {
// return hasVerification() || new Random().nextInt(6) != 1;
// }
}
@@ -0,0 +1,42 @@
package ru.vtosters.lite.utils.music;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
import java.io.File;
public class MusicCacheStorageUtils {
public static File getCacheStorageDir() {
return AndroidUtils.getGlobalContext().getExternalFilesDir("vt_tracks");
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static File getTrackDirById(final String trackId) {
final var res = new File(getCacheStorageDir(), trackId);
res.mkdirs();
return res;
}
public static File getTrackFile(final String trackId) {
return new File(getTrackDirById(trackId), "track.mp3");
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static File getThumbDirById(final String trackId) {
final var res = new File(getTrackDirById(trackId), "thumbs");
res.mkdirs();
return res;
}
public static File getTrackThumb(final String trackId, final int factor) {
return new File(getThumbDirById(trackId), "thumb_" + factor + ".png");
}
public static void removeTrackDirById(final String trackId) {
IOUtils.deleteRecursive(getTrackDirById(trackId));
}
public static void clear() {
IOUtils.deleteRecursive(getCacheStorageDir());
}
}
@@ -0,0 +1,47 @@
package ru.vtosters.lite.utils.music;
import android.os.RemoteException;
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
public class VKXUtils {
public static boolean isVkxCached(int playlist_id, int owner_id) {
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
@Override
public Boolean run(ILibVkxService service) {
try {
return service.isPlaylistCached(playlist_id, owner_id);
} catch (RemoteException e) {
return false;
}
}
@Override
public Boolean defaultValue() {
return false;
}
});
}
public static boolean isVkxCached(String trackId) {
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
@Override
public Boolean run(ILibVkxService service) {
try {
var splits = trackId.split("_");
var id = Integer.parseInt(splits[1]);
var owner = Integer.parseInt(splits[0]);
return service.isTrackCached(id, owner);
} catch (RemoteException e) {
return false;
}
}
@Override
public Boolean defaultValue() {
return false;
}
});
}
}
+9
View File
@@ -138,6 +138,7 @@
<uses-permission android:name="me.everything.badger.permission.BADGE_COUNT_WRITE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.mgoogle.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="app.revanced.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
@@ -2313,6 +2314,14 @@
<action android:name="com.mgoogle.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
<receiver
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver3"
android:exported="true"
android:permission="app.revanced.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="app.revanced.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
<activity
android:name="com.google.android.gms.common.api.GoogleApiActivity"
android:exported="false"

Some files were not shown because too many files have changed in this diff Show More