Compare commits

...
Author SHA1 Message Date
Erik Kiznov 3d4be416d8 mediacontent 2022-09-22 23:12:07 +05:00
gdlbo 11d12fc397 fix caching 2022-09-22 21:01:26 +03:00
gdlbo caf70b72b5 fix compiling 2022-09-22 20:09:05 +03:00
gdlbo f6d860c439 Merge remote-tracking branch 'origin/music'
# Conflicts:
#	app/src/main/java/ru/vtosters/lite/ui/fragments/MediaFragment.java
#	app/src/main/java/ru/vtosters/lite/utils/ContactsUtils.java
#	app/src/main/java/ru/vtosters/lite/utils/ThemesUtils.java
#	smali/res/values-ru/strings.xml
#	smali/res/values/strings.xml
2022-09-22 19:51:52 +03:00
gdlbo 7d99f06053 Update build.gradle 2022-09-22 19:47:05 +03:00
Erik Kiznov b4e260d7cb [music] translate app error string 2022-09-21 23:50:32 +05:00
Erik Kiznov 46281102cb fix get current activity (maybe) 2022-09-21 23:35:44 +05:00
gdlbo b237ec034a some null checks 2022-09-21 21:13:22 +03:00
Erik Kiznov bfae3b5a7d [music] translate strings 2022-09-21 23:05:01 +05:00
gdlbo 5311aa0fb9 speedup db cache cleaning + some imp 2022-09-21 20:50:02 +03:00
gdlbo 2dd61e4cce deleted useless buttons in mbsh 2022-09-21 20:36:34 +03:00
gdlbo 63b97d280b vkx playlist caching 2022-09-20 20:25:23 +03:00
aliveoutside 529d4afb3b [music] download mp3 via VK X if integration is enabled 2022-09-20 19:50:23 +03:00
aliveoutside d2de67bcc3 [music] support subtitles 2022-09-20 19:03:06 +03:00
aliveoutside d02d6ceac1 [music] use callback onfailure 2022-09-20 19:03:06 +03:00
gdlbo 4ac4ae03f1 Update MusicNotificationBuilder.java 2022-09-19 22:48:35 +03:00
gdlbo 4534ed1156 caching/downloading own music (without playlists) 2022-09-19 22:14:55 +03:00
aliveoutside c18bdb3abb [music] playlist caching 2022-09-19 20:11:43 +03:00
inotflying fb524186d1 im_ic_attach → im_ic_add_circle 2022-09-19 17:27:58 +03:00
aliveoutside 827f10392f [music] use executorservice in download playlist hook
so it won't freeze the ui
2022-09-19 15:31:40 +03:00
aliveoutside 105f2c76c0 [music] support playlists with more than 1000 tracks 2022-09-19 14:56:56 +03:00
aliveoutside 2e6617ec83 [music] [PlaylistConverter] add track to playlist only if it has a vaild download url 2022-09-19 13:58:23 +03:00
gdlbo 8806f36182 ios writebar lol 2022-09-18 22:09:48 +03:00
aliveoutside 07ca91d8a2 [music] move notifySavingToCache to MusicNotificationBuilder 2022-09-18 18:59:19 +03:00
aliveoutside 4443f69056 [music] move callback and notification builders to their own packages 2022-09-18 18:59:19 +03:00
gdlbo 4cc0b04b68 Delete VKListHelper.java 2022-09-18 18:55:12 +03:00
gdlbo 89f2e7cf51 Update MusicBottomSheetHook.java 2022-09-18 18:42:12 +03:00
aliveoutside 8b36fea7a5 [music] fix track caching 2022-09-18 18:35:48 +03:00
aliveoutside e5848f2f91 [music] remove hardcoded track name in M3UDownloader 2022-09-18 18:29:20 +03:00
Erik Kiznov 5448b2f27e [music] translate notification text 2022-09-18 19:48:25 +05:00
Erik Kiznov 253b3bdcb9 [main] translate video history error
this commit is already in c3a17ca4ca (music branch)
2022-09-18 13:57:24 +05:00
Erik Kiznov c3a17ca4ca [music] translate all strings 2022-09-18 13:54:55 +05:00
Erik Kiznov 4cf7422997 [main] translate all strings 2022-09-18 13:05:18 +05:00
Erik Kiznov 270fca127b [main] translate all strings 2022-09-18 13:00:15 +05:00
aliveoutside 8b614f786d 100% fix crash on start 2022-09-18 00:54:22 +03:00
Vologhat 415d3fb3a7 added thumbnails support for music cache 2022-09-18 00:35:49 +03:00
Vologhat 593086d3b1 fixed ffmpeg convertation 2022-09-17 21:53:38 +03:00
aliveoutside f3fda343d2 potentially fix crash on start related to system theme changer 2022-09-17 21:49:33 +03:00
gdlbo 6633eb8f60 some caching stuff 2022-09-17 20:51:40 +03:00
gdlbo 65d66007e0 auto caching 2022-09-17 20:24:18 +03:00
aliveoutside 9763f54bf5 [music] use double quotes instead of single quotes in ffmpeg 2022-09-17 20:23:46 +03:00
aliveoutside 6be6b5c714 [music] fix after merge 2022-09-17 20:23:46 +03:00
aliveoutside f7c4663329 [music] move notification callback outside of PlaylistDownloader 2022-09-17 20:23:45 +03:00
gdlbo 03c19bc9cf delete from cache 2022-09-17 20:09:32 +03:00
gdlbo c27410d359 caching.... 2022-09-17 20:03:27 +03:00
gdlbo 00dda2141b cache lol 2022-09-17 20:03:14 +03:00
aliveoutside 68c3a1d137 [music] make PlaylistDownloader more generic 2022-09-17 19:25:16 +03:00
aliveoutside e60ab4a54e [music] some docs for TrackDownloader 2022-09-17 19:25:04 +03:00
gdlbo d9d57428ce cache files impl 2022-09-17 18:45:39 +03:00
aliveoutside 78d30f9546 [music] The refactoring
one class should only do a one thing and do it great
2022-09-17 18:45:22 +03:00
gdlbo db1b95b15a disabled cache file path 2022-09-17 18:21:31 +03:00
gdlbo d27c191800 Fixed unpl songs 2022-09-17 17:00:43 +03:00
gdlbo 682d9bdef1 empty cache 2022-09-17 16:43:57 +03:00
gdlbo 6f3565a4e3 Update MusicBottomSheetHook.java 2022-09-17 16:10:20 +03:00
gdlbo a7e70dabe5 fix vkx caching 2022-09-17 16:06:27 +03:00
gdlbo d602766fc4 Merge remote-tracking branch 'origin/main' into music 2022-09-17 15:14:05 +03:00
gdlbo 5c95ae3dd0 fixCTX 2022-09-17 15:13:40 +03:00
gdlbo 0e354123fc new cache btn 2022-09-17 14:54:58 +03:00
gdlbo db51fb01c0 added caching vkx music from app 2022-09-17 01:53:52 +03:00
gdlbo cfdf283457 fixes 2022-09-17 01:25:58 +03:00
gdlbo 724e8b9308 some buttons inject 2022-09-17 01:06:19 +03:00
iTaysonLab 46a05c916c add separate playlist support 2022-09-16 22:37:17 +02:00
iTaysonLab c814a1b384 logs 2022-09-16 22:04:00 +02:00
aliveoutside d1eed0fbd5 do not reapply theme on every config change 2022-09-16 23:01:31 +03:00
iTaysonLab 6faa7dbb74 Merge branch 'music' of https://github.com/vtosters/lite into music 2022-09-16 21:50:22 +02:00
iTaysonLab 57e8c32629 Implement libvkx playlist injecting 2022-09-16 21:49:24 +02:00
aliveoutside 9b6425442a refactor converter code a bit 2022-09-16 21:50:01 +03:00
gdlbo 025f582f6a getTracksInPlaylist 2022-09-16 21:40:50 +03:00
gdlbo 2dec7b28f4 vkx lib update + vkx cache dev 2022-09-16 20:16:20 +03:00
gdlbo 30886adc62 fix strange crash 2022-09-16 18:59:48 +03:00
gdlbo 761d32dab6 logs 2022-09-16 17:08:35 +03:00
gdlbo 7dbebc1cd1 Update PlaylistHelper.java 2022-09-16 16:43:04 +03:00
gdlbo dd6c793979 Merge remote-tracking branch 'origin/main' into music 2022-09-16 16:11:04 +03:00
aliveoutside c441823d94 use new catalog for artist search
fixes #745
2022-09-16 15:45:39 +03:00
aliveoutside 090c82dd40 append id3 tags to downloaded tracks 2022-09-16 13:03:56 +03:00
aliveoutside a7a5a3cd4c add coreLibraryDesugaring so we can use some cool java apis
https://developer.android.com/studio/write/java8-support#library-desugaring
2022-09-16 13:03:40 +03:00
aliveoutside 0e36807bd6 Merge branch 'main' into music 2022-09-16 00:46:15 +03:00
aliveoutside 24c41e5a6d bypass android notification updates rate limits 2022-09-16 00:37:08 +03:00
aliveoutside 22e12b6422 close streams in IOUtils 2022-09-16 00:13:09 +03:00
gdlbo 0efdefb207 ThemeChanging meme 2022-09-15 23:57:20 +03:00
aliveoutside 80d2045fbb update build.gradle 2022-09-15 23:51:18 +03:00
aliveoutside 7bb0d69a96 parallel .ts downloading 2022-09-15 23:43:42 +03:00
aliveoutside 96d71394fd merge .ts files more properly
use copying instead of recoding merged .ts to mp3

fixes #688
fixes #630
2022-09-15 23:41:05 +03:00
aliveoutside 371f9ae4a6 what the fuck? @Vologhat 2022-09-15 22:51:25 +03:00
gdlbo b439165246 fixed icon + vkx playlists playing 2022-09-15 21:35:50 +03:00
gdlbo 07eefdcea9 playlist bs inj + fix getTrackFile 2022-09-15 21:02:38 +03:00
aliveoutside 9d8533e066 fix external video player 2022-09-15 20:30:27 +03:00
gdlbo 37f39f29ab Merge remote-tracking branch 'origin/main' into music 2022-09-15 19:47:59 +03:00
gdlbo 84d2d583a3 fix crash for applying theme 2022-09-15 19:46:27 +03:00
aliveoutside f2c73c6ede [music] speed up downloading like 100x times 2022-09-15 19:40:16 +03:00
aliveoutside 59e92e30f2 [music] merge .ts files with Java:
using Java NIO on android O+
using FileOutputStream on older androids
2022-09-15 19:19:46 +03:00
aliveoutside be1bc8efbb remove gridmedia from recommendations 2022-09-15 18:58:53 +03:00
gdlbo ac0206f5ab Merge branch 'main' into music
# Conflicts:
#	app/src/main/java/ru/vtosters/lite/music/MP3Downloader.java
2022-09-15 18:42:31 +03:00
gdlbo e576fa0be5 powersave flex 2022-09-15 18:05:31 +03:00
aliveoutside 9fc5aa4fc3 Merge branch 'vtl-remaster' into music
# Conflicts:
#	app/libs/classes-dex2jar.jar
#	app/libs/classes2-dex2jar.jar
#	app/libs/classes3-dex2jar.jar
#	app/libs/classes4-dex2jar.jar
#	app/libs/classes5-dex2jar.jar
#	app/src/main/java/ru/vtosters/lite/downloaders/notifications/NotificationChannels.java
#	app/src/main/java/ru/vtosters/lite/music/MP3Downloader.java
2022-08-19 13:54:46 +03:00
Vologhat f6555f49d8 beta stuff for music 2022-07-31 22:14:49 +03:00
94 changed files with 3233 additions and 833 deletions
+1 -1
View File
@@ -59,7 +59,7 @@
* Возможность отключить рекомендации от ВК
* Редактор докбара
* Редактор элементов Superapp
* Медиа контент
* Медиаконтент
* Округление дат
* 1 января в 4:20
* 1 января в 4:20:00
+4 -2
View File
@@ -77,10 +77,11 @@ android {
targetSdkVersion 32
versionCode 1
versionName "1.0"
multiDexEnabled false
multiDexEnabled true
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
@@ -88,8 +89,9 @@ android {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation 'androidx.preference:preference:1.2.0'
implementation fileTree(dir: 'libs', include: ['*.aar'])
compileOnly fileTree(dir: 'libs', include: ['*.jar'])
@@ -0,0 +1,33 @@
// IVKExternalMusic.aidl
package bruhcollective.itaysonlab.libvkx;
interface ILibVkxService {
// Starts playback. playbackContext can be "other" if you don't sure what it is
void play(in List<String> audio_ids, in int index, in String playbackContext) = 1;
// A UserID. CHECK FOR THIS TO BE EQUAL BEFORE SENDING ANYTHING TO SERVICE!
int getUserId() = 2;
// Cache add/remove
void addTrackToCache(in int track_id, in int owner_id, in String access_key) = 3;
void deleteTrackFromCache(in int track_id, in int owner_id) = 4;
boolean isTrackCached(in int track_id, in int owner_id) = 5;
// Get a list of cached tracks. The response is close to VK's original response (an audio list)
// Also, the items in the list are JSON ones.
List<String> getCache() = 6;
// [ver 1.1]
void downloadTrack(in int track_id, in int owner_id, in String access_key) = 7;
void downloadPlaylist(in int playlist_id, in int owner_id, in String access_key) = 8;
void addPlaylistToCache(in int playlist_id, in int owner_id, in String access_key) = 9;
void deletePlaylistFromCache(in int playlist_id, in int owner_id) = 10;
// [ver 1.2]
String getCacheCatalog() = 11;
List<String> getTracksInPlaylist(in String playlist_id, in String owner_id) = 12;
String getPlaylistDefJson(in String playlist_id, in String owner_id) = 13;
// [ver 1.3]
boolean isPlaylistCached(in int playlist_id, in int owner_id) = 14;
}
@@ -1,340 +0,0 @@
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package bruhcollective.itaysonlab.libvkx;
// Works only with original signature of VT
public interface ILibVkxService extends android.os.IInterface {
void play(java.util.List<String> audio_ids, int index, String playbackContext) throws android.os.RemoteException;
int getUserId() throws android.os.RemoteException;
// Starts playback. playbackContext can be "other" if you don't sure what it is
void addTrackToCache(int track_id, int owner_id, String access_key) throws android.os.RemoteException;
// A UserID. CHECK FOR THIS TO BE EQUAL BEFORE SENDING ANYTHING TO SERVICE!
void deleteTrackFromCache(int track_id, int owner_id) throws android.os.RemoteException;
// Cache add/remove
boolean isTrackCached(int track_id, int owner_id) throws android.os.RemoteException;
java.util.List<String> getCache() throws android.os.RemoteException;
/**
* Default implementation for ILibVkxService.
*/
class Default implements ILibVkxService {
// Starts playback. playbackContext can be "other" if you don't sure what it is
@Override
public void play(java.util.List<String> audio_ids, int index, String playbackContext) throws android.os.RemoteException {
}
// A UserID. CHECK FOR THIS TO BE EQUAL BEFORE SENDING ANYTHING TO SERVICE!
@Override
public int getUserId() throws android.os.RemoteException {
return 0;
}
// Cache add/remove
@Override
public void addTrackToCache(int track_id, int owner_id, String access_key) throws android.os.RemoteException {
}
@Override
public void deleteTrackFromCache(int track_id, int owner_id) throws android.os.RemoteException {
}
@Override
public boolean isTrackCached(int track_id, int owner_id) throws android.os.RemoteException {
return false;
}
// Get a list of cached tracks. The response is close to VK's original response (an audio list)
// Also, the items in the list are JSON ones.
@Override
public java.util.List<String> getCache() throws android.os.RemoteException {
return null;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
// Get a list of cached tracks. The response is close to VK's original response (an audio list)
// Also, the items in the list are JSON ones.
/**
* Local-side IPC implementation stub class.
*/
abstract class Stub extends android.os.Binder implements ILibVkxService {
static final int TRANSACTION_play = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_getUserId = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_addTrackToCache = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
static final int TRANSACTION_deleteTrackFromCache = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
static final int TRANSACTION_isTrackCached = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5);
static final int TRANSACTION_getCache = (android.os.IBinder.FIRST_CALL_TRANSACTION + 6);
private static final String DESCRIPTOR = "bruhcollective.itaysonlab.libvkx.ILibVkxService";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an bruhcollective.itaysonlab.libvkx.ILibVkxService interface,
* generating a proxy if needed.
*/
public static ILibVkxService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof ILibVkxService))) {
return ((ILibVkxService) iin);
}
return new Proxy(obj);
}
public static boolean setDefaultImpl(ILibVkxService impl) {
if (Proxy.sDefaultImpl == null && impl != null) {
Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static ILibVkxService getDefaultImpl() {
return Proxy.sDefaultImpl;
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_play: {
data.enforceInterface(descriptor);
java.util.List<String> _arg0;
_arg0 = data.createStringArrayList();
int _arg1;
_arg1 = data.readInt();
String _arg2;
_arg2 = data.readString();
this.play(_arg0, _arg1, _arg2);
reply.writeNoException();
return true;
}
case TRANSACTION_getUserId: {
data.enforceInterface(descriptor);
int _result = this.getUserId();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case TRANSACTION_addTrackToCache: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
String _arg2;
_arg2 = data.readString();
this.addTrackToCache(_arg0, _arg1, _arg2);
reply.writeNoException();
return true;
}
case TRANSACTION_deleteTrackFromCache: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
this.deleteTrackFromCache(_arg0, _arg1);
reply.writeNoException();
return true;
}
case TRANSACTION_isTrackCached: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
boolean _result = this.isTrackCached(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(((_result) ? (1) : (0)));
return true;
}
case TRANSACTION_getCache: {
data.enforceInterface(descriptor);
java.util.List<String> _result = this.getCache();
reply.writeNoException();
reply.writeStringList(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements ILibVkxService {
public static ILibVkxService sDefaultImpl;
private final android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
// Starts playback. playbackContext can be "other" if you don't sure what it is
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
// A UserID. CHECK FOR THIS TO BE EQUAL BEFORE SENDING ANYTHING TO SERVICE!
@Override
public void play(java.util.List<String> audio_ids, int index, String playbackContext) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStringList(audio_ids);
_data.writeInt(index);
_data.writeString(playbackContext);
boolean _status = mRemote.transact(Stub.TRANSACTION_play, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().play(audio_ids, index, playbackContext);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
// Cache add/remove
@Override
public int getUserId() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getUserId, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getUserId();
}
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addTrackToCache(int track_id, int owner_id, String access_key) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(track_id);
_data.writeInt(owner_id);
_data.writeString(access_key);
boolean _status = mRemote.transact(Stub.TRANSACTION_addTrackToCache, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addTrackToCache(track_id, owner_id, access_key);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void deleteTrackFromCache(int track_id, int owner_id) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(track_id);
_data.writeInt(owner_id);
boolean _status = mRemote.transact(Stub.TRANSACTION_deleteTrackFromCache, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().deleteTrackFromCache(track_id, owner_id);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
// Get a list of cached tracks. The response is close to VK's original response (an audio list)
// Also, the items in the list are JSON ones.
@Override
public boolean isTrackCached(int track_id, int owner_id) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(track_id);
_data.writeInt(owner_id);
boolean _status = mRemote.transact(Stub.TRANSACTION_isTrackCached, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().isTrackCached(track_id, owner_id);
}
_reply.readException();
_result = (0 != _reply.readInt());
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public java.util.List<String> getCache() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<String> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getCache, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getCache();
}
_reply.readException();
_result = _reply.createStringArrayList();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
}
}
@@ -81,11 +81,11 @@ public class LibVKXClient {
lambdaplay(list, musicTrack, iLibVkxService, null);
}
private static String asId(MusicTrack musicTrack) {
String str = musicTrack.e + "_" + musicTrack.d;
if (musicTrack.O == null || musicTrack.O.equals("")) {
return str;
public static String asId(MusicTrack musicTrack) {
String id = musicTrack.e + "_" + musicTrack.d;
if (musicTrack.O != null && !musicTrack.O.equals("")) {
id += "_" + musicTrack.O;
}
return str + "_" + musicTrack.O;
return id;
}
}
@@ -1,121 +1,136 @@
package ru.vtosters.lite.downloaders;
import android.os.Environment;
import android.util.Log;
import static ru.vtosters.lite.music.cache.FileCacheImplementation.getTrackFolder;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import android.os.Environment;
import com.vk.core.util.ToastUtils;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.music.Callback;
import ru.vtosters.lite.music.FFMpeg;
import ru.vtosters.lite.music.M3UDownloader;
import ru.vtosters.lite.music.MP3Downloader;
import ru.vtosters.lite.utils.AndroidUtils;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.FileCacheImplementation;
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
import ru.vtosters.lite.music.downloader.AudioGet;
import ru.vtosters.lite.music.downloader.PlaylistDownloader;
import ru.vtosters.lite.music.downloader.TrackDownloader;
import ru.vtosters.lite.music.notification.MusicNotificationBuilder;
import ru.vtosters.lite.utils.IOUtils;
/**
* Entrypoint for downloading audio
* <p>
* The actual code is in {@link ru.vtosters.lite.music}
*/
public class AudioDownloader {
private static NotificationManagerCompat notificationManager = NotificationManagerCompat.from(AndroidUtils.getGlobalContext());
private static final Executor executor = Executors.newFixedThreadPool(4);
public static void downloadAudio(MusicTrack track) {
if (AndroidUtils.getDefaultPrefs().getBoolean("new_music_downloading_way", false)) {
downloadMP3(track);
} else {
downloadM3U8(track);
}
public static void downloadPlaylist(Playlist playlist) {
var tracks = PlaylistConverter.getPlaylist(playlist);
var playlistName = IOUtils.getValidFileName(playlist.g);
var musicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath();
var downloadPath = musicPath + File.separator + playlistName;
var notificationId = playlistName.hashCode();
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId);
PlaylistDownloader.downloadPlaylist(
tracks,
IOUtils.getValidFileName(playlist.g),
downloadPath,
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
);
}
public static void downloadM3U8(MusicTrack track) {
public static void downloadAudio(MusicTrack track) {
downloadM3U8(track, false);
}
public static void cacheTrack(MusicTrack track) {
var trackId = track.y1();
if (CacheDatabaseDelegate.isCached(trackId)) {
CacheDatabaseDelegate.removeTrackFromCache(LibVKXClient.asId(track));
return;
}
var trackFile = FileCacheImplementation.getTrackFile(trackId);
if (!trackFile.exists())
trackFile.getParentFile().mkdirs();
downloadM3U8(track, true);
MusicNotificationBuilder.notifySavingToCache(track);
CacheDatabaseDelegate.insertTrack(track);
}
public static void cachePlaylist(Playlist playlist) {
var tracks = PlaylistConverter.getPlaylist(playlist);
var notificationId = playlist.g.hashCode();
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId);
PlaylistDownloader.cachePlaylist(
tracks,
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
);
}
public static void cacheAllAudios() {
var tracks = AudioGet.getAudios();
var notificationId = getUserId();
var notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
PlaylistDownloader.cachePlaylist(
tracks,
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
);
}
public static void downloadAllAudios() {
var tracks = AudioGet.getAudios();
var notificationId = getUserId();
var notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
var playlistName = "Audios of " + getUserId();
var musicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath();
var downloadPath = musicPath + File.separator + playlistName;
PlaylistDownloader.downloadPlaylist(
tracks,
playlistName,
downloadPath,
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
);
}
private static void downloadM3U8(MusicTrack track, boolean cache) {
if (track.D == null) {
ToastUtils.a(AndroidUtils.getString("link_audio_error"));
ToastUtils.a(getString("link_audio_error"));
return;
}
var musicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath();
var musicDir = new File(musicPath);
if (!musicDir.exists()) {
if (musicDir.mkdirs()) {
Log.d("AudioDownloader", "Created music dir");
} else {
Log.e("AudioDownloader", "Failed to create music dir");
return;
}
}
var tempId = track.d;
var downloadPath = musicPath + File.separator + tempId;
var downloadPath = musicPath + File.separator;
var notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId);
var notification = buildDownloadNotification(track, tempId);
downloadM3U8(track, downloadPath, new Callback() {
@Override
public void onProgress(int progress) {
notification.setProgress(100, progress, false);
notificationManager.notify(tempId, notification.build());
}
@Override
public void onSuccess() {
try {
var fileName = track.toString();
var success = FFMpeg.convert(downloadPath, musicPath + File.separator + fileName + ".mp3");
if (success) {
notification
.setContentText(AndroidUtils.getString("player_download_finished"))
.setProgress(0, 0, false)
.setSmallIcon(android.R.drawable.stat_sys_download_done);
notificationManager.notify(tempId, notification.build());
} else {
onFailure();
}
} catch (UnsatisfiedLinkError e) {
Log.e("AudioDownloader", "native libs");
Log.e("AudioDownloader", e.getMessage());
onFailure();
}
}
@Override
public void onFailure() {
notification.setContentText(AndroidUtils.getString("load_audio_error")).setProgress(0, 0, false);
notificationManager.notify(tempId, notification.build());
}
@Override
public void onSizeReceived(long size, long header) {
}
});
}
private static void downloadM3U8(MusicTrack track, String path, Callback callback) {
File outDir = new File(path);
if (!outDir.exists())
if (outDir.mkdir())
Log.v("AudioDownloader", "Directory created");
else
Log.e("AudioDownloader", "Directory creation failed");
Log.i("AudioDownloader", "Downloading audio to " + path);
M3UDownloader.execute(track.D, outDir, callback);
}
private static void downloadMP3(MusicTrack track) {
MP3Downloader.execute(track);
}
private static NotificationCompat.Builder buildDownloadNotification(MusicTrack track, int id) {
var notificationBuilder = new NotificationCompat.Builder(AndroidUtils.getGlobalContext(), NotificationChannels.MUSIC_DOWNLOAD_CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle(AndroidUtils.getString("audio_downloading"))
.setContentText(track.toString())
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.setAutoCancel(true);
notificationBuilder.setProgress(100, 0, false);
notificationManager.notify(id, notificationBuilder.build());
return notificationBuilder;
if (cache) {
TrackDownloader.cacheTrack(track, MusicCallbackBuilder.buildOneTrackCallback(tempId, notification));
} else {
TrackDownloader.downloadTrack(track, downloadPath, MusicCallbackBuilder.buildOneTrackCallback(tempId, notification));
}
}
}
@@ -4,8 +4,10 @@ import static ru.vtosters.lite.net.Request.makeRequest;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import static ru.vtosters.lite.utils.AndroidUtils.getArray;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.AndroidUtils.getResources;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import android.util.SparseArray;
import android.widget.Toast;
@@ -68,7 +70,7 @@ public class MessagesDownloader{
}
fos.write(format.provideDocumentEnd().getBytes(StandardCharsets.UTF_8));
fos.close();
Toast.makeText(getGlobalContext(), "Сохранено как " + out.getAbsolutePath(), Toast.LENGTH_SHORT).show();
Toast.makeText(getGlobalContext(), getString("saved_as") + " " + out.getAbsolutePath(), Toast.LENGTH_SHORT).show();
} catch (IOException | JSONException e) {
e.printStackTrace();
}
@@ -276,7 +278,7 @@ public class MessagesDownloader{
"\n" +
"";
return "<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta charset=\"utf-8\"><title>Экспорт из VTLite</title><style>" + style + "</style></head><body>";
return "<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta charset=\"utf-8\"><title>" + getString("chat_export_title") + "</title><style>" + style + "</style></head><body>";
}
@Override
@@ -286,7 +288,7 @@ public class MessagesDownloader{
@Override
String provideHeader(String dialogName, String date){
return "<div class=\"vtex-milk-header\"><h4 class=\"vtex-milk-header-txt\">VTLite</h4><h5 class=\"vtex-milk-header-sub\">Экспорт истории чата (" + date + ")</h5></div><div class=\"vtex-milk-msgcont\">";
return "<div class=\"vtex-milk-header\"><h4 class=\"vtex-milk-header-txt\">VTLite</h4><h5 class=\"vtex-milk-header-sub\">" + getString("chat_export_header") + " (" + date + ")</h5></div><div class=\"vtex-milk-msgcont\">";
}
@Override
@@ -307,13 +309,13 @@ public class MessagesDownloader{
}
String chatAction = (message.chat_action != null) ? getHtmlForChatAction(message.chat_action) : "";
String attaches = (message.attachments.size() > 0) ? "<p class=\"msg-attaches\">Вложения: " + getHtmlForAttach(message.attachments) + "</p>" : "";
String attaches = (message.attachments.size() > 0) ? "<p class=\"msg-attaches\">" + getString("attachments") + ": " + getHtmlForAttach(message.attachments) + "</p>" : "";
String reply = (message.reply_message != null) ? provideReply(message.reply_message) : "";
String fwdMessages = (message.fwd_messages.size() > 0) ? provideForwardMessages(message.fwd_messages) : "";
String userLink = (user.isGroup ? "club" : "id") + user.id;
return "<div class=\"vtex-milk-msg" + isOut + "\" id=\"" + message.id + "\">" +
"<p class=\"msg-from\">от <a href=\"https://" + VK_DOMAIN + "/" + userLink + "\">" + user.firstName + " " + user.lastName + "</a> <span class=\"msg-from-date\">" + formatTime(message.date) + "</span></p>" + // TODO photo100
"<p class=\"msg-from\">" + getString("from_lower") + " <a href=\"https://" + VK_DOMAIN + "/" + userLink + "\">" + user.firstName + " " + user.lastName + "</a> <span class=\"msg-from-date\">" + formatTime(message.date) + "</span></p>" + // TODO photo100
"<p class=\"msg-body\">" + mentionsReplace(message.text) + "</p>" +
geoPosition +
chatAction +
@@ -336,12 +338,12 @@ public class MessagesDownloader{
if (fwdMessages != null) {
var length = fwdMessages.length();
if (length > 0) text = length + " " + pluralMsg(length);
} else text = "Вложение";
} else text = getString("attachment"); // Вложение
}
return "<a href=\"#" + messageId + "\" class=\"msg-attach-link\">" +
"<div class=\"msg-reply\">" +
"<p class=\"reply-header\">Пересланное сообщение</p>" +
"<p class=\"reply-header\">" + getString("vkim_msg_fwd_single") + "</p>" +
"<p class=\"reply-content\">" + user.firstName + " " + user.lastName + ": " + text + "</p>" +
"</div>" +
"</a>";
@@ -382,7 +384,7 @@ public class MessagesDownloader{
JSONObject reply_message = fwd_message.optJSONObject("reply_message");
var attachesHtml = (attaches.size() > 0) ? "Вложения: " + getHtmlForAttach(attaches) : "";
var attachesHtml = (attaches.size() > 0) ? getString("attachments") + ": " + getHtmlForAttach(attaches) : "";
var replyHtml = (reply_message != null) ? provideReply(reply_message) : "";
var fwdsHtml = (fwdsMsg.size() > 0) ? provideForwardMessages(fwdsMsg) : ""; // recursion XD :>
@@ -405,7 +407,7 @@ public class MessagesDownloader{
}
private String pluralMsg(int count) {
String[] plurals = {"сообщение", "сообщения", "сообщений"};
String[] plurals = getArray("msg_plurals");
var hundredModulo = count % 100;
var tenModulo = count % 10;
@@ -424,22 +426,22 @@ public class MessagesDownloader{
switch(type) {
case "chat_photo_update":
htmlAction.append("Обновил фотографию беседы");
htmlAction.append(getString("chat_export_avatar_update"));
break;
case "chat_photo_remove":
htmlAction.append("Удалил фотографию беседы");
htmlAction.append(getString("chat_export_avatar_remove"));
break;
case "chat_create":
var name = action.optString("text");
htmlAction.append("Создал беседу").append((!name.isEmpty()) ? " с названием \"" + name + "\"" : "");
htmlAction.append(getString("chat_export_chat_created")).append((!name.isEmpty()) ? " \"" + name + "\"" : "");
break;
case "chat_title_update":
var newName = action.optString("text");
htmlAction.append("Сменил название беседы").append((!newName.isEmpty()) ? " на \"" + newName : "\"");
htmlAction.append(getString("chat_export_name_changed")).append((!newName.isEmpty()) ? (" " + getString("chat_export_to") + " \"" + newName + "\"") : "");
break;
case "chat_invite_user":
var newUserId = action.optInt("member_id");
htmlAction.append("Добавил ").append((newUserId > 0) ? "пользователя" : "группу");
htmlAction.append(getString("chat_export_user_added") + " ").append((newUserId > 0) ? getString("chat_export_type_user") : getString("chat_export_type_group"));
if (newUserId != 0) {
var userLink = ((newUserId < 0) ? "club" : "id") + Math.abs(newUserId);
htmlAction.append(" <a href=\"https://" + VK_DOMAIN + "/").append(userLink).append("\" class=\"msg-attach-link\">@").append(userLink).append("</a>");
@@ -447,7 +449,7 @@ public class MessagesDownloader{
break;
case "chat_kick_user":
var removedUserId = action.optInt("member_id");
htmlAction.append("Исключил ").append((removedUserId > 0) ? "пользователя" : "группу");
htmlAction.append(getString("chat_export_user_kicked") + " ").append((removedUserId > 0) ? getString("chat_export_type_user") : getString("chat_export_type_group"));
if (removedUserId != 0) {
var userLink = ((removedUserId < 0) ? "club" : "id") + Math.abs(removedUserId);
htmlAction.append(" <a href=\"https://" + VK_DOMAIN + "/").append(userLink).append("\" class=\"msg-attach-link\">@").append(userLink).append("</a>");
@@ -455,22 +457,22 @@ public class MessagesDownloader{
break;
case "chat_pin_message":
var message = action.optString("message");
htmlAction.append("Закрепил сообщение").append((!message.isEmpty()) ? (" с текстом: \"" + message + "\"") : "");
htmlAction.append(getString("chat_export_msg_pinned")).append((!message.isEmpty()) ? (" \"" + message + "\"") : "");
break;
case "chat_unpin_message":
htmlAction.append("Открепил сообщение");
htmlAction.append(getString("chat_export_msg_unpinned"));
break;
case "chat_invite_user_by_link":
htmlAction.append("Вступил в беседу по ссылке");
htmlAction.append(getString("chat_export_join_by_link"));
break;
case "chat_screenshot":
htmlAction.append("Сделал скриншот беседы");
htmlAction.append(getString("chat_export_chat_screenshot"));
break;
case "custom":
htmlAction.append(action.optString("message"));
break;
default:
htmlAction.append("Действие не поддерживается (").append(type).append(")");
htmlAction.append(getString("vkim_msg_unsupported") + " (").append(type).append(")");
break;
}
@@ -489,40 +491,40 @@ public class MessagesDownloader{
switch(type) { // market_album is not supported
case "photo":
var sizes = item.getJSONArray("sizes");
rs.append("<a href=\"").append(sizes.getJSONObject(sizes.length() - 1).getString("url")).append("\" class=\"msg-attach-link\">фотография</a>");
rs.append("<a href=\"").append(sizes.getJSONObject(sizes.length() - 1).getString("url")).append("\" class=\"msg-attach-link\">").append(getString("chat_export_type_photo")).append("</a>");
break;
case "doc":
rs.append("<a href=\"").append(item.getString("url")).append("\" class=\"msg-attach-link\">документ</a>");
rs.append("<a href=\"").append(item.getString("url")).append("\" class=\"msg-attach-link\">").append(getString("chat_export_type_doc")).append("</a>");
break;
case "link":
rs.append("<a href=\"").append(item.getString("url")).append("\" class=\"msg-attach-link\">ссылка</a>");
rs.append("<a href=\"").append(item.getString("url")).append("\" class=\"msg-attach-link\">").append(getString("chat_export_type_link")).append("</a>");
break;
case "wall":
rs.append("<a href=\"https://" + VK_DOMAIN + "/wall").append(item.optString("to_id", item.optString("owner_id"))).append("_").append(item.getString("id")).append("\" class=\"msg-attach-link\">запись на стене</a>");
rs.append("<a href=\"https://" + VK_DOMAIN + "/wall").append(item.optString("to_id", item.optString("owner_id"))).append("_").append(item.getString("id")).append("\" class=\"msg-attach-link\">").append(getString("chat_export_type_wallpost")).append("</a>");
break;
case "sticker":
rs.append("<img style=\"width:128px;height:128px;\" src=\"").append(item.getJSONArray("images").getJSONObject(1).getString("url")).append("\"/>");
break;
case "audio_message":
rs.append("<a href=\"").append(item.optString("link_mp3")).append("\" class=\"msg-attach-link\">голосовое сообщение</a>");
rs.append("<a href=\"").append(item.optString("link_mp3")).append("\" class=\"msg-attach-link\">").append(getString("chat_export_type_audiomsg")).append("</a>");
break;
case "audio":
rs.append("аудиозапись (").append(item.optString("artist")).append(" — ").append(item.optString("title")).append(")");
rs.append(getString("chat_export_type_audio")).append(" (").append(item.optString("artist")).append(" — ").append(item.optString("title")).append(")");
break;
case "video":
rs.append("видеозапись ( ").append(getVideoHtml(item.optJSONObject("files"))).append(")");
rs.append(getString("chat_export_type_video")).append(" (").append(getVideoHtml(item.optJSONObject("files"))).append(")");
break;
case "wall_reply":
var link = "https://" + VK_DOMAIN + "/wall" + item.optString("owner_id") + "_" + item.optString("post_id") + "?reply=" + item.optString("id");
rs.append("<a class=\"msg-attach-link\" href=\"").append(link).append("\">комментарий на стене</a>");
rs.append("<a class=\"msg-attach-link\" href=\"").append(link).append("\">").append(getString("chat_export_type_wallreply")).append("</a>");
break;
case "story":
var story_link = "https://" + VK_DOMAIN + "/story" + item.optString("owner_id") + "_" + item.optString("id");
rs.append("<a class=\"msg-attach-link\" href=\"").append(story_link).append("\">история</a>");
rs.append("<a class=\"msg-attach-link\" href=\"").append(story_link).append("\">").append(getString("chat_export_type_story")).append("</a>");
break;
case "podcast":
var podcast_link = "https://" + VK_DOMAIN + "/podcast" + item.optString("owner_id") + "_" + item.optString("id");
rs.append("<a class=\"msg-attach-link\" href=\"").append(podcast_link).append("\">подкаст</a>");
rs.append("<a class=\"msg-attach-link\" href=\"").append(podcast_link).append("\">").append(getString("chat_export_type_podcast")).append("</a>");
break;
case "gift":
rs.append("<img src=\"").append(item.optString("thumb_96")).append("\"/>");
@@ -532,25 +534,25 @@ public class MessagesDownloader{
if (app != null) {
var miniapp_link = "https://" + VK_DOMAIN + "/app" + app.optString("id");
rs.append("<a class=\"msg-attach-link\" href=\"").append(miniapp_link).append("\">мини-приложение</a>");
rs.append("<a class=\"msg-attach-link\" href=\"").append(miniapp_link).append("\">").append(getString("chat_export_type_miniapp")).append("</a>");
break;
}
rs.append("ошибка");
rs.append(getString("chat_export_error"));
break;
case "audio_playlist":
var playlist_link = "https://" + VK_DOMAIN + "/music/album/" + item.optString("owner_id") + "_" + item.optString("id") + "_" + item.optString("access_key");
rs.append("<a class=\"msg-attach-link\" href=\"").append(playlist_link).append("\">плейлист (").append(item.optString("title")).append(")</a>");
rs.append("<a class=\"msg-attach-link\" href=\"").append(playlist_link).append("\">").append(getString("chat_export_type_playlist")).append(" (").append(item.optString("title")).append(")</a>");
break;
case "poll":
rs.append((item.optBoolean("anonymous") ? "анонимный" : "публичный")).append(" опрос ").append(getPollHtml(item));
rs.append(getString(item.optBoolean("anonymous") ? "chat_export_poll_anon" : "chat_export_poll_publ")).append(" ").append(getString("chat_export_type_poll")).append(" ").append(getPollHtml(item));
break;
case "market":
var market_item_link = "https://" + VK_DOMAIN + "/market" + item.optString("owner_id") + "_" + item.optString("id");
rs.append("<a class=\"msg-attach-link\" href=\"").append(market_item_link).append("\">товар</a>");
rs.append("<a class=\"msg-attach-link\" href=\"").append(market_item_link).append("\">").append(getString("chat_export_type_market")).append("</a>");
break;
default:
rs.append("не поддерживается (").append(type).append(")");
rs.append(getString("vkim_msg_unsupported")).append(" (").append(type).append(")");
break;
}
@@ -575,13 +577,13 @@ public class MessagesDownloader{
pollHtml.append("<hr><p>").append(jsonObject.optString("text")).append(" · ").append(jsonObject.optString("votes")).append(" (").append(jsonObject.optString("rate")).append("%)</p>");
}
pollHtml.append("<hr><p style=\"text-align:center;\">Проголосовало ").append(poll.optString("votes")).append(" чел.</p>");
pollHtml.append("<hr><p style=\"text-align:center;\">").append(getString("chat_export_poll_voted")).append(" ").append(poll.optString("votes")).append(" ").append(getString("chat_export_users_name")).append("</p>");
return pollHtml.append("</div>").toString();
}
private static String getVideoHtml(JSONObject files) throws JSONException {
if (files == null) return "ссылки отсутствуют";
if (files == null) return getString("chat_export_video_nolinks");
Iterator< ? > keys = files.keys();
StringBuilder videoHtml = new StringBuilder();
@@ -1,5 +1,7 @@
package ru.vtosters.lite.downloaders.notifications;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -12,12 +14,21 @@ import ru.vtosters.lite.utils.AndroidUtils;
public class NotificationChannels {
public static final String MUSIC_DOWNLOAD_CHANNEL_ID = "music_download_channel";
public static final String MUSIC_DOWNLOAD_CHANNEL_NAME = AndroidUtils.getString("music_dl_title");
public static final String MUSIC_DOWNLOAD_CHANNEL_DESCRIPTION = AndroidUtils.getString("music_dl_progress");
public static final String MUSIC_DOWNLOAD_CHANNEL_NAME = getString("music_downloading");
public static final String MUSIC_DOWNLOAD_CHANNEL_DESCRIPTION = getString("music_downloading_progress");
public static final String MUSIC_PLAYLIST_DOWNLOAD_CHANNEL_ID = "music_playlist_download_channel";
public static final String MUSIC_PLAYLIST_DOWNLOAD_CHANNEL_NAME = getString("playlist_downloading");
public static final String MUSIC_PLAYLIST_DOWNLOAD_CHANNEL_DESCRIPTION = getString("playlist_downloading_progress");
public static final String MUSIC_CACHING_NAME = getString("caching_track_or_pic");
private static NotificationManagerCompat notificationManagerCompat;
@RequiresApi(api = Build.VERSION_CODES.O)
public static void createChannels() {
createMusicDownloadChannel();
createMusicPlaylistDownloadChannel();
}
@RequiresApi(api = Build.VERSION_CODES.O)
@@ -29,4 +40,20 @@ public class NotificationChannels {
var notificationManager = NotificationManagerCompat.from(AndroidUtils.getGlobalContext());
notificationManager.createNotificationChannel(musicDownloadChannel);
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static void createMusicPlaylistDownloadChannel() {
var musicDownloadChannel = new NotificationChannel(MUSIC_PLAYLIST_DOWNLOAD_CHANNEL_ID, MUSIC_PLAYLIST_DOWNLOAD_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
musicDownloadChannel.setDescription(MUSIC_PLAYLIST_DOWNLOAD_CHANNEL_DESCRIPTION);
musicDownloadChannel.setShowBadge(true);
musicDownloadChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
var notificationManager = NotificationManagerCompat.from(AndroidUtils.getGlobalContext());
notificationManager.createNotificationChannel(musicDownloadChannel);
}
public static NotificationManagerCompat getNotificationManager() {
if (notificationManagerCompat == null)
notificationManagerCompat = NotificationManagerCompat.from(AndroidUtils.getGlobalContext());
return notificationManagerCompat;
}
}
@@ -0,0 +1,19 @@
package ru.vtosters.lite.feature.discover;
import com.vk.dto.discover.DiscoverItem;
import java.util.ArrayList;
public class DiscoverTemplates {
public static ArrayList<String> getTemplates() {
var templates = DiscoverItem.Template.values();
var arrayList = new ArrayList<String>();
for (var template : templates) {
if (template != DiscoverItem.Template.GRID_MEDIA) {
arrayList.add(template.b());
}
}
return arrayList;
}
}
@@ -0,0 +1,15 @@
package ru.vtosters.lite.feature.music;
import android.content.Context;
import com.vk.music.fragment.MusicFragment;
public class MusicArtistSelectorHook {
public static void navigateToSearch(Context context, String artist) {
var fr = new MusicFragment.f();
fr.a(artist);
// navigate
fr.a(context);
}
}
@@ -13,8 +13,6 @@ import android.app.ProgressDialog;
import android.content.Context;
import android.util.Log;
import androidx.exifinterface.media.ExifInterface;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.network.Network;
@@ -165,7 +163,7 @@ public class FoafBase {
}
private static String getNormalHumanDate(String str) {
String[] split = str.split(ExifInterface.GPS_DIRECTION_TRUE);
String[] split = str.split("T");
return split[1].split("\\+")[0] + " " + split[0];
}
}
@@ -7,6 +7,7 @@ import static ru.vtosters.lite.utils.LifecycleUtils.getCurrentActivity;
import static ru.vtosters.lite.utils.ThemesUtils.getAlertStyle;
import static ru.vtosters.lite.utils.ThemesUtils.getSTextAttr;
import android.app.Activity;
import android.content.Context;
import android.content.res.ColorStateList;
import android.text.Editable;
@@ -57,7 +58,14 @@ public class CryptImHook{
}
IMProcessor finalEnabled = enabled;
new VkAlertDialog.Builder(getCurrentActivity())
Activity ctx = getCurrentActivity();
if (ctx == null) {
sendToast(getString("app_activities_error"));
return;
}
new VkAlertDialog.Builder(ctx)
.setTitle(getString("encryption_sett"))
.setSingleChoiceItems(titles, enabledPosition, (dialog, which) -> {
if (which == 0) {
@@ -103,6 +111,11 @@ public class CryptImHook{
Context ctx = getCurrentActivity();
if (ctx == null) {
sendToast(getString("app_activities_error"));
return;
}
LinearLayout linearLayout = new LinearLayout(ctx);
final EditText editText = new EditText(ctx);
@@ -447,7 +447,7 @@ public class JsonInjectors {
}
public static JSONObject fetchCatalogId(String section) {
if (section == null) return null;
if (section.isEmpty()) return null;
var request = new Request.a()
.b("https://" + getApi() + "/method/catalog.getAudio"
@@ -14,7 +14,6 @@ import android.preference.PreferenceManager;
import com.vk.core.concurrent.VkExecutors;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.hooks.ui.SystemThemeChangerHook;
import ru.vtosters.lite.ui.dialogs.DisableBattery;
import ru.vtosters.lite.ui.dialogs.InstallGMS;
import ru.vtosters.lite.ui.dialogs.OTADialog;
@@ -22,8 +21,11 @@ import ru.vtosters.lite.ui.dialogs.ServerDialog;
import ru.vtosters.lite.ui.dialogs.Start;
import ru.vtosters.lite.utils.DeletedMessagesHandler;
public class MainActivityInjector{
public static void inject(Activity activity){
/**
* @
*/
public class MainActivityInjector {
public static void inject(Activity activity) {
setNeededTheme(activity);
ServerDialog.activity = activity;
sendRequest();
@@ -36,7 +38,7 @@ public class MainActivityInjector{
.edit()
.putBoolean("isdark", isDarkTheme())
.commit();
SystemThemeChangerHook.onThemeChanged(activity.getResources().getConfiguration());
VkExecutors.x.q().a(() -> {
getInstance().autoCleaningCache();
@@ -7,6 +7,7 @@ import static ru.vtosters.lite.utils.Preferences.autoalltranslate;
import static ru.vtosters.lite.utils.Preferences.autotranslate;
import static ru.vtosters.lite.utils.Preferences.savemsgsett;
import android.content.Context;
import android.text.TextUtils;
import android.view.View;
@@ -16,6 +17,7 @@ import java.util.ArrayList;
import java.util.regex.Pattern;
import ru.vtosters.lite.translators.BaseTranslator;
import ru.vtosters.lite.utils.LifecycleUtils;
public class MessagesHook {
public static String injectOwnText(String oldText) {
@@ -72,6 +74,14 @@ public class MessagesHook {
});
}
public static Context fixCTX(Context ctx) {
if (ctx == null){
return LifecycleUtils.getCurrentActivity();
} else {
return ctx;
}
}
public static void injRequest(MethodCall.a req) {
var time = expireTime();
@@ -2,6 +2,7 @@ package ru.vtosters.lite.hooks;
import static java.lang.Long.MAX_VALUE;
import static ru.vtosters.lite.utils.AndroidUtils.getDefaultPrefs;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.AndroidUtils.getPrefsValue;
import static ru.vtosters.lite.utils.Preferences.ads;
import static ru.vtosters.lite.utils.Preferences.adsslider;
@@ -10,6 +11,9 @@ import static ru.vtosters.lite.utils.Preferences.friendsrecomm;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
import static ru.vtosters.lite.utils.Preferences.vkme;
import android.content.Context;
import android.os.PowerManager;
import com.vk.core.preference.Preference;
import com.vtosters.lite.api.ExtendedCommunityProfile;
@@ -122,4 +126,10 @@ public class NewsfeedHook {
hashSet.add("ads_post_pretty_cards");
hashSet.add("ads_post_snippet_video");
}
public static boolean isPowerSaveMode() {
var pw = (PowerManager) getGlobalContext().getSystemService(Context.POWER_SERVICE);
return !getBoolValue("force_disable_psm", false) && pw.isPowerSaveMode();
}
}
@@ -0,0 +1,10 @@
package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.utils.Preferences.wbios;
import ru.vtosters.lite.utils.AndroidUtils;
public class WritebarHook{
public static int getWriteBar(){
return AndroidUtils.getIdentifier((wbios() ? "write_bar_i" : "write_bar"), "layout");
}
}
@@ -1,6 +1,16 @@
package ru.vtosters.lite.hooks.ui;
import android.util.Log;
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.*;
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.asId;
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.getInstance;
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.isVkxInstalled;
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.lambdaplay;
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;
import static ru.vtosters.lite.utils.Preferences.milkshake;
import android.os.RemoteException;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
@@ -9,24 +19,181 @@ import com.vk.music.common.MusicPlaybackLaunchContext;
import com.vtosters.lite.R;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
public class MusicBottomSheetHook {
public static ArrayList<MusicAction> hook(ArrayList<MusicAction> actions) {
if (LibVKXClient.isVkxInstalled()) {
private static final ExecutorService executor = Executors.newCachedThreadPool();
public static ArrayList<MusicAction> hook(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
if (musicTrack.F1()) return actions;
var trackid = asId(musicTrack);
if (isVkxInstalled()) {
actions.add(getPlayInVKXAction());
}
if (isIntegrationEnabled()) {
if (isVkxCached(trackid)){
actions.add(getRemoveCacheTrackVkxAction());
} else {
actions.add(addToCacheTrackVkxAction());
}
}
if (isNetworkConnected()){
actions.add(downloadAsMp3Action());
}
return actions;
}
public static boolean tryPlayInVKX(int actionId, MusicTrack track, MusicPlaybackLaunchContext context, Playlist playlist) {
if (actionId != AndroidUtils.getIdentifier("play_in_vkx", "id")) {
return false;
public static ArrayList<MusicAction> hookDownloadBTN(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
if (musicTrack.F1()) return actions;
var trackid = asId(musicTrack);
if (isCached(trackid)){
actions.add(new MusicAction(
AndroidUtils.getIdentifier("music_action_toggle_download", "id"),
milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24,
AndroidUtils.getIdentifier("remove_from_cache", "string"),
musicTrack.F1() ? R.color.music_action_button_gray_50_alpha : R.color.caption_gray,
R.string.music_talkback_empty,
false
));
} else {
actions.add(new MusicAction(
AndroidUtils.getIdentifier("music_action_toggle_download", "id"),
milkshake() ? R.drawable.ic_download_outline_28 : R.drawable.ic_download_24,
AndroidUtils.getIdentifier("add_to_cache", "string"),
musicTrack.F1() ? R.color.music_action_button_gray_50_alpha : R.color.caption_gray,
R.string.music_talkback_empty,
false
));
}
return actions;
}
public static ArrayList<MusicAction> hook(ArrayList<MusicAction> actions, Playlist playlist) {
if (playlist.D) return actions;
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
if (isIntegrationEnabled()){
if (isVkxCached(playlist.a, playlist.b)){
actions.add(getRemoveCacheTrackVkxAction());
} else {
actions.add(addToCacheTrackVkxAction());
}
} else {
actions.add(addToCacheTrackAction());
}
if (isNetworkConnected()) actions.add(downloadAsMp3Action());
return actions;
}
public static boolean injectOnClick(int actionId, MusicTrack track, MusicPlaybackLaunchContext context, Playlist playlist) { // musictrack inj
if (actionId == AndroidUtils.getIdentifier("play_in_vkx", "id"))
return tryPlayInVKX(track, context, playlist);
if (actionId == AndroidUtils.getIdentifier("remove_from_cache_vkx", "id")) {
getInstance().runOnService(service -> {
try {
service.deleteTrackFromCache(track.d, track.e);
} catch (RemoteException e) {
e.printStackTrace();
}
});
return true;
} else if (actionId == AndroidUtils.getIdentifier("add_to_cache_vkx", "id")) {
getInstance().runOnService(service -> {
try {
service.addTrackToCache(track.d, track.e, (track.J != null) ? track.J : "");
} catch (RemoteException e) {
e.printStackTrace();
}
});
return true;
}
if (actionId == AndroidUtils.getIdentifier("download_mp3", "id")) {
if (isIntegrationEnabled()) {
getInstance().runOnService(service -> {
try {
service.downloadTrack(track.d, track.e, (track.J != null) ? track.J : "");
} catch (RemoteException e) {
e.printStackTrace();
}
});
} else {
AudioDownloader.downloadAudio(track);
}
return true;
}
return false;
}
public static boolean injectOnClick(int actionId, Playlist playlist) { // playlist inj
if (actionId == AndroidUtils.getIdentifier("play_in_vkx", "id"))
return tryPlayInVKX(null, null, playlist);
if (actionId == AndroidUtils.getIdentifier("add_to_cache", "id")) {
executor.submit(() -> AudioDownloader.cachePlaylist(playlist));
return true;
}
if (actionId == AndroidUtils.getIdentifier("remove_from_cache_vkx", "id")) {
getInstance().runOnService(service -> {
try {
service.deletePlaylistFromCache(playlist.a, playlist.b);
} catch (RemoteException e) {
e.printStackTrace();
}
});
return true;
} else if (actionId == AndroidUtils.getIdentifier("add_to_cache_vkx", "id")) {
getInstance().runOnService(service -> {
try {
service.addPlaylistToCache(playlist.a, playlist.b, (playlist.Q != null) ? playlist.Q : "");
} catch (RemoteException e) {
e.printStackTrace();
}
});
return true;
}
if (actionId == AndroidUtils.getIdentifier("download_mp3", "id")) {
if (isIntegrationEnabled()) {
getInstance().runOnService(service -> {
try {
service.downloadPlaylist(playlist.a, playlist.b, (playlist.Q != null) ? playlist.Q : "");
} catch (RemoteException e) {
e.printStackTrace();
}
});
} else {
executor.submit(() -> AudioDownloader.downloadPlaylist(playlist));
}
return true;
}
return false;
}
public static boolean tryPlayInVKX(MusicTrack track, MusicPlaybackLaunchContext context, Playlist playlist) {
var trackList = new ArrayList<MusicTrack>();
if (playlist != null) {
@@ -35,18 +202,73 @@ public class MusicBottomSheetHook {
trackList.add(track);
}
LibVKXClientImpl.LibVKXAction action = iLibVkxService -> LibVKXClient.lambdaplay(trackList, track, iLibVkxService, context);
return LibVKXClient.getInstance().runOnService(action);
LibVKXClientImpl.LibVKXAction action = iLibVkxService -> lambdaplay(trackList, track, iLibVkxService, context);
return getInstance().runOnService(action);
}
private static MusicAction getPlayInVKXAction() {
return new MusicAction(
AndroidUtils.getIdentifier("play_in_vkx", "id"),
R.drawable.ic_music_24,
milkshake() ? R.drawable.ic_music_outline_24 : R.drawable.ic_music_24,
AndroidUtils.getIdentifier("play_in_vkx", "string"),
R.color.caption_gray,
R.string.music_talkback_empty,
false
);
}
private static MusicAction getRemoveCacheTrackAction() {
return new MusicAction(
AndroidUtils.getIdentifier("remove_from_cache", "id"),
milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24,
AndroidUtils.getIdentifier("remove_from_cache", "string"),
R.color.caption_gray,
R.string.music_talkback_empty,
false
);
}
private static MusicAction addToCacheTrackAction() {
return new MusicAction(
AndroidUtils.getIdentifier("add_to_cache", "id"),
milkshake() ? R.drawable.ic_add_outline_28 : R.drawable.ic_add_24,
AndroidUtils.getIdentifier("add_to_cache", "string"),
R.color.caption_gray,
R.string.music_talkback_empty,
false
);
}
private static MusicAction getRemoveCacheTrackVkxAction() {
return new MusicAction(
AndroidUtils.getIdentifier("remove_from_cache_vkx", "id"),
milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24,
AndroidUtils.getIdentifier("remove_from_cache_vkx", "string"),
R.color.caption_gray,
R.string.music_talkback_empty,
false
);
}
private static MusicAction addToCacheTrackVkxAction() {
return new MusicAction(
AndroidUtils.getIdentifier("add_to_cache_vkx", "id"),
milkshake() ? R.drawable.ic_add_outline_28 : R.drawable.ic_add_24,
AndroidUtils.getIdentifier("add_to_cache_vkx", "string"),
R.color.caption_gray,
R.string.music_talkback_empty,
false
);
}
private static MusicAction downloadAsMp3Action() {
return new MusicAction(
AndroidUtils.getIdentifier("download_mp3", "id"),
milkshake() ? R.drawable.ic_download_outline_28 : R.drawable.ic_download_24,
AndroidUtils.getIdentifier("download_mp3", "string"),
R.color.caption_gray,
R.string.music_talkback_empty,
false
);
}
}
@@ -6,17 +6,24 @@ import ru.vtosters.lite.utils.Preferences;
import ru.vtosters.lite.utils.ThemesUtils;
public class SystemThemeChangerHook {
private static int latestTheme = 0;
public static void onThemeChanged(Configuration configuration) {
if (Preferences.systemtheme()) {
switch (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK) {
var theme = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (theme == latestTheme) return;
switch (theme) {
case Configuration.UI_MODE_NIGHT_YES:
// Night mode is active, we're using dark theme
ThemesUtils.applyTheme(ThemesUtils.getDarkTheme());
latestTheme = theme;
break;
case Configuration.UI_MODE_NIGHT_NO:
case Configuration.UI_MODE_NIGHT_UNDEFINED:
// Night mode is not active, we're using light theme
ThemesUtils.applyTheme(ThemesUtils.getLightTheme());
latestTheme = theme;
break;
}
}
@@ -1,6 +1,6 @@
package ru.vtosters.lite.music;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import java.io.File;
import java.io.IOException;
@@ -8,63 +8,119 @@ import java.io.InputStream;
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;
import javax.crypto.NoSuchPaddingException;
import java8.util.concurrent.CompletableFuture;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
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.IOUtils;
public class M3UDownloader {
public class M3UDownloader implements ITrackDownloader {
private static final OkHttpClient client = new OkHttpClient();
private static OkHttpClient client = new OkHttpClient();
public static M3UDownloader getInstance() {
return Holder.INSTANCE;
}
public static void execute(String url, File outDir, Callback callback) {
public void downloadTrack(MusicTrack track, File outDir, Callback callback, boolean cache) {
var request = new Request.a()
.b(url)
.b(track.D)
.a();
client.a(request).a(new okhttp3.Callback() {
@Override
public void a(Call call, IOException e) {
callback.onFailure();
}
@Override
public void a(Call call, Response response) throws IOException {
parse(response.a().g(), outDir, callback);
parse(response.a().g(), outDir, callback, track, cache);
}
});
}
private static void parse(String payload, File outDir, Callback callback) {
try {
VKM3UParser parser = new VKM3UParser(payload);
List<TransportStream> tses = parser.getTransportStreams();
callback.onProgress(5);
for (TransportStream ts : tses) {
String tsURL = parser.getBaseUrl() + ts.getName();
Log.d("M3UDownloader", "Downloading " + tsURL);
InputStream is = IOUtils.openStream(tsURL);
byte[] content;
if (TransportStream.METHOD_AES128.equals(ts.getMethod())) {
String key = IOUtils.readAllLines(IOUtils.openStream(ts.getKeyURL()));
content = IOUtils.decodeStream(is, key);
} else {
content = IOUtils.readAllBytes(is);
}
File tsDump = new File(outDir, ts.getName());
IOUtils.writeToFile(tsDump, content);
callback.onSizeReceived((long) content.length * tses.size(), parser.getHeapSize());
callback.onProgress(10 + Math.round(80.0f * tses.indexOf(ts) / tses.size()));
private void parse(String payload, File outDir, Callback callback, MusicTrack track, boolean cache) {
if (cache)
try {
ThumbnailDownloader.downloadThumbnails(track);
} catch (IOException e) {
e.printStackTrace();
}
VKM3UParser parser = new VKM3UParser(payload);
List<TransportStream> tses = parser.getTransportStreams();
AtomicInteger progress = new AtomicInteger(0);
List<CompletableFuture<Void>> futures = new ArrayList<>();
var tsesDir = new File(outDir, String.valueOf(track.d));
tsesDir.mkdirs();
var resultTs = new File(tsesDir, "result.ts");
String title = null;
if (track.f != null) {
title = track.f;
if (!track.g.isEmpty()) {
title += " (" + track.g + ")";
}
} catch (IOException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException e) {
e.printStackTrace();
callback.onFailure();
return;
}
callback.onSuccess();
var fileName = IOUtils.getValidFileName((cache ? "track" : track.C + " - " + title) + ".mp3");
var resultMp3 = new File(outDir, fileName);
callback.onProgress(5);
for (TransportStream ts : tses) {
String tsURL = parser.getBaseUrl() + ts.getName();
futures.add(CompletableFuture.runAsync(() -> {
try {
InputStream is = IOUtils.openStream(tsURL);
byte[] content;
if (TransportStream.METHOD_AES128.equals(ts.getMethod())) {
String key = IOUtils.readAllLines(IOUtils.openStream(ts.getKeyURL()));
content = IOUtils.decodeStream(is, key);
} else {
content = IOUtils.readAllBytes(is);
}
File tsDump = new File(tsesDir, ts.getName());
IOUtils.writeToFile(tsDump, content);
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / tses.size()));
callback.onSizeReceived((long) content.length * tses.size(), parser.getHeapSize());
} catch (IOException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException e) {
callback.onFailure();
throw new RuntimeException(e);
}
}));
}
var future = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
future.exceptionally(throwable -> {
callback.onFailure();
return null;
})
.thenApply(v -> TSMerger.merge(tsesDir, resultTs))
.thenApply(mergeResult -> mergeResult && FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track))
.thenApply(convertResult -> {
if (convertResult)
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / tses.size()));
else callback.onFailure();
return convertResult;
})
.thenRun(() -> IOUtils.deleteRecursive(tsesDir))
.thenRun(callback::onSuccess);
}
// Initialization-on-demand
private static class Holder {
private static final M3UDownloader INSTANCE = new M3UDownloader();
}
}
@@ -1,87 +1,76 @@
package ru.vtosters.lite.music;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import android.app.DownloadManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import com.vk.dto.music.MusicTrack;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
public class MP3Downloader {
private static final String URL_FORMAT = "https://" + getApi() + "/method/audio.getById?v=5.131&access_token=%s&audios=%s&copyright=https://vtosters.app/";
private static final OkHttpClient client = new OkHttpClient();
public static void execute(MusicTrack track) {
var token = AndroidUtils.getPreferences().getString("vk_admin_token", "");
if (token.isEmpty()) {
Toast.makeText(AndroidUtils.getGlobalContext(), AndroidUtils.getString("update_vk_admin_token"), Toast.LENGTH_SHORT).show();
return;
}
var request = new Request.a()
.b(String.format(URL_FORMAT, token, track.y1()))
.a();
client.a(request).a(new Callback() {
@Override
public void a(Call call, IOException e) {
Log.d("MP3Downloader", e + "");
}
@Override
public void a(Call call, Response response) throws IOException {
var payload = response.a().g();
Log.d("MP3Downloader", payload);
try {
JSONObject jsonObj = new JSONObject(payload);
if (jsonObj.has("response")) {
JSONObject responseNode = new JSONObject(payload).getJSONArray("response")
.getJSONObject(0);
if (responseNode.has("url")) {
var url = responseNode.optString("url");
Log.d("MP3Downloader", url);
if (url.endsWith(".mp3")) {
downloadMP3(track.toString(), url);
} else if (VKM3UParser.isM3U8(url)) {
AudioDownloader.downloadM3U8(track);
}
}
}
} catch (JSONException e) {
Log.d("MP3Downloader", e + "");
}
}
});
}
@RequiresApi(api = Build.VERSION_CODES.M)
private static void downloadMP3(String title, String url) {
var downloadManager = AndroidUtils.getGlobalContext().getSystemService(DownloadManager.class);
var request = new DownloadManager.Request(Uri.parse(url))
.setAllowedOverRoaming(true)
.setTitle("Downloading " + title)
.setDestinationInExternalFilesDir(AndroidUtils.getGlobalContext(),
Environment.DIRECTORY_MUSIC,
title + ".mp3")
.setVisibleInDownloadsUi(true);
downloadManager.enqueue(request);
}
}
//package ru.vtosters.lite.music;
//
//import android.text.TextUtils;
//import android.util.Log;
//import android.widget.Toast;
//
//import com.vk.dto.music.MusicTrack;
//
//import org.json.JSONException;
//import org.json.JSONObject;
//
//import java.io.File;
//import java.io.IOException;
//
//import okhttp3.Call;
//import okhttp3.Callback;
//import okhttp3.OkHttpClient;
//import okhttp3.Request;
//import okhttp3.Response;
//import ru.vtosters.lite.downloaders.AudioDownloader;
//import ru.vtosters.lite.music.interfaces.ITrackDownloader;
//import ru.vtosters.lite.utils.AndroidUtils;
//
//public class MP3Downloader implements ITrackDownloader {
// private static final String URL_FORMAT = "https://api.vk.com/method/audio.getById?v=5.131&access_token=%s&audios=%s&copyright=";
//
// private static final OkHttpClient client = new OkHttpClient();
//
// public static void downloadMP3(MusicTrack track) {
// var token = AndroidUtils.getPreferences().getString("vk_admin_token", "");
// if (token.isEmpty()) {
// Toast.makeText(AndroidUtils.getGlobalContext(), "Обновите токен VK Admin в настройках", Toast.LENGTH_SHORT).show();
// return;
// }
//
// var request = new Request.a()
// .b(String.format(URL_FORMAT, token, track.y1()))
// .a();
// client.a(request).a(new Callback() {
// @Override
// public void a(Call call, IOException e) {
// Log.d("MP3Downloader", e + "");
// }
//
// @Override
// public void a(Call call, Response response) throws IOException {
// var payload = response.a().g();
// Log.d("MP3Downloader", payload);
// try {
// JSONObject jsonObj = new JSONObject(payload);
// if (!jsonObj.has("response"))
// return;
// JSONObject responseNode = new JSONObject(payload).getJSONArray("response")
// .getJSONObject(0);
// if (!responseNode.has("url"))
// return;
// var url = responseNode.optString("url");
// Log.d("MP3Downloader", url);
// if (TextUtils.isEmpty(url))
// return;
// if (VKM3UParser.isM3U8(url)) {
// AudioDownloader.downloadM3U8(track);
// return;
// }
// } catch (JSONException e) {
// Log.d("MP3Downloader", e + "");
// }
// }
// });
// }
//
// @Override
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.Callback callback) {
//
// }
//}
@@ -6,6 +6,7 @@ import static ru.vtosters.lite.utils.AndroidUtils.edit;
import static ru.vtosters.lite.utils.AndroidUtils.getDefaultPrefs;
import static ru.vtosters.lite.utils.AndroidUtils.getPreferences;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
import static ru.vtosters.lite.utils.Preferences.autocache;
import android.util.Log;
@@ -19,6 +20,7 @@ import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.net.NetCall;
import ru.vtosters.lite.net.NetCallback;
import ru.vtosters.lite.net.NetClient;
@@ -86,6 +88,8 @@ public class Scrobbler {
return;
}
if (autocache()) AudioDownloader.cacheTrack(musictrack);
scrobbleTrack(duration, artist, title, uid);
}
@@ -120,7 +124,7 @@ public class Scrobbler {
@Override
public void onFailure(NetCall call, IOException e) {
Log.d("Scrobbler", "Auth failed", e);
sendToast(AndroidUtils.getString("lastfm_auth_error"));
sendToast(AndroidUtils.getString("auth_error_vtl"));
}
@Override
@@ -0,0 +1,344 @@
package ru.vtosters.lite.music.cache;
import static ru.vtosters.lite.music.cache.FileCacheImplementation.getCacheDir;
import static ru.vtosters.lite.utils.AndroidUtils.*;
import static ru.vtosters.lite.utils.IOUtils.*;
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 com.vk.core.util.ToastUtils;
import com.vk.dto.music.MusicTrack;
import org.json.JSONObject;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
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(getString("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(getString("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) {
}
}
}
@@ -0,0 +1,60 @@
package ru.vtosters.lite.music.cache;
import android.content.Context;
import android.net.Uri;
import com.vk.dto.music.MusicTrack;
import java.io.File;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
public class FileCacheImplementation {
public static void clear() {
CacheDatabaseDelegate.clear();
IOUtils.deleteRecursive(getCacheDir(), false);
}
public static File getCacheDir() {
return AndroidUtils.getGlobalContext().getDir("vt_tracks", Context.MODE_PRIVATE);
}
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(String trackId) {
return new File(getTrackFolder(trackId), "track.mp3");
}
public static boolean isTrackExist(String trackId) {
return CacheDatabaseDelegate.isCached(trackId);
}
public static File getTrackFile(MusicTrack track) {
return 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,93 @@
package ru.vtosters.lite.music.cache.helpers;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import com.vk.dto.music.Playlist;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.FileCacheImplementation;
import ru.vtosters.lite.utils.AccountManagerUtils;
public class PlaylistHelper {
public static Playlist createCachedPlaylistMetadata() {
try {
var obj = new JSONObject()
.put("id", -1)
.put("owner_id", AccountManagerUtils.getUserId())
.put("type", 0)
.put("album_type", "playlist")
.put("title", getString("cached_tracks_title"))
.put("description", getString("cached_tracks_desc"))
.put("count", 0);
return new Playlist(obj);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
public static Playlist createCachedPlaylistMetadata(String id) {
var track = TracklistHelper.getTracksOne(id).get(0);
var albumLink = track.I;
var thumb = albumLink.u1();
return createAlbum(
thumb != null ? thumb.h(600) : "",
albumLink.getTitle(),
track.C,
track.y1(),
albumLink.getId()
);
}
public static Playlist createAlbum(String photo, String title, String artist, String trackId, int albumId) {
try {
var jPhoto = new JSONObject();
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)));
var obj = new JSONObject()
.put("id", -2)
.put("owner_id", albumId)
.put("type", 1)
.put("album_type", "main_only")
.put("title", title)
.put("description", albumId + "/ " + getString("cached_album_warning"))
.put("main_artists", new JSONArray()
.put(new JSONObject().put("name", artist)
.put("id", "-1")
.put("domain", "-1")))
.putOpt("photo", jPhoto)
.put("count", 0);
return new Playlist(obj);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
public static List<Playlist> getAlbumPlaylists() {
List<Playlist> list = new ArrayList<>();
for (var track : CacheDatabaseDelegate.getTracksAsPlaylist()) {
var albumLink = track.I;
var thumb = albumLink.u1();
list.add(createAlbum(
thumb != null ? thumb.h(600) : "",
albumLink.getTitle(),
track.C,
track.y1(),
albumLink.getId()
));
}
return list;
}
}
@@ -0,0 +1,74 @@
package ru.vtosters.lite.music.cache.helpers;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.net.MalformedURLException;
import java.util.List;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.FileCacheImplementation;
public class TracklistHelper {
public static List<MusicTrack> getTracks() {
return CacheDatabaseDelegate.getTracks();
}
public static List<MusicTrack> getTracks(String id) {
return CacheDatabaseDelegate.getTracksByAlbum(id);
}
public static List<MusicTrack> getTracksOne(String id) {
return CacheDatabaseDelegate.getTracksByAlbumOne(id);
}
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
var arr = new JSONArray();
for (MusicTrack track : tracks) {
arr.put(track.y1());
}
return arr;
}
public static JSONArray tracksToJsons(List<MusicTrack> tracks) {
var arr = new JSONArray();
for (MusicTrack track : tracks) {
var json = track.J();
var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track));
try {
addCachedThumbnails(json, folder);
} catch (JSONException | MalformedURLException e) {
Log.d("TracklistHelper", e+"");
}
arr.put(json);
}
return arr;
}
private static void addCachedThumbnails(JSONObject target, File thumbnailsDir)
throws JSONException, MalformedURLException {
var sizes = new JSONArray();
var files = thumbnailsDir.listFiles();
if (files == null || files.length == 0)
return;
for (var file : files) {
var filename = file.getName();
var width = Integer.parseInt(filename.substring(6, filename.length() - 4));
var src = file.toURI().toURL().toString();
Log.d("tracksToJsons", src);
var size = new JSONObject()
.put("width", width)
.put("src", src);
sizes.put(size);
}
target.put("album", new JSONObject().put("thumb", new JSONObject().put("sizes", sizes)));
}
}
@@ -0,0 +1,106 @@
package ru.vtosters.lite.music.cache.injectors;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import com.vk.api.audio.AudioGetPlaylist;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
import ru.vtosters.lite.utils.NetworkUtils;
public class PlaylistInjector {
public final static String CHANNEL_NAME = "VTCH";
public static void injectDownloadPlaylist(Playlist playlist) {
AudioDownloader.downloadPlaylist(playlist);
}
public static boolean eligibleForOfflineCaching() {
Log.d("PlaylistInjector", "eligibleForOfflineCaching");
return !NetworkUtils.isNetworkConnected() && CacheDatabaseDelegate.hasTracks();
}
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
Log.d("PlaylistInjector", "injectGetPlaylist");
try {
if (!eligibleForOfflineCaching())
return null;
var requestArgs = audioGetPlaylist.b();
var id = requestArgs.get("id");
var ownerId = requestArgs.get("owner_id");
var accessKey = requestArgs.get("access_key");
boolean isVirtualPlaylist = accessKey != null && (accessKey.equals("cache"));
boolean isAlbumVirtualPlaylist = accessKey != null && (accessKey.equals("cacheAlbum"));
Log.d("PlaylistInjector", "id = " + id + " / owner = " + ownerId + " / access = " + accessKey + " / " + isVirtualPlaylist + " + " + isAlbumVirtualPlaylist);
if (TextUtils.isEmpty(id) || (!isVirtualPlaylist && !isAlbumVirtualPlaylist))
return null;
return Observable.a((ObservableOnSubscribe<AudioGetPlaylist.c>) observableEmitter -> {
AudioGetPlaylist.c response = new AudioGetPlaylist.c();
if (LibVKXClient.isIntegrationEnabled()) {
LibVKXClient.getInstance().runOnService((service) -> {
try {
List<String> cache;
ArrayList<MusicTrack> tracks = new ArrayList<>();
if (isAlbumVirtualPlaylist) {
response.b = new Playlist(new JSONObject(service.getPlaylistDefJson(id, ownerId)));
cache = service.getTracksInPlaylist(id, ownerId);
} else {
response.b = PlaylistHelper.createCachedPlaylistMetadata();
cache = service.getCache();
}
for (String json : cache) {
tracks.add(new MusicTrack(new JSONObject(json)));
}
response.c = tracks;
} catch (RemoteException | JSONException e) {
e.printStackTrace();
response.c = new ArrayList<>();
}
observableEmitter.b(response);
});
return;
}
if (isVirtualPlaylist) {
response.c = (ArrayList<MusicTrack>) TracklistHelper.getTracks();
response.b = PlaylistHelper.createCachedPlaylistMetadata();
} else {
response.c = (ArrayList<MusicTrack>) TracklistHelper.getTracks(requestArgs.get("owner_id"));
response.b = PlaylistHelper.createCachedPlaylistMetadata(requestArgs.get("owner_id"));
}
observableEmitter.b(response);
});
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
@@ -0,0 +1,223 @@
package ru.vtosters.lite.music.cache.injectors;
import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.hasTracks;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import android.util.Log;
import com.vk.catalog2.core.CatalogParser;
import com.vk.catalog2.core.api.dto.CatalogResponse;
import com.vk.dto.music.MusicTrack;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.NetworkUtils;
public class TracklistInjector {
public static boolean eligibleForOfflineCaching() {
return !NetworkUtils.isNetworkConnected();
}
public static Observable<CatalogResponse> createOfflineRx(CatalogParser parser) {
Log.d("TracklistInjector", "createOfflineRx");
return Observable.a((ObservableOnSubscribe<CatalogResponse>) observableEmitter -> {
if (LibVKXClient.isIntegrationEnabled()) {
LibVKXClient.getInstance().runOnService((service) -> {
try {
try {
Log.d("TracklistInjector", "Using vkx cache catalog");
observableEmitter.b(parser.c(new JSONObject(service.getCacheCatalog())));
} catch (Exception e){
e.printStackTrace();
List<MusicTrack> tracks = new ArrayList<>();
List<String> cache = service.getCache();
for (String json : cache) {
Log.d("TracklistInjector", "added " + json);
tracks.add(new MusicTrack(new JSONObject(json)));
}
Log.d("TracklistInjector", "Using own cache catalog from vkx");
observableEmitter.b(parser.c(createVirtualCatalog(tracks)));
}
} catch (Exception e) {
e.printStackTrace();
}
});
} else {
if (!hasTracks()){
observableEmitter.b(parser.c(getEmptyCatalog()));
} else {
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
}
}
});
}
private static JSONObject getEmptyCatalog() throws JSONException{
return new JSONObject("{\"catalog\":{\"default_section\":\"cache\",\"sections\":[{\"id\":\"cache\",\"title\":\"" + getString("cached_content_title") + "\",\"blocks\":[{\"data_type\":\"placeholder\",\"id\":\"0\",\"layout\":{\"name\":\"placeholder_big\"},\"placeholder_ids\":[\"_synth_trackEmpty\"]}]}]},\"audios\":[],\"playlists\":[],\"placeholders\":[{\"id\":\"_synth_trackEmpty\",\"title\":\"" + getString("cached_content_empty") + "\",\"text\":\"" + getString("cached_content_empty_desc") + "\",\"buttons\":[]}]}");
}
public static void injectIntoExistingCatalog(JSONObject catalogNode) {
Log.d("TracklistInjector", "injectIntoExistingCatalog");
if (!hasTracks()) return;
try {
var sectionsNode = catalogNode.getJSONObject("catalog")
.getJSONArray("sections");
var firstSection = sectionsNode.getJSONObject(0);
if (!firstSection.getString("url").equals("https://vk.com/audios" + AccountManagerUtils.getUserId() + "?section=all"))
return;
var foundPlaylistBlock = false;
var suitableIndex = 0;
var blocks = firstSection.getJSONArray("blocks");
for (int i = 0; i < blocks.length(); i++) {
var block = blocks.getJSONObject(i);
var dataType = block.getString("data_type");
if ("music_audios".equals(dataType)) {
suitableIndex = i - 1;
continue;
}
if (!"music_playlists".equals(dataType))
continue;
var playlistIds = block.getJSONArray("playlists_ids");
var newPlaylistIds = new JSONArray()
.put(AccountManagerUtils.getUserId() + "_-1");
for (int j = 0; j < playlistIds.length(); j++)
newPlaylistIds.put(playlistIds.get(j));
block.put("playlists_ids", newPlaylistIds);
// need test
blocks.remove(i);
blocks.put(i, block);
foundPlaylistBlock = true;
}
if (foundPlaylistBlock) {
var playlists = catalogNode.getJSONArray("playlists")
.put(0, PlaylistHelper.createCachedPlaylistMetadata().J());
catalogNode.put("playlists", playlists);
return;
}
var ids = new JSONArray()
.put(0, AccountManagerUtils.getUserId() + "_-1");
var playlistsBlock = new JSONObject()
.put("id", getRandomId())
.put("data_type", "music_playlists")
.put("url", "synth:cache:playlists")
.put("playlists_ids", ids);
var layout = new JSONObject()
.put("name", "large_slider")
.put("owner_id", AccountManagerUtils.getUserId());
playlistsBlock.put("layout", layout);
blocks.put(suitableIndex, playlistsBlock);
firstSection.put("blocks", blocks);
var playlists = new JSONArray()
.put(0, PlaylistHelper.createCachedPlaylistMetadata().J());
catalogNode.put("playlists", playlists);
} catch (JSONException e) {
e.printStackTrace();
}
}
public static JSONObject createVirtualCatalog(List<MusicTrack> tracks)
throws JSONException {
Log.d("TracklistInjector", "createVirtualCatalog");
var clickAction = new JSONObject()
.put("type", "open_url")
.put("target", "internal")
.put("url", "https://vkx.app/index.html?app=vt_ctg");
var catalogBanner = new JSONObject()
.put("id", 1234)
.put("image_mode", "cover")
.put("title", getString("vkx_integration_title"))
.put("text", getString("vkx_integration_desc"))
.put("click_action", new JSONObject().put("action", clickAction));
var catalogBanners = new JSONArray()
.put(catalogBanner);
var audiosBlock = new JSONObject()
.put("id", getRandomId())
.put("data_type", "music_audios")
.put("url", "synth:cache:list")
.put("audios_ids", TracklistHelper.tracksToIds(tracks));
var layout = new JSONObject()
.put("name", "list")
.put("owner_id", AccountManagerUtils.getUserId());
audiosBlock.put("layout", layout);
var blocks = new JSONArray()
.put(audiosBlock);
// var vkxPostirony = new JSONObject()
// .put("id", getRandomId())
// .put("data_type", "catalog_banners")
// .put("url", "synth:cache:vkx")
// .put("catalog_banner_ids", new JSONArray().put(1234));
//
// var vkxlayout = new JSONObject()
// .put("name", "large_list")
// .put("owner_id", Globals.getUserId())
// .put("infinite_repeat", true);
//
// vkxPostirony.put("layout", vkxlayout);
//
// if (eligibleForVkxAd())
// blocks.put(vkxPostirony);
var randomId = getRandomId();
var section = new JSONObject()
.put("id", randomId)
.put("title", getString("saved_tracks_title"))
.put("url", "synth:cache")
.put("blocks", blocks);
var sections = new JSONArray()
.put(section);
var catalog = new JSONObject()
.put("sections", sections)
.put("default_section", randomId);
return new JSONObject()
.put("catalog", catalog)
.put("audios", TracklistHelper.tracksToJsons(tracks))
.put("catalog_banners", catalogBanners);
}
private static String getRandomId() {
return UUID.randomUUID().toString();
}
}
@@ -0,0 +1,94 @@
package ru.vtosters.lite.music.callback;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.music.Callback;
public class MusicCallbackBuilder {
private static NotificationManagerCompat notificationManager = NotificationChannels.getNotificationManager();
public static Callback buildOneTrackCallback(int notificationId, NotificationCompat.Builder notification) {
return new Callback() {
long startTime = 0;
long elapsedTime = 0;
@Override
public void onProgress(int progress) {
if (elapsedTime > 1000) {
notification.setProgress(100, progress, false);
notificationManager.notify(notificationId, notification.build());
startTime = System.currentTimeMillis();
elapsedTime = 0;
} else {
elapsedTime = System.currentTimeMillis() - startTime;
}
}
@Override
public void onSuccess() {
try {
notification
.setContentText(getString("player_download_finished"))
.setProgress(0, 0, false)
.setSmallIcon(android.R.drawable.stat_sys_download_done);
notificationManager.notify(notificationId, notification.build());
} catch (UnsatisfiedLinkError e) {
Log.e("AudioDownloader", "native libs");
Log.e("AudioDownloader", e.getMessage());
onFailure();
}
}
@Override
public void onFailure() {
notification.setContentText(getString("load_audio_error")).setProgress(0, 0, false);
notificationManager.notify(notificationId, notification.build());
}
@Override
public void onSizeReceived(long size, long header) {
}
};
}
public static Callback buildPlaylistCallback(int playlistSize, NotificationCompat.Builder notification, int notificationId) {
return new Callback() {
@Override
public void onProgress(int progress) {
if (progress == playlistSize) {
onSuccess();
} else {
notification.setContentText(String.format(getString("playlist_download_notf"), progress, playlistSize));
notification.setProgress(playlistSize, progress, false);
}
notificationManager.notify(notificationId, notification.build());
}
@Override
public void onSuccess() {
notification.setContentText(getString("player_download_finished"));
notification.setProgress(0, 0, false);
}
@Override
public void onFailure() {
notification.setContentText(getString("load_audio_error")).setProgress(0, 0, false);
notificationManager.notify(notificationId, notification.build());
}
@Override
public void onSizeReceived(long size, long header) {
}
};
}
}
@@ -0,0 +1,86 @@
package ru.vtosters.lite.music.converter.playlist;
import static ru.vtosters.lite.hooks.DateHook.getLocale;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import com.vk.core.network.Network;
import com.vk.core.util.DeviceIdProvider;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java8.util.concurrent.CompletableFuture;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
/**
* stuff for converting playlists DTO
*/
public class PlaylistConverter {
public static List<MusicTrack> getPlaylist(Playlist oldPlaylist) {
var tracks = requestPlaylist(0, oldPlaylist.b, oldPlaylist.Q, oldPlaylist.a);
return tracks.stream().filter(track -> !track.D.isEmpty()).collect(Collectors.toList());
}
private static List<MusicTrack> requestPlaylist(int offset, int ownerId, String accessKey, int id) {
var audioCount = 1000;
var requestUrl = "https://" + getApi() + "/method/execute.getPlaylist" +
"?v=5.119" +
"&https=1" +
"&audio_count=" + audioCount +
"&need_playlist=1" +
"&device_id=" + DeviceIdProvider.d(AndroidUtils.getGlobalContext()) +
"&owner_id=" + ownerId +
"&access_key=" + accessKey +
"&func_v=5" +
"&id=" + id +
"&lang=" + getLocale() +
"&audio_offset=" + offset +
"&need_owner=1" +
"&access_token=" + AccountManagerUtils.getUserToken();
var request = new Request.a()
.b(requestUrl)
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
try {
var response = CompletableFuture.supplyAsync(() -> {
try {
return new OkHttpClient().a(request).execute().a().g();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
).get();
var jsonObj = new JSONObject(response);
if (!jsonObj.has("response"))
return null;
var playlist = new JSONObject(response).getJSONObject("response").getJSONArray("audios");
var tracks = new ArrayList<MusicTrack>();
for (int i = 0; i < playlist.length(); i++) {
var track = new MusicTrack(playlist.getJSONObject(i));
tracks.add(track);
}
if (tracks.size() == audioCount) {
tracks.addAll(requestPlaylist(offset + audioCount, ownerId, accessKey, id));
}
return tracks;
} catch (JSONException | ExecutionException | InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
@@ -1,21 +1,78 @@
package ru.vtosters.lite.music;
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 java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.stream.Collectors;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
public class FFMpeg {
private static final int BUFFER_SIZE = 2048;
/**
* @param in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger}
* @param out Output .mp3 file
* @param track Track to be converted
* @return True if success, else false
*/
public static boolean convert(File in, String out, MusicTrack track) {
checkFFMpegLibs();
// 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;
}
private static void copyAssets() {
try {
@@ -45,40 +102,4 @@ public class FFMpeg {
copyAssets();
}
}
public static boolean convert(String in, String out) {
checkFFMpegLibs();
// write the list of all .ts files to a txt file in "in" directory
var list = new File(in);
var listFile = new File(list, "list.txt");
try {
listFile.createNewFile();
var writer = new java.io.FileWriter(listFile);
for (var file : list.listFiles()) {
if (file.getName().endsWith(".ts")) {
writer.write("file " + file.getName() + "\n");
}
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
// concatenate .ts files to a single .ts file
FFmpegKit.execute("-f concat -safe 0 -i " + in + "/list.txt -c copy " + in + "/all.ts");
// convert .ts file to .mp3 file via ffmpeg
var session = FFmpegKit.execute("-i " + in + "/all.ts -f mp3 -acodec mp3 -q:a 0 -y '" + out + "'");
var rc = session.getReturnCode();
if (ReturnCode.isSuccess(rc)) {
// remove temp files
IOUtils.deleteRecursive(new File(in));
return true;
}
Log.e("FFMpeg", "FFmpegKit failed with return code " + rc);
return false;
}
}
@@ -0,0 +1,64 @@
package ru.vtosters.lite.music.converter.ts;
import android.os.Build;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import ru.vtosters.lite.utils.IOUtils;
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();
if (tsesList == null) {
return false;
}
var mergedByteArrayStream = new ByteArrayOutputStream();
Arrays.sort(tsesList, (o1, o2) -> {
var index1 = Integer.parseInt(o1.getName().split("-")[1]);
var index2 = Integer.parseInt(o2.getName().split("-")[1]);
return index1 - index2;
});
for (var file : tsesList) {
if (file.getName().endsWith(".ts")) {
try {
mergedByteArrayStream.write(IOUtils.readAllBytes(file));
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
Files.write(out.toPath(), mergedByteArrayStream.toByteArray());
} catch (IOException e) {
e.printStackTrace();
return false;
}
} else {
try (FileOutputStream fos = new FileOutputStream(out)) {
fos.write(mergedByteArrayStream.toByteArray());
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
return true;
}
}
@@ -0,0 +1,81 @@
package ru.vtosters.lite.music.downloader;
import static ru.vtosters.lite.hooks.DateHook.getLocale;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import android.util.Log;
import com.vk.core.network.Network;
import com.vk.core.util.DeviceIdProvider;
import com.vk.dto.music.MusicTrack;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java8.util.concurrent.CompletableFuture;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import ru.vtosters.lite.utils.AndroidUtils;
public class AudioGet{
public static List<MusicTrack> getAudios(){
var tracks = requestAudios(0);
return tracks != null ? tracks.stream().filter(track -> !track.D.isEmpty()).collect(Collectors.toList()) : null;
}
private static List<MusicTrack> requestAudios(int offset){
var audioCount = 1000;
var requestUrl = "https://" + getApi() + "/method/audio.get" +
"?v=5.119" +
"&https=1" +
"&count=" + audioCount +
"&device_id=" + DeviceIdProvider.d(AndroidUtils.getGlobalContext()) +
"&owner_id=" + getUserId() +
"&access_key=" + getUserToken() +
"&lang=" + getLocale() +
"&offset=" + offset +
"&access_token=" + getUserToken();
var request = new Request.a()
.b(requestUrl)
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
try {
var response = CompletableFuture.supplyAsync(() -> {
try {
return new OkHttpClient().a(request).execute().a().g();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
).get();
var jsonObj = new JSONObject(response);
if (!jsonObj.has("response"))
return null;
Log.d("AudioGet", jsonObj.toString());
var audios = new JSONObject(response).getJSONObject("response").getJSONArray("items");
var tracks = new ArrayList<MusicTrack>();
for (int i = 0; i < audios.length(); i++) {
var track = new MusicTrack(audios.getJSONObject(i));
tracks.add(track);
}
if (tracks.size() == audioCount) {
tracks.addAll(requestAudios(offset + audioCount));
}
return tracks;
} catch (JSONException | ExecutionException | InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
@@ -0,0 +1,77 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import androidx.core.app.NotificationManagerCompat;
import com.vk.dto.music.MusicTrack;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.music.Callback;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
public class PlaylistDownloader {
private static NotificationManagerCompat notificationManager = NotificationChannels.getNotificationManager();
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
File 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) {
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
@Override
public void onProgress(int progress) {}
@Override
public void onSuccess() {
var currentProgress = downloadedTracks.incrementAndGet();
callback.onProgress(currentProgress);
}
@Override
public void onFailure() {
callback.onFailure();
}
@Override
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) {
TrackDownloader.cacheTrack(musicTrack, new Callback() {
@Override
public void onProgress(int progress) {}
@Override
public void onSuccess() {
var currentProgress = downloadedTracks.incrementAndGet();
callback.onProgress(currentProgress);
CacheDatabaseDelegate.insertTrack(musicTrack);
}
@Override
public void onFailure() {
callback.onFailure();
}
@Override
public void onSizeReceived(long size, long header) {}
});
}
}
}
@@ -0,0 +1,49 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import java.io.IOException;
import java.net.URL;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import ru.vtosters.lite.music.cache.FileCacheImplementation;
import ru.vtosters.lite.utils.IOUtils;
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);
}
} 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);
}
}
}
private static void downloadThumbnail(String url, int res, String trackId)
throws IOException {
var data = IOUtils.readAllBytes(new URL(url).openStream());
IOUtils.writeToFile(FileCacheImplementation.getTrackThumbnail(trackId, res), data);
}
}
@@ -0,0 +1,34 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import java.io.File;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import ru.vtosters.lite.music.Callback;
import ru.vtosters.lite.music.M3UDownloader;
import ru.vtosters.lite.music.cache.FileCacheImplementation;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
public class TrackDownloader {
public static void downloadTrack(MusicTrack track, String path, Callback callback) {
download(track, path, callback, M3UDownloader.getInstance(), false);
}
public static void cacheTrack(MusicTrack track, Callback callback) {
var path = FileCacheImplementation.getTrackFolder(LibVKXClient.asId(track)).getAbsolutePath();
download(track, path, callback, M3UDownloader.getInstance(), true);
}
private static void download(MusicTrack track, String path, Callback callback, ITrackDownloader downloader, boolean cache) {
File outDir = new File(path);
if (!outDir.exists())
if (outDir.mkdir())
Log.v("TrackDownloader", "Directory created");
else
Log.e("TrackDownloader", "Directory creation failed");
downloader.downloadTrack(track, outDir, callback, cache);
}
}
@@ -0,0 +1,20 @@
package ru.vtosters.lite.music.interfaces;
import com.vk.dto.music.MusicTrack;
import java.io.File;
import ru.vtosters.lite.music.Callback;
/**
* Interface for track downloaders
*/
public interface ITrackDownloader {
/**
* Download track
* @param track Music track
* @param outDir Directory where to download the track
* @param callback Callback to call when download status changes
*/
void downloadTrack(MusicTrack track, File outDir, Callback callback, boolean cache);
}
@@ -0,0 +1,73 @@
package ru.vtosters.lite.music.notification;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import android.app.Notification;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
public class MusicNotificationBuilder {
private static NotificationManagerCompat notificationManager = NotificationChannels.getNotificationManager();
public static NotificationCompat.Builder buildDownloadNotification(MusicTrack track, int id) {
var notificationBuilder = new NotificationCompat.Builder(AndroidUtils.getGlobalContext(), NotificationChannels.MUSIC_DOWNLOAD_CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle(getString("audio_downloading"))
.setContentText(track.toString())
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.setAutoCancel(true)
.setProgress(100, 0, false);
notificationManager.notify(id, notificationBuilder.build());
return notificationBuilder;
}
public static NotificationCompat.Builder buildPlaylistDownloadNotification(String playlistName, int id) {
var notificationBuilder = new NotificationCompat.Builder(AndroidUtils.getGlobalContext(), NotificationChannels.MUSIC_PLAYLIST_DOWNLOAD_CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle(getString("playlist_downloading") + " «" + playlistName + "»")
.setContentText(playlistName)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.setAutoCancel(true)
.setProgress(100, 0, false);
notificationManager.notify(id, notificationBuilder.build());
return notificationBuilder;
}
public static NotificationCompat.Builder buildAllAudiosDownloadNotification(int id) {
var notificationBuilder = new NotificationCompat.Builder(AndroidUtils.getGlobalContext(), NotificationChannels.MUSIC_PLAYLIST_DOWNLOAD_CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle(getString("notf_audio_dl_user") + " " + AccountManagerUtils.getUsername())
.setContentText(getString("all_audio"))
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.setAutoCancel(true)
.setProgress(100, 0, false);
notificationManager.notify(id, notificationBuilder.build());
return notificationBuilder;
}
public static void notifySavingToCache(MusicTrack track) {
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= 26) {
builder = new Notification.Builder(AndroidUtils.getGlobalContext(), NotificationChannels.MUSIC_DOWNLOAD_CHANNEL_ID);
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
} else builder = new Notification.Builder(AndroidUtils.getGlobalContext());
builder.setSmallIcon(com.vtosters.lite.R.drawable.ic_download_outline_28)
.setShowWhen(false)
.setContentTitle(getString("cached_track") + ": " + track.toString());
notificationManager.notify(track.y1().hashCode(), builder.build());
}
}
@@ -0,0 +1,32 @@
package ru.vtosters.lite.ui.dialogs;
import static ru.vtosters.lite.utils.AndroidUtils.edit;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
import android.app.Activity;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.ui.themes.VKThemeHelper;
import ru.vtosters.lite.hooks.VKUIHook;
public class ThemeChanging{
public static void changeTheme(Activity activity, float[] f){
if (getBoolValue("systemtheme", true)) {
new VkAlertDialog.Builder(activity)
.setTitle(getString("warning"))
.setMessage(getString("system_theme_warning"))
.setCancelable(false)
.setPositiveButton(getString("proxy_disable"), (dialogInterface, i) -> { // Отключить
edit().putBoolean("systemtheme", false).commit();
VKThemeHelper.b(activity, f);
VKUIHook.clearWebCache();
})
.setNeutralButton(getString("cancel"), null)
.show();
} else {
VKThemeHelper.b(activity, f);
VKUIHook.clearWebCache();
}
}
}
@@ -39,22 +39,22 @@ import org.json.JSONObject;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.downloaders.VideoDownloader;
import ru.vtosters.lite.music.Scrobbler;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.ui.adapters.ImagineArrayAdapter;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
public class MediaFragment extends MaterialPreferenceToolbarFragment {
private final int REQUEST_CODE_SET_DOWNLOAD_DIRECTORY = 665;
private final int REQUEST_CODE_SET_MUSIC_DIRECTORY = 666;
private final int REQUEST_CODE_SET_PHOTOS_DIRECTORY = 667;
private final int REQUEST_CODE_SET_VIDEOS_DIRECTORY = 668;
private static final ExecutorService executor = Executors.newCachedThreadPool();
@Override
public void onCreate(Bundle bundle) {
@@ -95,6 +95,21 @@ public class MediaFragment extends MaterialPreferenceToolbarFragment {
findPreference("lastfm_enabled").setEnabled(false);
}
findPreference("cached_tracks").setSummary(String.format(AndroidUtils.getString("cached_tracks_counter"), CacheDatabaseDelegate.getTrackCount()));
findPreference("cached_tracks").setOnPreferenceClickListener(preference -> {
if (CacheDatabaseDelegate.getTrackCount() == 0){
sendToast(AndroidUtils.getString("no_cache_error"));
} else {
delcache(requireContext());
}
return true;
});
findPreference("audio_download").setOnPreferenceClickListener(preference -> {
dlaudio(requireContext());
return true;
});
if (!isVkxInstalled()){
findPreference("vkx_sett").setVisible(false);
}
@@ -178,7 +193,39 @@ public class MediaFragment extends MaterialPreferenceToolbarFragment {
alert.getButton(DialogInterface.BUTTON_NEUTRAL).setTextColor(getResources().getColor(com.vtosters.lite.R.color.red));
}
public static void deleteVideoHistory() {
private void delcache(Context ctx) {
VkAlertDialog.Builder alertDialog = new VkAlertDialog.Builder(ctx);
alertDialog.setTitle(AndroidUtils.getString("warning"));
alertDialog.setMessage(AndroidUtils.getString("cached_tracks_remove_confirm"));
alertDialog.setPositiveButton(AndroidUtils.getString("yes"), (dialog, which) -> {
executor.submit(CacheDatabaseDelegate::clear);
});
alertDialog.setNeutralButton(AndroidUtils.getString("no"), (dialog, which) -> {
dialog.cancel();
});
var alert = alertDialog.create();
alert.show();
alert.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(getAccentColor());
alert.getButton(DialogInterface.BUTTON_NEUTRAL).setTextColor(getResources().getColor(com.vtosters.lite.R.color.red));
}
private void dlaudio(Context ctx) {
VkAlertDialog.Builder alertDialog = new VkAlertDialog.Builder(ctx);
alertDialog.setTitle(AndroidUtils.getString("download_method"));
alertDialog.setMessage(AndroidUtils.getString("download_method_desc"));
alertDialog.setPositiveButton(AndroidUtils.getString("download_method_cache"), (dialog, which) -> {
executor.submit(AudioDownloader::cacheAllAudios);
});
alertDialog.setNegativeButton(AndroidUtils.getString("download_method_mp3"), (dialog, which) -> {
executor.submit(AudioDownloader::downloadAllAudios);
});
var alert = alertDialog.create();
alert.show();
alert.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(getAccentColor());
alert.getButton(DialogInterface.BUTTON_NEGATIVE).setTextColor(getAccentColor());;
}
public void deleteVideoHistory() {
Thread thread = new Thread(() -> {
try {
var request = new Request.a()
@@ -190,9 +237,9 @@ public class MediaFragment extends MaterialPreferenceToolbarFragment {
var response = new JSONObject(new OkHttpClient().a(request).execute().a().g());
if (response.optInt("response") == 1){
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> sendToast(AndroidUtils.getString("video_history_cleaned")));
requireActivity().runOnUiThread(() -> sendToast(AndroidUtils.getString("video_history_cleaned")));
} else {
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> sendToast("Ошибка при очистке истории. Подробности в логах"));
requireActivity().runOnUiThread(() -> sendToast(AndroidUtils.getString("delete_video_history_error")));
}
Log.d("VideoHistory", response.toString());
@@ -210,7 +257,7 @@ public class MediaFragment extends MaterialPreferenceToolbarFragment {
private void deleteVideoHistoryDialog(Context context){
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(context);
builder.setTitle(AndroidUtils.getString("warning"));
builder.setMessage("Вы действительно хотите очистить историю видео?");
builder.setMessage(AndroidUtils.getString("delete_video_history_confirm"));
builder.setCancelable(false);
builder.setPositiveButton(AndroidUtils.getString("yes"), (dialogInterface, i) -> {
deleteVideoHistory();
@@ -309,7 +309,7 @@ public class OtherFragment extends MaterialPreferenceToolbarFragment {
private void delprefs(Context context) {
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(context);
builder.setTitle(AndroidUtils.getString("warning"));
builder.setMessage("Вы действительно хотите сбросить настройки?");
builder.setMessage(AndroidUtils.getString("settings_reset_confirm"));
builder.setCancelable(false);
builder.setPositiveButton(AndroidUtils.getString("yes"), (dialogInterface, i) -> {
deletePrefs();
@@ -179,19 +179,17 @@ public class VTSettings extends MaterialPreferenceToolbarFragment {
if (Preferences.systemtheme()) {
new VkAlertDialog.Builder(requireActivity())
.setTitle("Внимание!")
.setMessage("У вас включена системная тема \n" +
"При переключении темы системная тема будет отключена \n\n" +
"Вы действительно хотите отключить её?")
.setTitle(AndroidUtils.getString("warning"))
.setMessage(AndroidUtils.getString("system_theme_warning"))
.setCancelable(false)
.setPositiveButton("Отключить", (dialogInterface, i) -> {
.setPositiveButton(AndroidUtils.getString("proxy_disable"), (dialogInterface, i) -> { // Отключить
switchPreference.setChecked(isDarkTheme);
((MaterialSwitchPreference) findPreference("systemtheme")).setChecked(false);
requireActivity().runOnUiThread(() -> {
switchTheme(isDarkTheme);
});
})
.setNeutralButton("Отмена", null)
.setNeutralButton(AndroidUtils.getString("cancel"), null)
.show();
return false;
}
@@ -157,7 +157,7 @@ public class WallpaperMenuFragment extends MaterialPreferenceToolbarFragment{
});
}
PreferencesUtil.addMaterialSwitchPreference(this, "compresswp", "Сжимать обои", "Значительно ускоряет открытие чатов", null, true, (preference, o) -> {
PreferencesUtil.addMaterialSwitchPreference(this, "compresswp", AndroidUtils.getString("compress_wallpaper_title"), AndroidUtils.getString("compress_wallpaper_summ"), null, true, (preference, o) -> {
boolean value = (boolean) o;
edit().putBoolean("compresswp", value).commit();
requestUpdateWallpaper();
@@ -4,6 +4,7 @@ import static com.vk.im.engine.h.im_bg_chat;
import static ru.vtosters.lite.ui.wallpapers.ImageFilters.getFilteredDrawable;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.AndroidUtils.getPreferences;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
@@ -71,7 +72,7 @@ public class WallpapersHooks{
private static Boolean eligibleWallpaperFile(File file){
if (file.length() >= 6291456) {
sendToast("Размер файла превышает лимит 6 MB");
sendToast(getString("wallpaper_size_limit"));
removeWallpaper();
return false;
} else {
@@ -131,13 +132,13 @@ public class WallpapersHooks{
String radius = getPreferences().getString("msg_blur_radius", "disabled");
switch(radius) {
case "low":
return AndroidUtils.getString("wallpapers_low");
return getString("wallpapers_low");
case "med":
return AndroidUtils.getString("wallpapers_med");
return getString("wallpapers_med");
case "high":
return AndroidUtils.getString("wallpapers_high");
return getString("wallpapers_high");
default:
return AndroidUtils.getString("wallpapers_disabled");
return getString("wallpapers_disabled");
}
}
@@ -145,11 +146,11 @@ public class WallpapersHooks{
String radius = getPreferences().getString("msg_dim", "disabled");
switch(radius) {
case "dim_black":
return AndroidUtils.getString("wallpapers_dim_black");
return getString("wallpapers_dim_black");
case "dim_white":
return AndroidUtils.getString("wallpapers_dim_white");
return getString("wallpapers_dim_white");
default:
return AndroidUtils.getString("wallpapers_disabled");
return getString("wallpapers_disabled");
}
}
@@ -157,13 +158,13 @@ public class WallpapersHooks{
String radius = getPreferences().getString("msg_mosaic", "disabled");
switch(radius) {
case "low":
return AndroidUtils.getString("wallpapers_low");
return getString("wallpapers_low");
case "med":
return AndroidUtils.getString("wallpapers_med");
return getString("wallpapers_med");
case "high":
return AndroidUtils.getString("wallpapers_high");
return getString("wallpapers_high");
default:
return AndroidUtils.getString("wallpapers_disabled");
return getString("wallpapers_disabled");
}
}
@@ -12,6 +12,7 @@ import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import com.vk.core.util.Screen;
@@ -19,6 +20,7 @@ import com.vk.core.util.Screen;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
import java.util.Objects;
public class AndroidUtils {
@@ -44,13 +46,14 @@ public class AndroidUtils {
return PreferenceManager.getDefaultSharedPreferences(getGlobalContext()).edit();
} // Edit SharedPreferences
@NonNull
public static Context getGlobalContext() {
try {
return ReflectionUtils.invokeStaticMethod("android.app.AppGlobals", "getInitialApplication");
} catch (Exception e) {
Log.d("GlobalContext", "Error while fetching context via refl");
}
return null; // need fix for a13+ cuz google shit disabled global context via refl
return Objects.requireNonNull(LifecycleUtils.getCurrentActivity());
} // Getting the global context through reflection to use context on application initialization
public static Resources getResources() {
@@ -4,9 +4,11 @@ import static androidx.core.app.ActivityCompat.requestPermissions;
import static ru.vtosters.lite.net.Request.makeRequest;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
import android.Manifest;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.SyncResult;
@@ -39,16 +41,18 @@ public class ContactsUtils{
Log.d("ContactsUtils", "Result: " + mainJson + ", enabled sync: " + enabled);
} catch (JSONException e) {
Log.d("ContactsUtils", e.getMessage());
sendToast("Ошибка установки контактов");
sendToast(getString("contact_sync_error"));
}
});
}
public static void uploadContacts(Context ctx){
public static void uploadContacts(Activity ctx){
try {
if (ContextCompat.checkSelfPermission(ctx, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
sendToast("Для работы данной функции необходимо выдать право на чтение контактов");
sendToast(getString("contact_permission_toast"));
requestPermissions(LifecycleUtils.getCurrentActivity(), new String[] {Manifest.permission.READ_CONTACTS}, 11111);
sendToast(getString("contact_permission_toast"));
requestPermissions(ctx, new String[] {Manifest.permission.READ_CONTACTS}, 11111);
} else {
var account = VKAuthUtils.a.a(VKAccountManager.d().Z());
ContactsSyncAdapterService.c(ctx, account, new Bundle(), new SyncResult());
@@ -60,7 +64,7 @@ public class ContactsUtils{
public static void getContactsStatus(Context ctx){
final ProgressDialog progressDialog = new ProgressDialog(ctx);
progressDialog.setMessage("Загрузка данных о контактах...");
progressDialog.setMessage(getString("contact_info_loading"));
progressDialog.show();
var request = new okhttp3.Request.a()
@@ -80,10 +84,10 @@ public class ContactsUtils{
boolean enabledsync = resp.equals("contact");
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(ctx);
builder.setTitle("Синхронизация контактов");
builder.setMessage("Статус синхронизации: " + (enabledsync ? "Включена" : "Отключена"));
builder.setTitle(getString("contact_sync_title"));
builder.setMessage(getString("contact_sync_state") + ": " + getString(enabledsync ? "contact_sync_enabled" : "contact_sync_disabled"));
builder.setCancelable(true);
builder.setPositiveButton((!enabledsync ? "Включить" : "Отключить"), (dialog, which) -> {
builder.setPositiveButton((!enabledsync ? getString("enable") : getString("proxy_disable")), (dialog, which) -> {
setContactsSync(!enabledsync);
});
// builder.setNegativeButton("Импортировать контакты", (dialog, which) -> {
@@ -59,12 +59,12 @@ public class ExternalLinkParser {
if (checkVkVideo(file)) {
if (qualities.size() == 1) {
startExternalVideo((String) qualities.values().toArray()[0]);
startExternalVideo((String) qualities.values().toArray()[0], context);
} else {
String[] titles = qualities.keySet().toArray(new String[0]);
new AlertDialog.Builder(context)
.setItems(titles, ((dialog, which) -> {
startExternalVideo(qualities.get(titles[which]));
startExternalVideo(qualities.get(titles[which]), context);
}))
.show();
}
@@ -117,7 +117,7 @@ public class ExternalLinkParser {
return qualities.size() > 0;
}
public static boolean startExternalVideo(String url) {
public static boolean startExternalVideo(String url, Context ctx) {
try {
qualities.clear();
@@ -136,7 +136,7 @@ public class ExternalLinkParser {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
getGlobalContext().startActivity(intent);
ctx.startActivity(intent);
return true;
} catch (Exception e) {
e.printStackTrace();
@@ -26,7 +26,10 @@ public class IOUtils {
public static byte[] decodeStream(InputStream encIs, String keyURL) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IOException {
CipherInputStream cip = new CipherInputStream(encIs, getCipher(keyURL));
return readAllBytes(cip);
var bytes = readAllBytes(cip);
cip.close();
return bytes;
}
public static Cipher getCipher(String key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
@@ -46,7 +49,11 @@ public class IOUtils {
}
public static String readAllLines(File file) throws IOException {
return readAllLines(new FileInputStream(file));
var is = new FileInputStream(file);
var bytes = readAllBytes(is);
is.close();
return new String(bytes, StandardCharsets.UTF_8);
}
public static String readAllLines(InputStream is) throws IOException {
@@ -54,7 +61,11 @@ public class IOUtils {
}
public static byte[] readAllBytes(File file) throws IOException {
return readAllBytes(new FileInputStream(file));
var is = new FileInputStream(file);
var bytes = readAllBytes(is);
is.close();
return bytes;
}
public static byte[] readAllBytes(InputStream is) throws IOException {
@@ -63,7 +74,11 @@ public class IOUtils {
int len;
while ((len = is.read(buffer)) > 0)
bos.write(buffer, 0, len);
return bos.toByteArray();
var bytes = bos.toByteArray();
bos.close();
return bytes;
}
public static void writeToFile(File file, String content) throws IOException {
@@ -73,6 +88,7 @@ public class IOUtils {
public static void writeToFile(File file, byte[] content) throws IOException {
FileOutputStream fos = new FileOutputStream(file);
fos.write(content);
fos.close();
}
public static void deleteRecursive(File f) {
@@ -101,4 +117,8 @@ public class IOUtils {
return size;
}
public static String getValidFileName(String fileName) {
return fileName.replaceAll("[\\\\/:*?\"<>|]", "");
}
}
@@ -43,9 +43,7 @@ public class LifecycleUtils {
@Override // android.app.Application.ActivityLifecycleCallbacks
public void onActivityCreated(Activity activity, Bundle bundle) {
if (activity instanceof VKActivity) {
activities.add(activity);
}
activities.add(activity);
}
@Override // android.app.Application.ActivityLifecycleCallbacks
@@ -56,7 +54,7 @@ public class LifecycleUtils {
}
public static Activity getCurrentActivity() {
return activities.size() > 0 ? activities.get(0) : null;
return activities.size() > 0 ? activities.get(activities.size() - 1) : null;
}
public static void restartApplication() {
@@ -104,10 +104,18 @@ public class Preferences {
return getBoolValue("__dbg_no_slider_ads", true);
}
public static boolean autocache() {
return getBoolValue("autocache", false);
}
public static boolean adsstories() {
return getBoolValue("adsstories", true);
}
public static boolean wbios() {
return getBoolValue("wbios", false);
}
public static boolean videofeed() {
return getBoolValue("__dbg_disable_video_feed", false);
}
@@ -7,6 +7,7 @@ import static ru.vtosters.lite.utils.AndroidUtils.getCenterScreenCoords;
import static ru.vtosters.lite.utils.AndroidUtils.getIdentifier;
import static ru.vtosters.lite.utils.AndroidUtils.getPrefsValue;
import static ru.vtosters.lite.utils.AndroidUtils.getResources;
import static ru.vtosters.lite.utils.LifecycleUtils.*;
import static ru.vtosters.lite.utils.Preferences.color_grishka;
import static ru.vtosters.lite.utils.Preferences.dockbar_accent;
import static ru.vtosters.lite.utils.Preferences.milkshake;
@@ -46,11 +47,22 @@ public class ThemesUtils {
);
public static void applyTheme(VKTheme theme) {
VKThemeHelper.theme(theme, LifecycleUtils.getCurrentActivity(), getCenterScreenCoords());
var currentActivity = LifecycleUtils.getCurrentActivity();
if (currentActivity != null) {
try {
VKThemeHelper.theme(theme, currentActivity, getCenterScreenCoords());
} catch (Exception e) {
e.printStackTrace();
}
}
} // Apply VKTheme and ImTheme (hard applying without dynamic theme changing)
public static void setTheme(VKTheme theme, Activity activity) {
if (activity == null) activity = LifecycleUtils.getCurrentActivity();
if (activity == null) {
activity = getCurrentActivity();
if (activity == null) return;
}
VKThemeHelper.theme(theme, activity, getCenterScreenCoords());
ThemeTracker.a();
isLoaded = false;
@@ -59,7 +71,11 @@ public class ThemesUtils {
} // apply changed theme
public static void setThemeFL(VKTheme theme, Activity activity, float[] fl) {
if (activity == null) activity = LifecycleUtils.getCurrentActivity();
if (activity == null) {
activity = getCurrentActivity();
if (activity == null) return;
}
VKThemeHelper.theme(theme, activity, fl);
ThemeTracker.a();
isLoaded = false;
@@ -326,20 +342,21 @@ public class ThemesUtils {
}
public static void setNeededTheme(Activity activity) {
var currentTheme = getCurrentTheme();
if (milkshake()) {
if (getCurrentTheme() == VKTheme.VKAPP_LIGHT) {
if (currentTheme == VKTheme.VKAPP_LIGHT) {
setTheme(VKTheme.VKAPP_MILK_LIGHT, activity);
}
if (getCurrentTheme() == VKTheme.VKAPP_DARK) {
if (currentTheme == VKTheme.VKAPP_DARK) {
setTheme(VKTheme.VKAPP_MILK_DARK, activity);
}
} else {
if (getCurrentTheme() == VKTheme.VKAPP_MILK_LIGHT) {
if (currentTheme == VKTheme.VKAPP_MILK_LIGHT) {
setTheme(VKTheme.VKAPP_LIGHT, activity);
}
if (getCurrentTheme() == VKTheme.VKAPP_MILK_DARK) {
if (currentTheme == VKTheme.VKAPP_MILK_DARK) {
setTheme(VKTheme.VKAPP_DARK, activity);
}
}
@@ -5,6 +5,7 @@ import static ru.vtosters.lite.proxy.ProxyUtils.getOauth;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import static ru.vtosters.lite.utils.AndroidUtils.edit;
import static ru.vtosters.lite.utils.AndroidUtils.getPrefsValue;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
import android.util.Log;
@@ -33,7 +34,7 @@ public class VKMeHandler{
setToken(mainJson.getString("access_token"));
} else if (mainJson.has("error_description")){
Log.d("VKMeHandler", mainJson.getString("error_description"));
sendToast("Ошибка входа: " + mainJson.getString("error_description"));
sendToast(getString("auth_error_vtl") + ": " + mainJson.getString("error_description"));
}
} catch (JSONException e) {
+1 -1
View File
@@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.2'
classpath 'com.android.tools.build:gradle:7.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
-1
View File
@@ -1 +0,0 @@
2966f42770fa9480b905416ecea04a6c4b401193
BIN
View File
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
0840244ca18d071426e1a4cf350bdb0d446ca68f
@@ -1 +0,0 @@
6278c6512808b17c9470c0b231c40ab1d01ef877
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?vk_search_bar_field_background" />
<corners
android:radius="8dp"
android:topLeftRadius="20dp"
android:topRightRadius="20dp"
android:bottomLeftRadius="20dp"
android:bottomRightRadius="20dp" />
<stroke
android:width="0.8dp"
android:color="?vk_search_bar_field_background" />
</shape>
+276
View File
@@ -0,0 +1,276 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:id="@id/comment_restriction_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:layout_width="32.0dip"
android:layout_height="32.0dip"
android:layout_gravity="center|start"
android:layout_marginStart="12.0dip"
android:adjustViewBounds="true"
android:importantForAccessibility="no"
android:scaleType="centerInside"
android:src="@drawable/ic_bullying_32" />
<TextView
android:id="@id/comment_restriction_text"
style="@style/VKUIText.Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="56.0dip"
android:layout_marginTop="12.0dip"
android:layout_marginEnd="16.0dip"
android:layout_marginBottom="12.0dip"
android:fontFamily="@font/roboto_regular" />
</FrameLayout>
<View
android:id="@id/comment_restriction_attach_divider"
android:layout_width="match_parent"
android:layout_height="1.0dip"
android:layout_marginLeft="12.0dip"
android:layout_marginRight="12.0dip"
android:background="?separator_alternate"
android:visibility="gone" />
<LinearLayout
android:id="@id/write_bar_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.vk.im.ui.views.ReplyView
android:id="@id/write_bar_reply_content"
style="@style/VkIm.ReplyView.Default"
android:layout_width="0.0dip"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="12.0dip"
android:layout_marginTop="10.0dip"
android:layout_marginEnd="10.0dip"
android:layout_marginBottom="10.0dip"
android:layout_weight="1.0" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@id/write_bar_reply_close"
android:layout_width="48.0dip"
android:layout_height="48.0dip"
android:layout_marginTop="4.0dip"
android:layout_marginEnd="2.0dip"
android:layout_marginBottom="4.0dip"
android:adjustViewBounds="true"
android:background="?selectableItemBackground"
android:scaleType="centerInside"
android:src="@drawable/ic_cancel_24"
android:tint="?icon_outline_secondary" />
</LinearLayout>
<com.vtosters.lite.ui.AttachmentsEditorView
android:id="@id/write_bar_attach_editor"
android:layout_width="match_parent"
android:layout_height="91.0dip"
android:paddingLeft="16.0dip"
android:paddingRight="16.0dip" />
<View
android:id="@id/write_bar_attach_divider"
android:layout_width="match_parent"
android:layout_height="0.5dip"
android:layout_marginLeft="12.0dip"
android:layout_marginRight="12.0dip"
android:background="?separator_alternate"
android:visibility="gone" />
<FrameLayout
android:id="@id/write_bar_edit_area_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true">
<include
android:id="@id/writebar_waveform_area"
layout="@layout/write_bar_waveform"
android:visibility="invisible" />
<include
android:id="@id/writebar_audio_area"
layout="@layout/write_bar_audio"
android:visibility="invisible" />
<TextView
android:id="@id/writebar_record_tip"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="2"
android:paddingStart="@dimen/voice_rec_tip_left_padding_max"
android:paddingEnd="@dimen/voice_rec_tip_right_padding"
android:text="@string/audio_message_hold_to_record"
android:textColor="?field_text_placeholder"
android:textSize="18.0sp"
android:visibility="invisible" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@id/writebar_text_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="52.0dip"
android:orientation="horizontal">
<ImageView
android:id="@id/writebar_attach"
android:layout_width="44.0dip"
android:layout_height="44.0dip"
android:layout_gravity="center_vertical"
android:layout_marginLeft="6.0dip"
android:layout_marginTop="2.0dip"
android:layout_marginRight="6.0dip"
android:layout_marginBottom="2.0dip"
android:background="@drawable/highlight_icon"
android:contentDescription="@string/accessibility_msg_send_attach"
android:padding="8.0dip"
android:src="?im_ic_add_circle"
android:tint="?accent" />
<com.vk.im.ui.views.RichEditText
android:id="@id/writebar_edit"
android:layout_width="0.0dip"
android:layout_height="wrap_content"
android:layout_marginTop="5.0dip"
android:layout_marginBottom="5.0dip"
android:layout_weight="1.0"
android:background="@drawable/roundmsg"
android:ems="10"
android:gravity="center_vertical"
android:hint="@string/msg_hint"
android:inputType="textCapSentences|textMultiLine"
android:maxHeight="110.0dip"
android:minHeight="42.0dip"
android:paddingLeft="14.0dip"
android:paddingTop="8.0dip"
android:paddingRight="8.0dip"
android:paddingBottom="10.0dip"
android:textColor="?text_primary"
android:textColorHint="?text_placeholder"
android:textSize="18.0dip" />
<ImageView
android:id="@id/writebar_bot_keyboard"
android:layout_width="44.0dip"
android:layout_height="44.0dip"
android:layout_gravity="center_vertical"
android:layout_marginLeft="6.0dip"
android:layout_marginTop="2.0dip"
android:layout_marginBottom="2.0dip"
android:background="@drawable/highlight_icon"
android:contentDescription="@string/accessibility_msg_bot_keyboard"
android:scaleType="center"
android:src="?im_ic_bot_keyboard"
android:visibility="gone"
android:tint="?accent" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="6.0dip">
<ImageView
android:id="@id/writebar_emoji"
android:layout_width="44.0dip"
android:layout_height="44.0dip"
android:layout_marginTop="2.0dip"
android:layout_marginBottom="2.0dip"
android:background="@drawable/highlight_icon"
android:contentDescription="@string/accessibility_msg_send_stickers"
android:scaleType="center"
android:src="?im_ic_emoji"
android:tint="?accent" />
<ImageView
android:id="@id/writebar_emoji_new"
android:layout_width="10.0dip"
android:layout_height="10.0dip"
android:layout_gravity="center|end|top"
android:layout_marginTop="10.0dip"
android:layout_marginEnd="10.0dip"
android:background="@drawable/ic_sticker_new_badge_composite_full"
android:contentDescription="@string/accessibility_msg_send_stickers" />
</FrameLayout>
</LinearLayout>
<FrameLayout
android:id="@id/writebar_action"
android:layout_width="44.0dip"
android:layout_height="44.0dip"
android:layout_gravity="center|end"
android:layout_marginTop="2.0dip"
android:layout_marginEnd="6.0dip"
android:layout_marginBottom="2.0dip">
<ImageView
android:id="@id/writebar_send"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/highlight_icon"
android:contentDescription="@string/accessibility_msg_send_msg"
android:scaleType="center"
android:visibility="invisible" />
<ImageView
android:id="@id/writebar_record"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/highlight_icon"
android:clickable="true"
android:contentDescription="@string/accessibility_msg_record_audiomsg"
android:hapticFeedbackEnabled="false"
android:scaleType="center"
android:soundEffectsEnabled="false"
android:src="?im_ic_microphone"
android:tint="?accent" />
<com.vk.core.view.UploadProgressView
android:id="@id/cancelable_progress"
android:layout_width="44.0dip"
android:layout_height="44.0dip"
android:layout_gravity="end|center"
android:background="@drawable/highlight_icon"
android:contentDescription="@string/accessibility_cancel"
android:visibility="gone"
app:upload_cancelIcon="@drawable/ic_cancel_16"
app:upload_cancelIconSize="16.0dip"
app:upload_cancelIconTintColor="?accent"
app:upload_cancelIconVisible="true"
app:upload_layerColor="@color/transparent"
app:upload_lineColor="?accent"
app:upload_lineDownScaleThreshold="22.0dip"
app:upload_lineRounded="true"
app:upload_lineWidth="2.0dip"
app:upload_progressInverse="true"
app:upload_progressMax="100"
app:upload_progressMin="0"
app:upload_progressMovement="false"
app:upload_progressRadius="22.0dip"
app:upload_progressValue="0" />
</FrameLayout>
</FrameLayout>
</FrameLayout>
</merge>
+5 -2
View File
@@ -559,8 +559,6 @@
<item>Fade</item>
<item>AndroidX</item>
<item>Слайд</item>
<item>Системный</item>
<item>Системный (task)</item>
</string-array>
<string-array name="language">
<item>Системный</item>
@@ -676,4 +674,9 @@
<item>Затемнить</item>
<item>Осветлить</item>
</string-array>
<string-array name="msg_plurals">
<item>сообщение</item>
<item>сообщения</item>
<item>сообщений</item>
</string-array>
</resources>
+119 -3
View File
@@ -5351,7 +5351,7 @@
<string name="vtlfeed">Лента</string>
<string name="vtlinterface">Интерфейс</string>
<string name="vtlmedia">Медиа контент</string>
<string name="vtlmedia">Медиаконтент</string>
<string name="vtlproxy">Прокси</string>
<string name="vtlother">Другое</string>
<string name="vtlactivity">Активность</string>
@@ -5518,7 +5518,7 @@
<string name="sett_compress_photo_summ">Отключение значительно улучшит качество загружаемых фотографий</string>
<string name="sett_compress_video_summ">Отключение значительно улучшит качество загружаемых видео</string>
<string name="lastfm_auth_title">Аккаунт</string>
<string name="lastfm_auth_error">Ошибка входа</string>
<string name="auth_error_vtl">Ошибка входа</string>
<string name="lastfm_auth_summ">Вы не вошли в аккаунт</string>
<string name="lastfm_logout_success">Вы успешно вышли из учетной записи</string>
<string name="lastfm_enabled_title">Включить скробблинг</string>
@@ -5953,8 +5953,12 @@
<string name="download_mp3">Скачать как mp3</string>
<string name="remove_from_cache">Удалить из кеша</string>
<string name="play_in_vkx">Воспроизвести в VK X</string>
<string name="add_to_cache">Добавить в кеш</string>
<string name="remove_from_cache_vkx">Удалить из VK X кеша</string>
<string name="add_to_cache_vkx">Добавить в кеш VK X</string>
<string name="play_in_vkx">Воспроизвести в VK X</string>
<string name="thanksfordonate">Спасибо тебе за поддержку проекта!</string>
<string name="getdonate">Поддержи проект донатом и получи бонусы</string>
@@ -6185,4 +6189,116 @@
<string name="download_dl">Скачать диалог</string>
<string name="download_dl_error">Произошла ошибка при попытке скачать диалог</string>
<string name="saved_as">Сохранено как</string>
<string name="settings_reset_confirm">Вы действительно хотите сбросить настройки?</string>
<string name="system_theme_warning">У вас включена системная тема \nПри переключении темы системная тема будет отключена \n\nВы действительно хотите отключить её?</string>
<string name="compress_wallpaper_title">Сжимать обои</string>
<string name="compress_wallpaper_summ">Значительно ускоряет открытие чатов</string>
<string name="from_lower">от</string>
<string name="chat_export_error">ошибка</string>
<string name="chat_export_title">Экспорт из VTLite</string>
<string name="chat_export_header">Экспорт истории чата</string>
<string name="chat_export_avatar_update">Обновил фотографию беседы</string>
<string name="chat_export_avatar_remove">Удалил фотографию беседы</string>
<string name="chat_export_chat_created">Создал беседу</string>
<string name="chat_export_name_changed">Сменил название беседы</string>
<string name="chat_export_to">на</string>
<string name="chat_export_user_added">Добавил</string>
<string name="chat_export_user_kicked">Исключил</string>
<string name="chat_export_type_group">группу</string>
<string name="chat_export_type_user">пользователя</string>
<string name="chat_export_msg_pinned">Закрепил сообщение</string>
<string name="chat_export_msg_unpinned">Открепил сообщение</string>
<string name="chat_export_join_by_link">Вступил в беседу по ссылке</string>
<string name="chat_export_chat_screenshot">Сделал скриншот беседы</string>
<string name="chat_export_type_photo">фотография</string>
<string name="chat_export_type_doc">документ</string>
<string name="chat_export_type_link">ссылка</string>
<string name="chat_export_type_wallpost">запись на стене</string>
<string name="chat_export_type_audiomsg">голосовое сообщение</string>
<string name="chat_export_type_audio">аудиозапись</string>
<string name="chat_export_type_video">видеозапись</string>
<string name="chat_export_type_wallreply">комментарий на стене</string>
<string name="chat_export_type_story">история</string>
<string name="chat_export_type_podcast">подкаст</string>
<string name="chat_export_type_miniapp">мини-приложение</string>
<string name="chat_export_type_playlist">плейлист</string>
<string name="chat_export_poll_anon">анонимный</string>
<string name="chat_export_poll_publ">публичный</string>
<string name="chat_export_type_poll">опрос</string>
<string name="chat_export_type_market">товар</string>
<string name="chat_export_poll_voted">Проголосовало</string>
<string name="chat_export_users_name">чел.</string>
<string name="chat_export_video_nolinks">ссылки отсутствуют</string>
<string name="delete_video_history_confirm">Вы действительно хотите очистить историю видео?</string>
<string name="delete_video_history_error">Ошибка при очистке истории. Подробности в логах</string>
<string name="wallpaper_size_limit">Размер файла превышает лимит 6 MB</string>
<string name="contact_sync_error">Ошибка установки контактов</string>
<string name="contact_permission_toast">Для работы данной функции необходимо выдать разрешение на чтение контактов</string>
<string name="contact_info_loading">Загрузка данных о контактах...</string>
<string name="contact_sync_title">Синхронизация контактов</string>
<string name="contact_sync_state">Статус синхронизации</string>
<string name="contact_sync_enabled">Включена</string>
<string name="contact_sync_disabled">Отключена</string>
<string name="donotgetads_title">Не пытаться получать рекламу</string>
<string name="donotgetads_summ">Не запрашивать рекламу и рекомендации\n\nМогут быть проблемы</string>
<string name="force_disable_psm_title">Не отправлять информацию о батарее</string>
<string name="force_disable_psm_summ">Включение этой функции скроет информацию о состоянии вашего устройства в режиме экономии батареи и ВКонтакте будет отправлять вам медиаконтент без сильного сжатия и блюра</string>
<string name="track_bd_insert_err">Ошибка при записи трека в БД!</string>
<string name="cached_tracks_deleted">Кешированные песни удалены</string>
<string name="cached_track">Закешированный трек</string>
<string name="cached_tracks_title">Кешированные песни</string>
<string name="saved_tracks_title">Сохраненные треки</string>
<string name="cached_tracks_desc">Это - список всех кешированных треков. Данный плейлист НЕ СУЩЕСТВУЕТ в вашем профиле.\n\nПри отсутствии соединения с Интернетом данный плейлист превратится в обычный список треков.</string>
<string name="cached_album_warning">Это кешированный альбом!</string>
<string name="cached_playlist_loading">Загрузка плейлиста</string>
<string name="cached_content_title">Кешированный контент</string>
<string name="cached_content_empty">Похоже, тут пусто</string>
<string name="cached_content_empty_desc">У вас отсутствует кешированный контент</string>
<string name="cached_tracks_remove_confirm">Вы действительно хотите удалить все кешированные песни?</string>
<string name="vkx_integration_title">Включите интеграцию VK X!</string>
<string name="vkx_integration_desc">Получите доступ к хранилищу треков и возможности включать/кешировать треки из VTLite в VK X.\n\nПодробнее - в Настройки > Медиа контент > VK X</string>
<string name="music_downloading">Загрузка музыки</string>
<string name="music_downloading_progress">Прогресс загрузки музыки</string>
<string name="playlist_downloading">Загрузка плейлиста</string>
<string name="playlist_downloading_progress">Прогресс загрузки плейлиста</string>
<string name="caching_track_or_pic">Кеширование трека / обложки</string>
<string name="cached_tracks_counter">Всего закешировано %1$d песен</string>
<string name="autocache_title">Автокеширование песен</string>
<string name="autocache_summ">Автоматически кешировать все воспроизведенные вами песни</string>
<string name="playlist_download_notf">Загружено песен: %1$d из %2$d</string>
<string name="all_audio">Все аудио</string>
<string name="notf_audio_dl_user">Скачивание всех аудио пользователя</string>
<string name="no_cache_error">У вас отсутствуют кешированные песни</string>
<string name="download_method">Способ скачивания</string>
<string name="download_method_desc">Выберите способ скачивания всех своих аудио (без плейлистов)</string>
<string name="download_method_cache">В кеш</string>
<string name="download_method_mp3">Как .mp3</string>
<string name="audio_download_title">Скачивание всех своих аудио</string>
<string name="app_activities_error">Ошибка в работе приложения. Перезапустите приложение</string>
</resources>
+2
View File
@@ -103,6 +103,7 @@
<style name="Vk.Theme.VkApp" parent="@style/Vk.Theme">
<item name="android:windowLayoutInDisplayCutoutMode">default</item>
<item name="im_ic_add_circle">@drawable/ic_add_circle_outline_28</item>
<item name="im_avatars_combine">true</item>
<item name="im_bg_chat">?background_content</item>
<item name="im_bg_install_vkme_btn_bg">@drawable/bg_install_vkme_vkapp</item>
@@ -182,6 +183,7 @@
<style name="Vk.Theme.VkMe" parent="@style/Vk.Theme">
<item name="android:windowLayoutInDisplayCutoutMode">default</item>
<item name="im_ic_add_circle">@drawable/ic_add_circle_outline_28</item>
<item name="im_avatars_combine">false</item>
<item name="im_bg_sticky_date">@drawable/bg_sticky_date</item>
<item name="im_button_all_caps">false</item>
+5 -4
View File
@@ -656,8 +656,6 @@
<item>Fade</item>
<item>AndroidX</item>
<item>Slide</item>
<item>System</item>
<item>System task</item>
</string-array>
<string-array name="animations_values">
<item>noanim</item>
@@ -665,8 +663,6 @@
<item>frag_fade</item>
<item>frag_enter</item>
<item>slide</item>
<item>system</item>
<item>system_task</item>
</string-array>
<string-array name="animations2">
<item>No Animation</item>
@@ -878,4 +874,9 @@
<item>Dim</item>
<item>Lighten</item>
</string-array>
<string-array name="msg_plurals">
<item>message</item>
<item>messages</item>
<item>messages</item>
</string-array>
</resources>
+1
View File
@@ -803,6 +803,7 @@
<attr name="id_image" format="reference" />
<attr name="id_time" format="reference" />
<attr name="ignore_tint" format="boolean" />
<attr name="im_ic_add_circle" format="reference" />
<attr name="im_app_swipe_background_end" format="color" />
<attr name="im_app_swipe_background_start" format="color" />
<attr name="im_attach_tint" format="reference|color" />
+4
View File
@@ -3915,4 +3915,8 @@
<item name="download_video" type="id" />
<item name="savedmsg" type="id" />
<item name="play_in_vkx" type="id" />
<item name="add_to_cache" type="id" />
<item name="download_mp3" type="id" />
<item name="remove_from_cache_vkx" type="id" />
<item name="add_to_cache_vkx" type="id" />
</resources>
+118 -2
View File
@@ -2834,7 +2834,7 @@ or deleted"</string>
<string name="place">Map</string>
<string name="places">Places</string>
<string name="play_queue">Up next</string>
<string name="player_download_finished">Download complete</string>
<string name="player_download_finished">Download completed</string>
<string name="player_download_pause">Pause</string>
<string name="player_download_repeat">Repeat</string>
<string name="player_download_resume">Resume</string>
@@ -5652,7 +5652,7 @@ Expand text..."</string>
<string name="sett_compress_photo_summ">Disabling will significantly improve the quality of uploaded photos</string>
<string name="sett_compress_video_summ">Disabling will significantly improve the quality of uploaded videos</string>
<string name="lastfm_auth_title">Account</string>
<string name="lastfm_auth_error">Login failed</string>
<string name="auth_error_vtl">Login failed</string>
<string name="lastfm_auth_summ">You are not logged in</string>
<string name="lastfm_logout_success">You have successfully logged out</string>
<string name="lastfm_enabled_title">Enable scrobbling</string>
@@ -6088,6 +6088,10 @@ May look like a fake leaving the online"</string>
<string name="download_mp3">Download as mp3</string>
<string name="remove_from_cache">Delete from cache</string>
<string name="add_to_cache">Add to cache</string>
<string name="remove_from_cache_vkx">Delete from VK X cache</string>
<string name="add_to_cache_vkx">Add to VK X cache</string>
<string name="play_in_vkx">Play the track in VK X</string>
@@ -6323,4 +6327,116 @@ May look like a fake leaving the online"</string>
<string name="download_dl">Download dialog</string>
<string name="download_dl_error">An error occurred while trying to download the dialog</string>
<string name="saved_as">Saved as</string>
<string name="settings_reset_confirm">Are you sure you want to reset your settings?</string>
<string name="system_theme_warning">You have the system theme enabled \nWhen you switch the theme, the system theme will be disabled \n\nAre you sure you want to disable it?</string>
<string name="compress_wallpaper_title">Compress wallpaper</string>
<string name="compress_wallpaper_summ">Greatly speeds up opening chats</string>
<string name="from_lower">from</string>
<string name="chat_export_error">error</string>
<string name="chat_export_title">Export from VTLite</string>
<string name="chat_export_header">Export chat history</string>
<string name="chat_export_avatar_update">Updated the chat photo</string>
<string name="chat_export_avatar_remove">Removed the chat photo</string>
<string name="chat_export_chat_created">Created a chat</string>
<string name="chat_export_name_changed">Changed the name of the conversation</string>
<string name="chat_export_to">to</string>
<string name="chat_export_user_added">Added</string>
<string name="chat_export_user_kicked">Kicked</string>
<string name="chat_export_type_group">group</string>
<string name="chat_export_type_user">user</string>
<string name="chat_export_msg_pinned">Pinned message</string>
<string name="chat_export_msg_unpinned">Unpinned message</string>
<string name="chat_export_join_by_link">Joined a chat by link</string>
<string name="chat_export_chat_screenshot">Took a screenshot of the conversation</string>
<string name="chat_export_type_photo">photo</string>
<string name="chat_export_type_doc">document</string>
<string name="chat_export_type_link">link</string>
<string name="chat_export_type_wallpost">wall post</string>
<string name="chat_export_type_audiomsg">voice message</string>
<string name="chat_export_type_audio">audio</string>
<string name="chat_export_type_video">video</string>
<string name="chat_export_type_wallreply">wall reply</string>
<string name="chat_export_type_story">story</string>
<string name="chat_export_type_podcast">podcast</string>
<string name="chat_export_type_miniapp">miniapp</string>
<string name="chat_export_type_playlist">playlist</string>
<string name="chat_export_poll_anon">anonymous</string>
<string name="chat_export_poll_publ">public</string>
<string name="chat_export_type_poll">poll</string>
<string name="chat_export_type_market">product</string>
<string name="chat_export_poll_voted">Voted</string>
<string name="chat_export_users_name">user(s)</string>
<string name="chat_export_video_nolinks">no links</string>
<string name="delete_video_history_confirm">Are you sure you want to clear your video history?</string>
<string name="delete_video_history_error">Error clearing video history. Details in the logs</string>
<string name="wallpaper_size_limit">File size exceeds 6 MB limit</string>
<string name="contact_sync_error">Error syncing contacts</string>
<string name="contact_permission_toast">For this function to work, you must grant permission to read contacts</string>
<string name="contact_info_loading">Loading contact data...</string>
<string name="contact_sync_title">Sync contacts</string>
<string name="contact_sync_state">Sync status</string>
<string name="contact_sync_enabled">Enabled</string>
<string name="contact_sync_disabled">Disabled</string>
<string name="donotgetads_title">Don\'t try to get ads</string>
<string name="donotgetads_summ">Don\'t request ads and recommendations\n\nMay cause problems</string>
<string name="force_disable_psm_title">Don\'t send battery information</string>
<string name="force_disable_psm_summ">Enabling this feature will hide information about the state of your device in battery saving mode and VK will send you media content without strong compression and blur</string>
<string name="track_bd_insert_err">Error writing track to database!</string>
<string name="cached_tracks_deleted">Cached songs deleted</string>
<string name="cached_track">Cached track</string>
<string name="cached_tracks_title">Cached songs</string>
<string name="saved_tracks_title">Saved tracks</string>
<string name="cached_tracks_desc">This is a list of all cached tracks. This playlist DOES NOT EXIST on your profile.\n\nIf there is no Internet connection, this playlist will turn into a regular track list.</string>
<string name="cached_album_warning">This is a cached album!</string>
<string name="cached_playlist_loading">Playlist loading</string>
<string name="cached_content_title">Cached content</string>
<string name="cached_content_empty">Looks like it\'s empty</string>
<string name="cached_content_empty_desc">You have no cached content</string>
<string name="cached_tracks_remove_confirm">Are you sure you want to delete all cached songs?</string>
<string name="vkx_integration_title">Enable VK X integration!</string>
<string name="vkx_integration_desc">Get access to the track storage and the ability to include/cache tracks from VTLite to VK X.\n\nFor details, go to Settings > Media Content > VK X</string>
<string name="music_downloading">Downloading music</string>
<string name="music_downloading_progress">Music download progress</string>
<string name="playlist_downloading">Downloading a playlist</string>
<string name="playlist_downloading_progress">Playlist download progress</string>
<string name="caching_track_or_pic">Caching track / cover art</string>
<string name="cached_tracks_counter">Total %1$d song(s) cached</string>
<string name="autocache_title">Autocache songs</string>
<string name="autocache_summ">Automatically cache all songs you have played</string>
<string name="playlist_download_notf">Downloaded %1$d of %2$d tracks</string>
<string name="all_audio">All audio</string>
<string name="notf_audio_dl_user">Download all user audio</string>
<string name="no_cache_error">You have no cached songs</string>
<string name="download_method">Download method</string>
<string name="download_method_desc">Select how you want to download all your audios (without playlists)</string>
<string name="download_method_cache">To cache</string>
<string name="download_method_mp3">As .mp3</string>
<string name="audio_download_title">Download all your audio</string>
<string name="app_activities_error">An application error has occurred. Restart the application</string>
</resources>
+7 -2
View File
@@ -134,7 +134,12 @@
<com.vtosters.lite.ui.MaterialSwitchPreference
android:defaultValue="false"
android:key="donotgetads"
android:summary="Не запрашивать рекламу и рекомендации\n\nМогуть быть проблемы"
android:title="Не пытаться получать рекламу" />
android:summary="@string/donotgetads_summ"
android:title="@string/donotgetads_title" />
<com.vtosters.lite.ui.MaterialSwitchPreference
android:defaultValue="false"
android:key="force_disable_psm"
android:summary="@string/force_disable_psm_summ"
android:title="@string/force_disable_psm_title" />
</PreferenceCategory>
</PreferenceScreen>
+11
View File
@@ -48,6 +48,17 @@
android:title="@string/lastfm_enabled_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/vtl_music_settings">
<Preference
android:key="cached_tracks"
android:title="@string/cached_tracks_title"/>
<Preference
android:key="audio_download"
android:title="@string/audio_download_title"/>
<com.vtosters.lite.ui.MaterialSwitchPreference
android:defaultValue="false"
android:key="autocache"
android:summary="@string/autocache_summ"
android:title="@string/autocache_title" />
<com.vtosters.lite.ui.SummaryListPreference
android:defaultValue="default"
android:dialogTitle="@string/musicdefcatalog_dialogTitle"
+4
View File
@@ -14,6 +14,10 @@
android:key="convBarRecomm"
android:summary="@string/convBarRecomm_summ"
android:title="@string/convBarRecomm_title" />
<com.vtosters.lite.ui.MaterialSwitchPreference
android:defaultValue="false"
android:key="wbios"
android:title="Панель ввода с iOS" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/vk_messenger" android:key="vkmesett">
<com.vtosters.lite.ui.MaterialSwitchPreference
+2 -2
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Синхронизация контактов">
<PreferenceCategory android:title="@string/contact_sync_title">
<Preference
android:key="contacts_sync"
android:title="Синхронизация контактов" />
android:title="@string/contact_sync_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/other_fix_app">
<Preference
@@ -28,42 +28,50 @@
iput-object p1, p0, Lcom/vk/api/discover/DiscoverCategoriesGet;->F:Lcom/vk/dto/discover/DiscoverIntent;
# .line 2
# invoke-static {}, Lcom/vk/dto/discover/DiscoverItem$Template;->values()[Lcom/vk/dto/discover/DiscoverItem$Template;
#
# move-result-object p1
#
# .line 3
# new-instance v0, Ljava/util/ArrayList;
#
# array-length v1, p1
#
# invoke-direct {v0, v1}, Ljava/util/ArrayList;-><init>(I)V
#
# .line 4
# array-length v1, p1
#
# const/4 v2, 0x0
#
# const/4 v3, 0x0
#
# :goto_0
# if-ge v3, v1, :cond_0
#
# aget-object v4, p1, v3
#
# .line 5
# invoke-virtual {v4}, Lcom/vk/dto/discover/DiscoverItem$Template;->b()Ljava/lang/String;
#
# move-result-object v4
#
# invoke-interface {v0, v4}, Ljava/util/Collection;->add(Ljava/lang/Object;)Z
#
# add-int/lit8 v3, v3, 0x1
#
# goto :goto_0
#
# :cond_0
.line 2
invoke-static {}, Lcom/vk/dto/discover/DiscoverItem$Template;->values()[Lcom/vk/dto/discover/DiscoverItem$Template;
# ru.vtosters.lite.feature.discover.DiscoverTemplates. ArrayList<DiscoverItem.Template> getTemplates()
move-result-object p1
invoke-static {}, Lru/vtosters/lite/feature/discover/DiscoverTemplates;->getTemplates()Ljava/util/ArrayList;
.line 3
new-instance v0, Ljava/util/ArrayList;
move-result-object v0
array-length v1, p1
invoke-direct {v0, v1}, Ljava/util/ArrayList;-><init>(I)V
.line 4
array-length v1, p1
const/4 v2, 0x0
const/4 v3, 0x0
:goto_0
if-ge v3, v1, :cond_0
aget-object v4, p1, v3
.line 5
invoke-virtual {v4}, Lcom/vk/dto/discover/DiscoverItem$Template;->b()Ljava/lang/String;
move-result-object v4
invoke-interface {v0, v4}, Ljava/util/Collection;->add(Ljava/lang/Object;)Z
add-int/lit8 v3, v3, 0x1
goto :goto_0
:cond_0
const-string p1, ","
invoke-static {p1, v0}, Landroid/text/TextUtils;->join(Ljava/lang/CharSequence;Ljava/lang/Iterable;)Ljava/lang/String;
@@ -814,6 +814,11 @@
goto :goto_0
:cond_0
invoke-static {}, Lru/vtosters/lite/feature/discover/DiscoverTemplates;->getTemplates()Ljava/util/ArrayList;
move-result-object v1
const-string v0, ","
invoke-static {v0, v1}, Landroid/text/TextUtils;->join(Ljava/lang/CharSequence;Ljava/lang/Iterable;)Ljava/lang/String;
@@ -89,6 +89,8 @@
invoke-static {p1, v1}, Lkotlin/jvm/internal/Intrinsics;->a(Ljava/lang/Object;Ljava/lang/String;)V
invoke-static/range {p1 .. p1}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->injectIntoExistingCatalog(Lorg/json/JSONObject;)V
invoke-virtual {v0, p1}, Lcom/vk/catalog2/core/CatalogParser;->c(Lorg/json/JSONObject;)Lcom/vk/catalog2/core/api/dto/CatalogResponse;
move-result-object p1
@@ -814,7 +814,13 @@
.method private final e(Landroid/view/View;)Z
.locals 1
.line 2
if-nez p1, :cond_1
const/4 p1, 0x0
return p1
:cond_1
sget v0, Lcom/vk/core/ui/themes/ThemeBinder;->b:I
invoke-virtual {p1, v0}, Landroid/view/View;->getTag(I)Ljava/lang/Object;
@@ -788,6 +788,11 @@
.line 99
:cond_0
if-nez p0, :cond_1
return-void
:cond_1
sget-object v0, Lcom/vk/core/ui/themes/VKThemeHelper;->k:Lcom/vk/core/ui/themes/VKThemeHelper;
invoke-direct {v0}, Lcom/vk/core/ui/themes/VKThemeHelper;->m()Lcom/vk/core/ui/themes/ThemeBinder;
@@ -1072,12 +1072,16 @@
value = 0x15
.end annotation
.line 1
invoke-direct {p0}, Lcom/vk/core/util/DeviceState;->M()Landroid/os/PowerManager;
# .line 1
# invoke-direct {p0}, Lcom/vk/core/util/DeviceState;->M()Landroid/os/PowerManager;
move-result-object v0
# move-result-object v0
invoke-virtual {v0}, Landroid/os/PowerManager;->isPowerSaveMode()Z
# invoke-virtual {v0}, Landroid/os/PowerManager;->isPowerSaveMode()Z
# move-result v0
invoke-static {}, Lru/vtosters/lite/hooks/NewsfeedHook;->isPowerSaveMode()Z
move-result v0
@@ -4409,6 +4409,10 @@
move-result-object v6
invoke-static {v6}, Lru/vtosters/lite/hooks/MessagesHook;->fixCTX(Landroid/content/Context;)Landroid/content/Context;
move-result-object v6
iput-object v6, p0, Lcom/vk/im/ui/fragments/ChatFragment;->L:Landroid/content/Context;
.line 8
@@ -6119,6 +6123,10 @@
.line 1
iget-object v0, p0, Lcom/vk/im/ui/fragments/ChatFragment;->L:Landroid/content/Context;
invoke-static {v0}, Lru/vtosters/lite/hooks/MessagesHook;->fixCTX(Landroid/content/Context;)Landroid/content/Context;
move-result-object v0
const/4 v1, 0x0
if-eqz v0, :cond_1
@@ -7272,10 +7272,14 @@
.method public final setUIVisibility(Z)V
.locals 7
.line 1
iget-object v0, p0, Lcom/vk/libvideo/ui/VideoView;->n0:Landroid/os/PowerManager;
# .line 1
# iget-object v0, p0, Lcom/vk/libvideo/ui/VideoView;->n0:Landroid/os/PowerManager;
invoke-virtual {v0}, Landroid/os/PowerManager;->isPowerSaveMode()Z
# invoke-virtual {v0}, Landroid/os/PowerManager;->isPowerSaveMode()Z
# move-result v0
invoke-static {}, Lru/vtosters/lite/hooks/NewsfeedHook;->isPowerSaveMode()Z
move-result v0
@@ -83,15 +83,18 @@
.locals 1
.line 21
new-instance v0, Lcom/vk/music/search/MusicSearchFragment$a;
# ru.vtosters.lite.feature.music.MusicArtistSelectorHook.navigateToSearch(Context context, String artist)
invoke-static {p1, p2}, Lru/vtosters/lite/feature/music/MusicArtistSelectorHook;->navigateToSearch(Landroid/content/Context;Ljava/lang/String;)V
invoke-direct {v0}, Lcom/vk/music/search/MusicSearchFragment$a;-><init>()V
.line 22
invoke-virtual {v0, p2}, Lcom/vk/music/search/MusicSearchFragment$a;->b(Ljava/lang/String;)Lcom/vk/music/search/MusicSearchFragment$a;
.line 23
invoke-virtual {v0, p1}, Lcom/vk/navigation/Navigator;->a(Landroid/content/Context;)V
# new-instance v0, Lcom/vk/music/search/MusicSearchFragment$a;
#
# invoke-direct {v0}, Lcom/vk/music/search/MusicSearchFragment$a;-><init>()V
#
# .line 22
# invoke-virtual {v0, p2}, Lcom/vk/music/search/MusicSearchFragment$a;->b(Ljava/lang/String;)Lcom/vk/music/search/MusicSearchFragment$a;
#
# .line 23
# invoke-virtual {v0, p1}, Lcom/vk/navigation/Navigator;->a(Landroid/content/Context;)V
return-void
.end method
@@ -366,6 +366,8 @@
.line 30
iget v2, v2, Lcom/vk/dto/music/Playlist;->O:I
const v2, 0x0
if-lez v2, :cond_7
new-instance v2, Lcom/vk/music/bottomsheets/a/MusicAction;
@@ -462,6 +464,12 @@
invoke-interface {v1, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z
:cond_8
iget-object v2, v0, Lcom/vk/music/bottomsheets/playlist/PlaylistBottomSheet;->b:Lcom/vk/dto/music/Playlist;
invoke-static {v1, v2}, Lru/vtosters/lite/hooks/ui/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;Lcom/vk/dto/music/Playlist;)Ljava/util/ArrayList;
move-result-object v1
return-object v1
.end method
@@ -132,13 +132,22 @@
.end method
.method public a(Lcom/vk/music/bottomsheets/a/MusicAction;Lcom/vk/dto/music/Playlist;)Z
.locals 2
.locals 4
.line 6
invoke-virtual {p1}, Lcom/vk/music/bottomsheets/a/MusicAction;->a()I
move-result p1
invoke-static {p1, p2}, Lru/vtosters/lite/hooks/ui/MusicBottomSheetHook;->injectOnClick(ILcom/vk/dto/music/Playlist;)Z
move-result v3
if-eqz v3, :cond_7
goto/16 :goto_0
:cond_7
const/4 p2, 0x0
sparse-switch p1, :sswitch_data_0
@@ -153,7 +162,7 @@
iget-object v0, p0, Lcom/vk/music/bottomsheets/playlist/PlaylistBottomSheetClickListener;->e:Lcom/vk/dto/music/Playlist;
invoke-interface {p1, p2, v0}, Lcom/vk/music/bottomsheets/playlist/PlaylistBottomSheetModel;->a(Landroid/content/Context;Lcom/vk/dto/music/Playlist;)V
invoke-static/range {v0 .. v0}, Lru/vtosters/lite/music/cache/injectors/PlaylistInjector;->injectDownloadPlaylist(Lcom/vk/dto/music/Playlist;)V
goto/16 :goto_0
@@ -716,7 +716,7 @@
move-result-object v3
invoke-static {p1, v0, v2, v3}, Lru/vtosters/lite/hooks/ui/MusicBottomSheetHook;->tryPlayInVKX(ILcom/vk/dto/music/MusicTrack;Lcom/vk/music/common/MusicPlaybackLaunchContext;Lcom/vk/dto/music/Playlist;)Z
invoke-static {p1, v0, v2, v3}, Lru/vtosters/lite/hooks/ui/MusicBottomSheetHook;->injectOnClick(ILcom/vk/dto/music/MusicTrack;Lcom/vk/music/common/MusicPlaybackLaunchContext;Lcom/vk/dto/music/Playlist;)Z
move-result v3
@@ -569,7 +569,7 @@
.method public a(Landroid/content/Context;Lcom/vk/dto/music/MusicTrack;Lcom/vk/music/common/BoomModel$From;Lcom/vk/music/common/MusicPlaybackLaunchContext;)V
.locals 1
invoke-static {p2}, Lru/vtosters/lite/downloaders/AudioDownloader;->downloadAudio(Lcom/vk/dto/music/MusicTrack;)V
invoke-static {p2}, Lru/vtosters/lite/downloaders/AudioDownloader;->cacheTrack(Lcom/vk/dto/music/MusicTrack;)V
return-void
@@ -62,52 +62,60 @@
if-nez v2, :cond_4
.line 4
new-instance v2, Lcom/vk/music/bottomsheets/a/MusicAction;
# .line 4
# new-instance v2, Lcom/vk/music/bottomsheets/a/MusicAction;
const v4, 0x7f0a087c
# const v4, 0x7f0a087c
const v3, 0x7f08043c
# const v3, 0x7f08043c
const v5, 0x7f08043a
# const v5, 0x7f08043a
invoke-virtual {v0, v3, v5}, Lcom/vk/music/bottomsheets/track/d/VkMusicTrackBottomSheetActionsFactory;->a(II)I
# invoke-virtual {v0, v3, v5}, Lcom/vk/music/bottomsheets/track/d/VkMusicTrackBottomSheetActionsFactory;->a(II)I
move-result v5
# move-result v5
const/4 v6, 0x0
# const/4 v6, 0x0
invoke-virtual/range {p1 .. p1}, Lcom/vk/dto/music/MusicTrack;->F1()Z
# invoke-virtual/range {p1 .. p1}, Lcom/vk/dto/music/MusicTrack;->F1()Z
move-result v3
# move-result v3
const v12, 0x7f060156
# const v12, 0x7f060156
const v13, 0x7f060077
# const v13, 0x7f060077
if-eqz v3, :cond_0
# if-eqz v3, :cond_0
const v7, 0x7f060156
# const v7, 0x7f060156
goto :goto_0
# goto :goto_0
:cond_0
const v7, 0x7f060077
# :cond_0
# const v7, 0x7f060077
:goto_0
const v8, 0x7f12087d
# :goto_0
# const v8, 0x7f12087d
const/4 v9, 0x0
# const/4 v9, 0x0
const/16 v10, 0x20
# const/16 v10, 0x20
const/4 v11, 0x0
# const/4 v11, 0x0
move-object v3, v2
# move-object v3, v2
invoke-direct/range {v3 .. v11}, Lcom/vk/music/bottomsheets/a/MusicAction;-><init>(IIIIIZILkotlin/jvm/internal/DefaultConstructorMarker;)V
# invoke-direct/range {v3 .. v11}, Lcom/vk/music/bottomsheets/a/MusicAction;-><init>(IIIIIZILkotlin/jvm/internal/DefaultConstructorMarker;)V
invoke-interface {v1, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z
# invoke-interface {v1, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z
move-object/from16 v10, p1
invoke-static {v1, v10}, Lru/vtosters/lite/hooks/ui/MusicBottomSheetHook;->hookDownloadBTN(Ljava/util/ArrayList;Lcom/vk/dto/music/MusicTrack;)Ljava/util/ArrayList;
move-result-object v1
move-object/from16 p1, v10
.line 5
sget-object v2, Lcom/vk/music/common/Music$a;->j:Lcom/vk/music/common/Music$a;
@@ -697,6 +705,10 @@
.line 31
:cond_9
invoke-static {v3, v1}, Lru/vtosters/lite/hooks/ui/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;Lcom/vk/dto/music/MusicTrack;)Ljava/util/ArrayList;
move-result-object v3
invoke-interface/range {p2 .. p2}, Lcom/vk/music/bottomsheets/track/MusicTrackBottomSheetModel1;->k()Z
move-result v2
@@ -738,11 +750,5 @@
:cond_a
:goto_3
# invoke static ru.vtosters.lite.hooks.ui.MusicBottomSheetHook.hook(ArrayList<MusicAction> actions)
invoke-static {v3}, Lru/vtosters/lite/hooks/ui/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;)Ljava/util/ArrayList;
move-result-object v3
return-object v3
.end method
@@ -65,6 +65,23 @@
}
.end annotation
invoke-static {}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->eligibleForOfflineCaching()Z
move-result v0
if-eqz v0, :cond_custom
invoke-virtual {p0}, Lcom/vk/catalog2/core/VkCatalogConfiguration;->j()Lcom/vk/catalog2/core/CatalogParser;
move-result-object v0
invoke-static {v0}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->createOfflineRx(Lcom/vk/catalog2/core/CatalogParser;)Lio/reactivex/Observable;
move-result-object v0
return-object v0
:cond_custom
.line 1
new-instance v0, Lcom/vk/catalog2/core/api/music/CatalogGetAudio;
@@ -211,8 +211,7 @@
if-eqz v1, :cond_0
.line 12
invoke-static {p1}, Lru/vtosters/lite/downloaders/AudioDownloader;->downloadAudio(Lcom/vk/dto/music/MusicTrack;)V
invoke-static {p1}, Lru/vtosters/lite/downloaders/AudioDownloader;->cacheTrack(Lcom/vk/dto/music/MusicTrack;)V
return-void
@@ -239,11 +239,22 @@
const/4 v1, 0x1
.line 14
invoke-static {p1}, Lru/vtosters/lite/music/cache/injectors/PlaylistInjector;->injectGetPlaylist(Lcom/vk/api/audio/AudioGetPlaylist;)Lio/reactivex/Observable;
move-result-object v2
if-eqz v2, :cond_custom
move-object p1, v2
goto :goto_20
:cond_custom
invoke-static {p1, v0, v1, v0}, Lcom/vk/api/base/ApiRequest;->d(Lcom/vk/api/base/ApiRequest;Lcom/vk/api/base/ApiThreadHolder;ILjava/lang/Object;)Lio/reactivex/Observable;
move-result-object p1
:goto_20
.line 15
new-instance v6, Lcom/vk/music/playlist/ModernPlaylistModel$d;
@@ -593,6 +593,10 @@
move-result-object v0
invoke-static/range {v0 .. v0}, Lru/vtosters/lite/music/cache/injectors/PlaylistInjector;->injectDownloadPlaylist(Lcom/vk/dto/music/Playlist;)V
return-void
if-eqz v0, :cond_1
.line 3
@@ -242,9 +242,7 @@
if-eqz v0, :cond_0
.line 6
invoke-static {v0, p1}, Lcom/vk/core/ui/themes/VKThemeHelper;->b(Landroid/app/Activity;[F)V
invoke-static {}, Lru/vtosters/lite/hooks/VKUIHook;->clearWebCache()V
invoke-static {v0, p1}, Lru/vtosters/lite/ui/dialogs/ThemeChanging;->changeTheme(Landroid/app/Activity;[F)V
:cond_0
return-void
@@ -5871,12 +5871,14 @@
.line 1
iget-object v0, p0, Lcom/vk/profile/ui/BaseProfileFragment;->P0:Landroid/view/View;
if-nez v0, :cond_229
return-void
:cond_229
invoke-static {v0}, Lcom/vk/core/ui/themes/VKThemeHelper;->a(Landroid/view/View;)V
.line 2
iget-object v0, p0, Lcom/vk/profile/ui/BaseProfileFragment;->P0:Landroid/view/View;
.line 3
invoke-virtual {v0}, Landroid/view/View;->getContext()Landroid/content/Context;
move-result-object v0
@@ -561,6 +561,18 @@
invoke-virtual {p0, p1, v0}, Landroidx/fragment/app/FragmentActivity;->startActivityForResult(Landroid/content/Intent;I)V
:goto_1
# ru.vtosters.lite.hooks.ui.SystemThemeChangerHook.onThemeChanged(activity.getResources().getConfiguration());
invoke-virtual {p0}, Lcom/vtosters/lite/MainActivity;->getResources()Landroid/content/res/Resources;
move-result-object v0
invoke-virtual {v0}, Landroid/content/res/Resources;->getConfiguration()Landroid/content/res/Configuration;
move-result-object v0
invoke-static {v0}, Lru/vtosters/lite/hooks/ui/SystemThemeChangerHook;->onThemeChanged(Landroid/content/res/Configuration;)V
return-void
.end method
@@ -7,7 +7,6 @@
.method public static a(Landroid/content/Context;)Ljava/io/File;
.locals 10
.line 5
invoke-static {p0}, Landroidx/core/content/ContextCompat;->getExternalCacheDirs(Landroid/content/Context;)[Ljava/io/File;
move-result-object v0
@@ -111,8 +110,22 @@
.end method
.method static a(Ljava/lang/String;)Ljava/io/File;
.locals 4
.locals 6
invoke-static {p0}, Lru/vtosters/lite/music/cache/FileCacheImplementation;->isTrackExist(Ljava/lang/String;)Z
move-result v5
if-eqz v5, :cond_228
.line 5
invoke-static {p0}, Lru/vtosters/lite/music/cache/FileCacheImplementation;->getTrackFile(Ljava/lang/String;)Ljava/io/File;
move-result-object v0
return-object v0
:cond_228
const/4 v0, 0x0
.line 26
@@ -452,6 +452,8 @@
.method public static a([BIIJ)V
.locals 10
return-void
move-wide v0, p3
move p3, p1
@@ -1314,7 +1314,9 @@
move-result-object p1
const v0, 0x7f0d06da
invoke-static {}, Lru/vtosters/lite/hooks/WritebarHook;->getWriteBar()I
move-result v0
.line 23
invoke-static {p1, v0, p0}, Landroid/widget/LinearLayout;->inflate(Landroid/content/Context;ILandroid/view/ViewGroup;)Landroid/view/View;