Compare commits
66
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5180f61d5 | ||
|
|
540fffdcc9 | ||
|
|
5357c60eaa | ||
|
|
c59f29121f | ||
|
|
bd38cab377 | ||
|
|
0058431590 | ||
|
|
bf4cf2be53 | ||
|
|
aac86a9952 | ||
|
|
ae00e72bac | ||
|
|
fea984f6fd | ||
|
|
d870c2f984 | ||
|
|
f7f43928cf | ||
|
|
1accb45f12 | ||
|
|
d9f53920e5 | ||
|
|
76d91332d7 | ||
|
|
728cbde683 | ||
|
|
60c98cd998 | ||
|
|
f1d106e0f0 | ||
|
|
c92194e90b | ||
|
|
91683af807 | ||
|
|
c5c898aa4e | ||
|
|
25634b2469 | ||
|
|
b8e764d09d | ||
|
|
17ed32857a | ||
|
|
d631d07e0f | ||
|
|
2ec4c69832 | ||
|
|
6426c965c6 | ||
|
|
4f6d410185 | ||
|
|
18a899f439 | ||
|
|
7e5b3fe233 | ||
|
|
4abd0eec6c | ||
|
|
cf17ed64c7 | ||
|
|
a2dd7c1e06 | ||
|
|
021fe66bec | ||
|
|
1b3896f4ca | ||
|
|
fc9ccf66d1 | ||
|
|
d205f2894b | ||
|
|
7afc04b2b2 | ||
|
|
584ee96ee0 | ||
|
|
66f7ea1b79 | ||
|
|
7b0a83458b | ||
|
|
cdaf7909d4 | ||
|
|
c70ab388c1 | ||
|
|
50c66c2703 | ||
|
|
357e520bb0 | ||
|
|
6379ce5741 | ||
|
|
02807739a1 | ||
|
|
40635ab75a | ||
|
|
32e1bfe6fd | ||
|
|
281a0e8499 | ||
|
|
220c61afe1 | ||
|
|
7a632d5224 | ||
|
|
3218c1a46b | ||
|
|
b2028d86ba | ||
|
|
45ed5879e6 | ||
|
|
48479dd064 | ||
|
|
3b556b81a1 | ||
|
|
526c9a607a | ||
|
|
81752895ab | ||
|
|
ad970d097a | ||
|
|
bc2010b49d | ||
|
|
ff4a4094ff | ||
|
|
3629e71098 | ||
|
|
b955ac355f | ||
|
|
409279eca0 | ||
|
|
ea0a0f1d93 |
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
* Стабильные сборки находятся в [Releases](https://github.com/vtosters/lite/releases)
|
* Стабильные сборки находятся в [Releases](https://github.com/vtosters/lite/releases)
|
||||||
|
|
||||||
Зеркало на нашем [<img src="https://gitea.com/assets/img/logo.svg" align="center" width="20" height="20"/> Git](https://git.maki.su/gdlbo/lite)
|
Зеркало на нашем [<img src="https://gitea.com/assets/img/logo.svg" align="center" width="20" height="20"/> Git](https://git.maki.su/vtosters/lite)
|
||||||
|
|
||||||
### Быстрая навигация
|
### Быстрая навигация
|
||||||
|
|
||||||
|
|||||||
+57
-71
@@ -1,8 +1,6 @@
|
|||||||
import org.apache.tools.ant.taskdefs.condition.Os
|
import org.apache.tools.ant.taskdefs.condition.Os
|
||||||
|
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
import java.util.zip.ZipInputStream
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
@@ -21,6 +19,10 @@ cargo {
|
|||||||
|
|
||||||
def smaliFolder = projectDir.toPath().resolve("../smali").normalize()
|
def smaliFolder = projectDir.toPath().resolve("../smali").normalize()
|
||||||
def scriptsFolder = projectDir.toPath().resolve("../scripts").normalize()
|
def scriptsFolder = projectDir.toPath().resolve("../scripts").normalize()
|
||||||
|
//you can add own folders with decompiled smali code
|
||||||
|
def dexes=smaliFolder.toFile()
|
||||||
|
.listFiles( { f -> f.isDirectory() && f.name.startsWith("smali") } as FileFilter)
|
||||||
|
.size()
|
||||||
|
|
||||||
versionFile {
|
versionFile {
|
||||||
file = projectDir.toPath().resolve("src/main/assets/version.properties").toFile()
|
file = projectDir.toPath().resolve("src/main/assets/version.properties").toFile()
|
||||||
@@ -50,19 +52,24 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
buildConfigField 'String', 'APPCENTER_KEY', '""'
|
||||||
|
proguardFile 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
rc {
|
||||||
|
minifyEnabled true
|
||||||
|
buildConfigField 'String', 'APPCENTER_KEY', '""'
|
||||||
|
proguardFile 'proguard-rules.pro'
|
||||||
|
}
|
||||||
dev {
|
dev {
|
||||||
initWith release
|
minifyEnabled false
|
||||||
buildConfigField 'String', 'APPCENTER_KEY', '""'
|
buildConfigField 'String', 'APPCENTER_KEY', '""'
|
||||||
}
|
}
|
||||||
beta {
|
beta {
|
||||||
initWith release
|
minifyEnabled false
|
||||||
buildConfigField 'String', 'APPCENTER_KEY', '""'
|
buildConfigField 'String', 'APPCENTER_KEY', '""'
|
||||||
}
|
}
|
||||||
release {
|
|
||||||
buildConfigField 'String', 'APPCENTER_KEY', '""'
|
|
||||||
minifyEnabled true
|
|
||||||
proguardFile 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -77,75 +84,55 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task testClasses {
|
||||||
|
doLast {
|
||||||
|
println 'This is a dummy testClasses task'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.microsoft.appcenter:appcenter-analytics:5.0.1'
|
implementation 'com.microsoft.appcenter:appcenter-analytics:5.0.1'
|
||||||
implementation 'com.microsoft.appcenter:appcenter-crashes:5.0.1'
|
implementation 'com.microsoft.appcenter:appcenter-crashes:5.0.1'
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||||
implementation 'net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4'
|
implementation 'net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4'
|
||||||
|
implementation 'com.google.guava:guava:23.3-android'
|
||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
||||||
compileOnly fileTree(dir: 'libs', include: ['*.jar'])
|
compileOnly fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation 'com.google.guava:guava:23.3-android'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task generateBuildTasks() {
|
task generateBuildTasks() {
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
variant.outputs.each { output ->
|
variant.outputs.each { output ->
|
||||||
def buildType = output.name.toString().capitalize()
|
def buildType = output.name.toString().capitalize()
|
||||||
def lowerBuildType = buildType.toLowerCase()
|
if(buildType=="Base")return
|
||||||
task "exportDex$buildType"() {
|
task "exportDex$buildType"() {
|
||||||
dependsOn('assemble' + buildType)
|
dependsOn('assemble' + buildType)
|
||||||
doLast {
|
doLast {
|
||||||
try {
|
try {
|
||||||
var z = ""
|
def suffix=buildType.toLowerCase()
|
||||||
if (lowerBuildType != "debug") {
|
String apkPath=projectDir.getAbsolutePath()+"/build/outputs/apk/$suffix/app-$suffix"
|
||||||
z = "-unsigned"
|
def apk=new File(apkPath+"-unsigned.apk")
|
||||||
}
|
if(!apk.exists())apk=new File(apkPath+".apk")
|
||||||
|
def zip=new ZipFile(apk)
|
||||||
File moduleDir = projectDir
|
def entries=zip.entries()
|
||||||
ZipInputStream inputStream = new ZipInputStream(new FileInputStream(moduleDir.getAbsolutePath() + "/build/outputs/apk/$lowerBuildType/app-$lowerBuildType${z}.apk"))
|
def dexCounter=dexes
|
||||||
ZipFile file = new ZipFile(moduleDir.getAbsolutePath() + "/build/outputs/apk/$lowerBuildType/app-$lowerBuildType${z}.apk");
|
while(entries.hasMoreElements())
|
||||||
ZipEntry entry
|
{
|
||||||
while ((entry = inputStream.nextEntry) != null) {
|
def entry=entries.nextElement()
|
||||||
if (entry.getName().equals("classes.dex")) {
|
String filename
|
||||||
print('classes.dex found!')
|
if(entry.name.matches("^classes(?:(?=\\d)2|)\\.dex\$"))filename="classes${++dexCounter}.dex"
|
||||||
|
else if(entry.name=="assets/version.properties")filename="version.properties"
|
||||||
FileOutputStream outputStream = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "classes6.dex"))
|
else continue
|
||||||
|
println("Exporting "+entry.name)
|
||||||
byte[] buffer = new byte[1024]
|
def tmpFile=new File(projectDir,filename)
|
||||||
int len = 0
|
def is=zip.getInputStream(entry)
|
||||||
while ((len = inputStream.read(buffer)) > 0) {
|
def fos=new FileOutputStream(tmpFile)
|
||||||
outputStream.write(buffer, 0, len)
|
def buff=new byte[0x800]
|
||||||
}
|
int len
|
||||||
outputStream.close()
|
while((len=is.read(buff))>0)fos.write(buff,0,len)
|
||||||
}
|
is.close()
|
||||||
if (entry.getName().equals("classes2.dex")) {
|
fos.close()
|
||||||
print('\nclasses2.dex found!')
|
|
||||||
|
|
||||||
FileOutputStream outputStream = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "classes7.dex"))
|
|
||||||
|
|
||||||
byte[] buffer = new byte[1024]
|
|
||||||
int len = 0
|
|
||||||
while ((len = inputStream.read(buffer)) > 0) {
|
|
||||||
outputStream.write(buffer, 0, len)
|
|
||||||
}
|
|
||||||
outputStream.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ZipEntry e : Collections.list(file.entries())) {
|
|
||||||
if (e.getName().endsWith("assets/version.properties")) {
|
|
||||||
var is = file.getInputStream(e)
|
|
||||||
FileOutputStream outputStream = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "version.properties"))
|
|
||||||
|
|
||||||
byte[] buffer = new byte[1024]
|
|
||||||
int len = 0
|
|
||||||
while ((len = is.read(buffer)) > 0) {
|
|
||||||
outputStream.write(buffer, 0, len)
|
|
||||||
}
|
|
||||||
outputStream.close()
|
|
||||||
|
|
||||||
print('\nversion.properties found!')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -155,17 +142,16 @@ task generateBuildTasks() {
|
|||||||
task "prepareDexForMerge$buildType"(type: Copy) {
|
task "prepareDexForMerge$buildType"(type: Copy) {
|
||||||
dependsOn('exportDex' + buildType)
|
dependsOn('exportDex' + buildType)
|
||||||
// Remove comment for native lib build
|
// Remove comment for native lib build
|
||||||
dependsOn('cargoBuild')
|
//dependsOn('cargoBuild')
|
||||||
into smaliFolder.toString()
|
into smaliFolder.toString()
|
||||||
from(projectDir) {
|
from(projectDir) {
|
||||||
include 'classes6.dex'
|
into '../smali'
|
||||||
include 'classes7.dex'
|
include "classes*.dex"
|
||||||
}
|
}
|
||||||
from(projectDir) {
|
from(projectDir) {
|
||||||
include 'version.properties'
|
include 'version.properties'
|
||||||
into 'assets'
|
into 'assets'
|
||||||
}
|
}
|
||||||
// Remove comment for native lib build
|
|
||||||
from('../native-effects/target/aarch64-linux-android/release'){
|
from('../native-effects/target/aarch64-linux-android/release'){
|
||||||
include 'libnative_effects.so'
|
include 'libnative_effects.so'
|
||||||
into 'lib/arm64-v8a'
|
into 'lib/arm64-v8a'
|
||||||
@@ -174,15 +160,16 @@ task generateBuildTasks() {
|
|||||||
include 'libnative_effects.so'
|
include 'libnative_effects.so'
|
||||||
into 'lib/armeabi-v7a'
|
into 'lib/armeabi-v7a'
|
||||||
}
|
}
|
||||||
doLast {
|
}
|
||||||
new File(projectDir, "classes6.dex").delete()
|
task "cleanTemporaryFiles$buildType"(type:Delete)
|
||||||
new File(projectDir, "classes7.dex").delete()
|
{
|
||||||
new File(projectDir, "version.properties").delete()
|
delete fileTree(dir:projectDir,include:["*.dex","version.properties"])
|
||||||
}
|
delete fileTree(dir:smaliFolder,include:['*.dex',"assets/version.properties"])
|
||||||
}
|
}
|
||||||
task "buildVTL$buildType"(type: Exec) {
|
task "buildVTL$buildType"(type: Exec) {
|
||||||
dependsOn('prepareDexForMerge' + buildType)
|
dependsOn('prepareDexForMerge' + buildType)
|
||||||
workingDir(projectDir.parentFile)
|
workingDir(projectDir.parentFile)
|
||||||
|
finalizedBy "cleanTemporaryFiles$buildType"
|
||||||
if (Os.isFamily("unix") || Os.isFamily("mac")) {
|
if (Os.isFamily("unix") || Os.isFamily("mac")) {
|
||||||
commandLine 'bash', '-c', './scripts/build.sh'
|
commandLine 'bash', '-c', './scripts/build.sh'
|
||||||
} else {
|
} else {
|
||||||
@@ -210,7 +197,6 @@ task generateBuildTasks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
task generateRjava(type: Exec) {
|
task generateRjava(type: Exec) {
|
||||||
workingDir(projectDir.parentFile)
|
workingDir(projectDir.parentFile)
|
||||||
if (Os.isFamily("unix") || Os.isFamily("mac"))
|
if (Os.isFamily("unix") || Os.isFamily("mac"))
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,9 +10,9 @@ import com.vk.music.common.MusicPlaybackLaunchContext;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
|
||||||
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
||||||
import static ru.vtosters.hooks.other.Preferences.isValidSignature;
|
import static ru.vtosters.hooks.other.Preferences.isValidSignature;
|
||||||
|
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
||||||
|
|
||||||
public class LibVKXClient {
|
public class LibVKXClient {
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
@@ -80,11 +80,6 @@ public class LibVKXClient {
|
|||||||
lambdaplay(list, musicTrack, iLibVkxService, null);
|
lambdaplay(list, musicTrack, iLibVkxService, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String asId(MusicTrack musicTrack) {
|
public static String asId(MusicTrack musicTrack)
|
||||||
String id = musicTrack.e + "_" + musicTrack.d;
|
{ return musicTrack.y1(); }
|
||||||
if (musicTrack.O != null && !musicTrack.O.equals("")) {
|
|
||||||
id += "_" + musicTrack.O;
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -36,10 +36,8 @@ public class TelegramStickersPack extends CustomStickersPack {
|
|||||||
public HashMap<String, List<Integer>> emojis = new HashMap<>();
|
public HashMap<String, List<Integer>> emojis = new HashMap<>();
|
||||||
|
|
||||||
public TelegramStickersPack(String id) {
|
public TelegramStickersPack(String id) {
|
||||||
final var idLowerCase = id.toLowerCase();
|
this.id = this.title = id;
|
||||||
this.id = idLowerCase;
|
this.hash = id.hashCode();
|
||||||
this.hash = idLowerCase.hashCode();
|
|
||||||
title = id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONObject encodeEmojis() throws JSONException {
|
public JSONObject encodeEmojis() throws JSONException {
|
||||||
@@ -103,7 +101,7 @@ public class TelegramStickersPack extends CustomStickersPack {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
return obj instanceof TelegramStickersPack && ((TelegramStickersPack) obj).id.equalsIgnoreCase(id);
|
return obj instanceof TelegramStickersPack && ((TelegramStickersPack) obj).id.equals(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.util.Log;
|
|||||||
import com.aefyr.tsg.g2.sql.TelegramStickersDbHelper;
|
import com.aefyr.tsg.g2.sql.TelegramStickersDbHelper;
|
||||||
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber;
|
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber;
|
||||||
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersPackInfo;
|
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersPackInfo;
|
||||||
|
import ru.vtosters.lite.ui.fragments.VTSettings;
|
||||||
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
|
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -25,19 +26,13 @@ import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
|||||||
public class TelegramStickersService {
|
public class TelegramStickersService {
|
||||||
private static final String TAG = "TGStickersService";
|
private static final String TAG = "TGStickersService";
|
||||||
private static TelegramStickersService instance;
|
private static TelegramStickersService instance;
|
||||||
|
|
||||||
private final Context c;
|
|
||||||
|
|
||||||
private final ArrayList<TelegramStickersPack> packs;
|
private final ArrayList<TelegramStickersPack> packs;
|
||||||
private final ArrayList<TelegramStickersPack> activePacks;
|
private final ArrayList<TelegramStickersPack> activePacks;
|
||||||
private final ArrayList<TelegramStickersPack> inactivePacks;
|
private final ArrayList<TelegramStickersPack> inactivePacks;
|
||||||
private final ArrayList<StickersEventsListener> listeners;
|
private final ArrayList<StickersEventsListener> listeners;
|
||||||
private final HashSet<String> currentlyDownloading;
|
private final HashSet<String> currentlyDownloading;
|
||||||
|
|
||||||
private final Handler uiThreadHandler;
|
private final Handler uiThreadHandler;
|
||||||
|
|
||||||
private final TelegramStickersGrabber grabber;
|
private final TelegramStickersGrabber grabber;
|
||||||
|
|
||||||
private final TelegramStickersDbHelper dbHelper;
|
private final TelegramStickersDbHelper dbHelper;
|
||||||
private final ArrayList<Runnable> queuedTasks;
|
private final ArrayList<Runnable> queuedTasks;
|
||||||
private final ThreadPoolExecutor executor;
|
private final ThreadPoolExecutor executor;
|
||||||
@@ -47,16 +42,14 @@ public class TelegramStickersService {
|
|||||||
private TelegramStickersService(Context context) {
|
private TelegramStickersService(Context context) {
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
this.c = context.getApplicationContext();
|
|
||||||
|
|
||||||
listeners = new ArrayList<>();
|
listeners = new ArrayList<>();
|
||||||
currentlyDownloading = new HashSet<>();
|
currentlyDownloading = new HashSet<>();
|
||||||
uiThreadHandler = new Handler(Looper.getMainLooper());
|
uiThreadHandler = new Handler(Looper.getMainLooper());
|
||||||
grabber = new TelegramStickersGrabber(null);
|
grabber = new TelegramStickersGrabber(null);
|
||||||
dbHelper = new TelegramStickersDbHelper(c);
|
dbHelper = new TelegramStickersDbHelper(context);
|
||||||
queuedTasks = new ArrayList<>();
|
queuedTasks = new ArrayList<>();
|
||||||
executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
|
executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
|
||||||
notificationsHelper = new NotificationsHelper(c);
|
notificationsHelper = new NotificationsHelper(context);
|
||||||
|
|
||||||
packs = new ArrayList<>();
|
packs = new ArrayList<>();
|
||||||
activePacks = new ArrayList<>();
|
activePacks = new ArrayList<>();
|
||||||
@@ -69,11 +62,11 @@ public class TelegramStickersService {
|
|||||||
return instance == null ? new TelegramStickersService(c) : instance;
|
return instance == null ? new TelegramStickersService(c) : instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePacks(final boolean notify) {
|
private void updatePacks(boolean notify) {
|
||||||
packs.clear();
|
packs.clear();
|
||||||
dbHelper.getAllPacks(new TelegramStickersDbHelper.PacksLoadingListener() {
|
dbHelper.getAllPacks(new TelegramStickersDbHelper.PacksLoadingListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onPackLoaded(final TelegramStickersPack pack) {
|
public void onPackLoaded(TelegramStickersPack pack) {
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
packs.add(pack);
|
packs.add(pack);
|
||||||
|
|
||||||
@@ -102,14 +95,6 @@ public class TelegramStickersService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addStickersEventsListener(StickersEventsListener listener) {
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeStickersEventsListener(StickersEventsListener listener) {
|
|
||||||
listeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyPackAdded(TelegramStickersPack pack, int atIndex) {
|
private void notifyPackAdded(TelegramStickersPack pack, int atIndex) {
|
||||||
getGlobalContext().sendBroadcast(new Intent(StickersFragment.ACTION_RELOAD));
|
getGlobalContext().sendBroadcast(new Intent(StickersFragment.ACTION_RELOAD));
|
||||||
if (listeners.isEmpty())
|
if (listeners.isEmpty())
|
||||||
@@ -152,6 +137,7 @@ public class TelegramStickersService {
|
|||||||
|
|
||||||
private void notifyActivePacksListChanged() {
|
private void notifyActivePacksListChanged() {
|
||||||
getGlobalContext().sendBroadcast(new Intent(StickersFragment.ACTION_RELOAD));
|
getGlobalContext().sendBroadcast(new Intent(StickersFragment.ACTION_RELOAD));
|
||||||
|
getGlobalContext().sendBroadcast(new Intent(VTSettings.ACTION_INVALIDATE_TGS_COUNT));
|
||||||
if (listeners.isEmpty())
|
if (listeners.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -186,20 +172,16 @@ public class TelegramStickersService {
|
|||||||
return inactivePacks;
|
return inactivePacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDoneLoading() {
|
public void requestPackDownload(String id, File packFolder) {
|
||||||
return ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean requestPackDownload(final String id, File packFolder) {
|
|
||||||
if (currentlyDownloading.contains(id)) {
|
if (currentlyDownloading.contains(id)) {
|
||||||
Log.e(TAG, String.format("Got request to download pack %s which is already downloading", id));
|
Log.e(TAG, String.format("Got request to download pack %s which is already downloading", id));
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ready) {
|
if (!ready) {
|
||||||
final File folder = packFolder;
|
File folder = packFolder;
|
||||||
queuedTasks.add(() -> requestPackDownload(id, folder));
|
queuedTasks.add(() -> requestPackDownload(id, folder));
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TelegramStickersPack pack = new TelegramStickersPack(id);
|
TelegramStickersPack pack = new TelegramStickersPack(id);
|
||||||
@@ -219,85 +201,86 @@ public class TelegramStickersService {
|
|||||||
notifyPackAdded(pack, packs.size() - 1);
|
notifyPackAdded(pack, packs.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TelegramStickersPack newPack = pack;
|
TelegramStickersPack newPack = pack;
|
||||||
final boolean isUpdate = update;
|
boolean isUpdate = update;
|
||||||
|
|
||||||
currentlyDownloading.add(id.toLowerCase());
|
currentlyDownloading.add(id);
|
||||||
notificationsHelper.packStartedDownloading(newPack);
|
notificationsHelper.packStartedDownloading(newPack);
|
||||||
|
|
||||||
|
// try-catch block to handle possible timeout exception
|
||||||
grabber.enableProxy();
|
try {
|
||||||
grabber.grabPack(id, packFolder, pack.version, new TelegramStickersGrabber.PackDownloadListener() {
|
grabber.grabPack(id, packFolder, pack.version, new TelegramStickersGrabber.PackDownloadListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onPackDownloaded(TelegramStickersPackInfo packInfo, boolean newVersionFound) {
|
public void onPackDownloaded(TelegramStickersPackInfo packInfo, boolean newVersionFound) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
runOnUiThread(() -> {
|
|
||||||
newPack.state = TelegramStickersPack.DOWNLOADED;
|
|
||||||
notifyPackChanged(newPack, packs.indexOf(newPack));
|
|
||||||
currentlyDownloading.remove(newPack.id);
|
|
||||||
syncPack(newPack);
|
|
||||||
|
|
||||||
if (isUpdate) {
|
|
||||||
if (newPack.enabled)
|
|
||||||
notifyActivePacksListChanged();
|
|
||||||
} else {
|
|
||||||
activePacks.add(newPack);
|
|
||||||
notifyActivePacksListChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
notificationsHelper.packDoneDownloading(newPack, true, isUpdate, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPackDownloadError(final Exception e) {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
Log.e(TAG, "Error while downloading pack " + newPack.id);
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
if (!isUpdate) {
|
|
||||||
int index = packs.indexOf(newPack);
|
|
||||||
packs.remove(index);
|
|
||||||
notifyPackRemoved(newPack, index);
|
|
||||||
} else {
|
|
||||||
newPack.state = TelegramStickersPack.DOWNLOADED;
|
newPack.state = TelegramStickersPack.DOWNLOADED;
|
||||||
notifyPackChanged(newPack, packs.indexOf(newPack));
|
notifyPackChanged(newPack, packs.indexOf(newPack));
|
||||||
}
|
currentlyDownloading.remove(newPack.id);
|
||||||
|
syncPack(newPack);
|
||||||
|
|
||||||
notifyPackDownloadError(newPack, e);
|
if (isUpdate) {
|
||||||
currentlyDownloading.remove(newPack.id);
|
if (newPack.enabled)
|
||||||
});
|
notifyActivePacksListChanged();
|
||||||
notificationsHelper.packDoneDownloading(newPack, false, isUpdate, e);
|
} else {
|
||||||
}
|
activePacks.add(newPack);
|
||||||
|
notifyActivePacksListChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
notificationsHelper.packDoneDownloading(newPack, true, isUpdate, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onGotPackInfo(final TelegramStickersPackInfo packInfo) {
|
public void onPackDownloadError(Exception e) {
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
newPack.title = packInfo.title;
|
Log.e(TAG, "Error while downloading pack " + newPack.id);
|
||||||
newPack.stickersCount = packInfo.stickersCount;
|
e.printStackTrace();
|
||||||
newPack.version = packInfo.version;
|
|
||||||
notifyPackChanged(newPack, packs.indexOf(newPack));
|
|
||||||
});
|
|
||||||
|
|
||||||
notificationsHelper.packDownloadUpdated(newPack, 0);
|
if (!isUpdate) {
|
||||||
}
|
int index = packs.indexOf(newPack);
|
||||||
|
packs.remove(index);
|
||||||
|
notifyPackRemoved(newPack, index);
|
||||||
|
} else {
|
||||||
|
newPack.state = TelegramStickersPack.DOWNLOADED;
|
||||||
|
notifyPackChanged(newPack, packs.indexOf(newPack));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
notifyPackDownloadError(newPack, e);
|
||||||
public void onStickerDownloaded(String pack, File sticker, String boundEmoji, int stickerIndex, int downloadedStickersCount, int totalStickersCount) {
|
currentlyDownloading.remove(newPack.id);
|
||||||
stickerIndex--;
|
});
|
||||||
|
notificationsHelper.packDoneDownloading(newPack, false, isUpdate, e);
|
||||||
|
}
|
||||||
|
|
||||||
List<Integer> list = newPack.emojis.get(boundEmoji);
|
@Override
|
||||||
if (list == null) list = new ArrayList<>();
|
public void onGotPackInfo(TelegramStickersPackInfo packInfo) {
|
||||||
list.add(stickerIndex);
|
runOnUiThread(() -> {
|
||||||
newPack.emojis.put(boundEmoji, list);
|
newPack.title = packInfo.title;
|
||||||
|
newPack.stickersCount = packInfo.stickersCount;
|
||||||
|
newPack.version = packInfo.version;
|
||||||
|
notifyPackChanged(newPack, packs.indexOf(newPack));
|
||||||
|
});
|
||||||
|
|
||||||
notificationsHelper.packDownloadUpdated(newPack, downloadedStickersCount);
|
notificationsHelper.packDownloadUpdated(newPack, 0);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
@Override
|
||||||
|
public void onStickerDownloaded(String pack, File sticker, String boundEmoji, int stickerIndex, int downloadedStickersCount, int totalStickersCount) {
|
||||||
|
stickerIndex--;
|
||||||
|
|
||||||
|
List<Integer> list = newPack.emojis.get(boundEmoji);
|
||||||
|
if (list == null) list = new ArrayList<>();
|
||||||
|
list.add(stickerIndex);
|
||||||
|
newPack.emojis.put(boundEmoji, list);
|
||||||
|
|
||||||
|
notificationsHelper.packDownloadUpdated(newPack, downloadedStickersCount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
// handle other possible exceptions
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPackEnabled(final TelegramStickersPack pack, final boolean enabled, final boolean notify) {
|
public void setPackEnabled(TelegramStickersPack pack, boolean enabled, boolean notify) {
|
||||||
if (!ready) {
|
if (!ready) {
|
||||||
queuedTasks.add(() -> setPackEnabled(pack, enabled, notify));
|
queuedTasks.add(() -> setPackEnabled(pack, enabled, notify));
|
||||||
return;
|
return;
|
||||||
@@ -323,7 +306,7 @@ public class TelegramStickersService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deletePack(final TelegramStickersPack pack) {
|
public void deletePack(TelegramStickersPack pack) {
|
||||||
if (!ready) {
|
if (!ready) {
|
||||||
queuedTasks.add(() -> deletePack(pack));
|
queuedTasks.add(() -> deletePack(pack));
|
||||||
return;
|
return;
|
||||||
@@ -332,7 +315,6 @@ public class TelegramStickersService {
|
|||||||
boolean d = dbHelper.deletePack(pack);
|
boolean d = dbHelper.deletePack(pack);
|
||||||
Log.d(TAG, "Delete from DB=" + d);
|
Log.d(TAG, "Delete from DB=" + d);
|
||||||
|
|
||||||
|
|
||||||
int index = packs.indexOf(pack);
|
int index = packs.indexOf(pack);
|
||||||
packs.remove(index);
|
packs.remove(index);
|
||||||
notifyPackRemoved(pack, index);
|
notifyPackRemoved(pack, index);
|
||||||
@@ -353,7 +335,7 @@ public class TelegramStickersService {
|
|||||||
uiThreadHandler.post(r);
|
uiThreadHandler.post(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncPack(final TelegramStickersPack pack) {
|
private void syncPack(TelegramStickersPack pack) {
|
||||||
if (!ready) {
|
if (!ready) {
|
||||||
queuedTasks.add(() -> syncPack(pack));
|
queuedTasks.add(() -> syncPack(pack));
|
||||||
return;
|
return;
|
||||||
@@ -395,6 +377,4 @@ public class TelegramStickersService {
|
|||||||
|
|
||||||
void onInactivePacksListChanged();
|
void onInactivePacksListChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,23 +7,20 @@ import android.os.Looper;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.aefyr.tsg.g2.stickersgrabber.util.Flag;
|
import com.aefyr.tsg.g2.stickersgrabber.util.Flag;
|
||||||
import com.aefyr.tsg.g2.stickersgrabber.util.GoalCounter;
|
import com.aefyr.tsg.g2.stickersgrabber.util.GoalCounter;
|
||||||
|
import okhttp3.*;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.net.*;
|
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.PasswordAuthentication;
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,14 +28,19 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
*/
|
*/
|
||||||
public class TelegramStickersGrabber {
|
public class TelegramStickersGrabber {
|
||||||
private static final String TAG = "TSG";
|
private static final String TAG = "TSG";
|
||||||
public static String REAL_TG_IP = null;
|
private static final OkHttpClient sClient = VtOkHttpClient.getInstance();
|
||||||
public static String PROXY_IP = null;
|
private static final int MAX_RETRIES = 2;
|
||||||
public static int PROXY_PORT = -1;
|
private static final AtomicInteger retryCount = new AtomicInteger();
|
||||||
public static String PROXY_USER = null;
|
private static final int STICKER_QUALITY = 100;
|
||||||
public static String PROXY_PASS = null;
|
private static final String STICKER_FILE_NAME_FORMAT = "%03d.png";
|
||||||
public static boolean PROXY_SOCKS = true;
|
private static final String STICKERS_KEY = "stickers";
|
||||||
public static boolean HAS_PASS = false;
|
private static final String FILE_ID_KEY = "file_id";
|
||||||
public static boolean USE_PROXY = false;
|
private static final String EMOJI_KEY = "emoji";
|
||||||
|
private static final String IS_ANIMATED_KEY = "is_animated";
|
||||||
|
private static final String IS_VIDEO_KEY = "is_video";
|
||||||
|
private static final String THUMB_KEY = "thumb";
|
||||||
|
private static final String NAME_KEY = "name";
|
||||||
|
private static final String TITLE_KEY = "title";
|
||||||
private static String BOT_API_BASE_URL;
|
private static String BOT_API_BASE_URL;
|
||||||
private static String GET_STICKER_SET_URL;
|
private static String GET_STICKER_SET_URL;
|
||||||
private static String GET_FILE_URL;
|
private static String GET_FILE_URL;
|
||||||
@@ -50,12 +52,10 @@ public class TelegramStickersGrabber {
|
|||||||
|
|
||||||
private final MessageDigest sha256;
|
private final MessageDigest sha256;
|
||||||
private final Handler uiThreadHandler;
|
private final Handler uiThreadHandler;
|
||||||
private String botApiKey = "";
|
private String botApiKey;
|
||||||
private NetClient httpClient;
|
|
||||||
|
|
||||||
public TelegramStickersGrabber(String botApiKey) {
|
public TelegramStickersGrabber(String botApiKey) {
|
||||||
this.botApiKey = botApiKey;
|
this.botApiKey = botApiKey;
|
||||||
httpClient = new NetClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
|
|
||||||
uiThreadHandler = new Handler(Looper.getMainLooper());
|
uiThreadHandler = new Handler(Looper.getMainLooper());
|
||||||
try {
|
try {
|
||||||
sha256 = MessageDigest.getInstance("SHA-256");
|
sha256 = MessageDigest.getInstance("SHA-256");
|
||||||
@@ -69,81 +69,12 @@ public class TelegramStickersGrabber {
|
|||||||
public static void updateURLs() {
|
public static void updateURLs() {
|
||||||
BOT_API_BASE_FILE_URL = "https://api.telegram.org/file/bot%s/%s";
|
BOT_API_BASE_FILE_URL = "https://api.telegram.org/file/bot%s/%s";
|
||||||
|
|
||||||
|
BOT_API_BASE_URL = "https://api.telegram.org/bot%s/";
|
||||||
BOT_API_BASE_URL = REAL_TG_IP != null ? "https://" + REAL_TG_IP + "/bot%s/" : "https://api.telegram.org/bot%s/";
|
|
||||||
GET_STICKER_SET_URL = BOT_API_BASE_URL + "getStickerSet?name=%s";
|
GET_STICKER_SET_URL = BOT_API_BASE_URL + "getStickerSet?name=%s";
|
||||||
GET_FILE_URL = BOT_API_BASE_URL + "getFile?file_id=%s";
|
GET_FILE_URL = BOT_API_BASE_URL + "getFile?file_id=%s";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isTelegramBlocked() {
|
public void grabPack(String id, File packFolder, String installedVersion, PackDownloadListener listener) {
|
||||||
NetClient client = new NetClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
|
|
||||||
try {
|
|
||||||
if (PROXY_IP != null) {
|
|
||||||
Socket socket = new Socket(PROXY_IP, PROXY_PORT);
|
|
||||||
Proxy proxy = new Proxy(Proxy.Type.SOCKS, socket.getRemoteSocketAddress());
|
|
||||||
NetClient.Builder b = new NetClient.Builder().proxy(proxy);
|
|
||||||
if (PROXY_USER != null) {
|
|
||||||
b = b.proxyAuthenticator(new PasswordAuthentication(PROXY_USER, PROXY_PASS.toCharArray()));
|
|
||||||
}
|
|
||||||
client = b.build();
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
NetRequest req = new NetRequest.Builder().url(REAL_TG_IP != null ? "https://" + REAL_TG_IP + "/test/" : "https://api.telegram.org/test/").build();
|
|
||||||
try {
|
|
||||||
NetResponse res = client.newCall(req).execute();
|
|
||||||
String s = res.getDataString();
|
|
||||||
|
|
||||||
if (!s.equals("{\"ok\":false,\"error_code\":404,\"description\":\"Not Found\"}"))
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetProxy() {
|
|
||||||
httpClient = new NetClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enableProxy() {
|
|
||||||
Thread kostil = new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
if (PROXY_IP != null) {
|
|
||||||
Socket socket = new Socket(PROXY_IP, PROXY_PORT);
|
|
||||||
Proxy proxy = new Proxy(Proxy.Type.SOCKS, socket.getRemoteSocketAddress());
|
|
||||||
NetClient.Builder b = httpClient.newBuilder().proxy(proxy);
|
|
||||||
|
|
||||||
if (PROXY_USER != null) {
|
|
||||||
httpClient = b.proxyAuthenticator(new PasswordAuthentication(PROXY_USER, PROXY_PASS.toCharArray())).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient = b.build();
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
kostil.start();
|
|
||||||
try {
|
|
||||||
kostil.join();
|
|
||||||
Log.d(TAG, "Proxy set");
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(TAG, "Unable to set proxy:");
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void grabPack(String id, File packFolder, String installedVersion, final PackDownloadListener listener) {
|
|
||||||
getPackInfo(id, packFolder, installedVersion, listener);
|
getPackInfo(id, packFolder, installedVersion, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,264 +82,247 @@ public class TelegramStickersGrabber {
|
|||||||
botApiKey = key;
|
botApiKey = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkKey(final KeyCheckListener listener) {
|
public void checkKey(KeyCheckListener listener) {
|
||||||
Log.d(TAG, "Checking key: " + botApiKey);
|
Log.d(TAG, "Checking key: " + botApiKey);
|
||||||
NetRequest request = new NetRequest.Builder().get().url(String.format(BOT_API_BASE_URL + "getMe", botApiKey)).build();
|
// Create a request object with the URL and bot API key
|
||||||
httpClient.newCall(request).enqueue(new NetCallback() {
|
Request request = new Request.a()
|
||||||
|
.b(String.format(BOT_API_BASE_URL + "getMe", botApiKey))
|
||||||
|
.a();
|
||||||
|
// Create a new call object and enqueue it to run asynchronously
|
||||||
|
sClient.a(request).a(new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(NetCall call, IOException e) {
|
public void a(Call call, IOException e) {
|
||||||
e.printStackTrace();
|
// Handle the failure case
|
||||||
|
Log.d(TAG, e.getMessage());
|
||||||
runOnUiThread(listener::onNetError);
|
runOnUiThread(listener::onNetError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(NetCall call, final NetResponse response) throws IOException {
|
public void a(Call call, Response response) {
|
||||||
runOnUiThread(() -> listener.onKeyChecked(response.code() == 200));
|
Log.d(TAG, response.toString());
|
||||||
|
runOnUiThread(() -> listener.onKeyChecked(response.h()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getPackInfo(final String packName, final File packFolder, final String installedVersion, final PackDownloadListener listener) {
|
private void getPack(StickerSet set, File packFolder, TelegramStickersPackInfo packInfo, PackDownloadListener listener) {
|
||||||
NetRequest packInfoRequest = new NetRequest.Builder().get().url(String.format(GET_STICKER_SET_URL, botApiKey, packName)).build();
|
GoalCounter downloadedStickers = new GoalCounter(set.stickers.size(), () -> {
|
||||||
httpClient.newCall(packInfoRequest).enqueue(new NetCallback() {
|
Log.d(TAG, String.format("Pack %s has been downloaded to %s", set.id, packFolder.getAbsolutePath()));
|
||||||
|
listener.onPackDownloaded(packInfo, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Flag deathFlag = new Flag();
|
||||||
|
|
||||||
|
for (int i = 0; i < set.stickers.size(); i++) {
|
||||||
|
Sticker sticker = set.stickers.get(i);
|
||||||
|
String getFileUrl = String.format(GET_FILE_URL, botApiKey, sticker.fileId);
|
||||||
|
Request fileInfoRequest = new Request.a()
|
||||||
|
.b(getFileUrl)
|
||||||
|
.a();
|
||||||
|
|
||||||
|
int stickerIndex = i + 1;
|
||||||
|
|
||||||
|
sClient.a(fileInfoRequest).a(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void a(Call call, IOException e) {
|
||||||
|
handleFailure(deathFlag, listener, e, downloadedStickers, call, fileInfoRequest, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void a(Call call, Response response) {
|
||||||
|
if (deathFlag.up())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!response.h()) {
|
||||||
|
retryRequest(call, fileInfoRequest, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ResponseBody responseBody = response.a()) {
|
||||||
|
JSONObject fileInfoResponse = new JSONObject(responseBody.g()).getJSONObject("result");
|
||||||
|
String filePath = fileInfoResponse.getString("file_path");
|
||||||
|
|
||||||
|
if (botApiKey == null) botApiKey = "";
|
||||||
|
|
||||||
|
String fileDownloadUrl = String.format(BOT_API_BASE_FILE_URL, botApiKey, filePath);
|
||||||
|
Request fileDownloadRequest = new Request.a()
|
||||||
|
.b(fileDownloadUrl)
|
||||||
|
.a();
|
||||||
|
|
||||||
|
sClient.a(fileDownloadRequest).a(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void a(Call call, IOException e) {
|
||||||
|
handleFailure(deathFlag, listener, e, downloadedStickers, call, fileDownloadRequest, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void a(Call call, Response response) {
|
||||||
|
if (deathFlag.up())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!response.h()) {
|
||||||
|
retryRequest(call, fileDownloadRequest, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ResponseBody responseBody = response.a()) {
|
||||||
|
if (responseBody == null) {
|
||||||
|
handleFailure(deathFlag, listener, new TSGException("Response body for a sticker is null :/"), downloadedStickers, call, fileDownloadRequest, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream stickerInputStream = responseBody.a();
|
||||||
|
Bitmap stickerImage = BitmapFactory.decodeStream(stickerInputStream);
|
||||||
|
|
||||||
|
if (stickerImage == null) {
|
||||||
|
handleFailure(deathFlag, listener, new TSGException("Unable to decode sticker image"), downloadedStickers, call, fileDownloadRequest, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File stickerFile = new File(packFolder, String.format(STICKER_FILE_NAME_FORMAT, stickerIndex));
|
||||||
|
|
||||||
|
try {
|
||||||
|
saveStickerImage(stickerImage, stickerFile);
|
||||||
|
} catch (TSGException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.onStickerDownloaded(set.name, stickerFile, sticker.emoji, stickerIndex,
|
||||||
|
downloadedStickers.value() + 1, set.stickers.size());
|
||||||
|
downloadedStickers.increase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
handleFailure(deathFlag, listener, e, downloadedStickers, call, fileInfoRequest, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFailure(Flag deathFlag, PackDownloadListener listener, Exception e, GoalCounter downloadedStickers, Call call, Request request, Callback callback) {
|
||||||
|
if ((e instanceof IOException || e instanceof TSGException) && retryCount.get() > MAX_RETRIES) {
|
||||||
|
Log.e(TAG, "Unable to download sticker: " + e.getMessage());
|
||||||
|
downloadedStickers.increase();
|
||||||
|
return;
|
||||||
|
} else if (retryCount.get() < MAX_RETRIES) {
|
||||||
|
retryRequest(call, request, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deathFlag.up()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deathFlag.raise();
|
||||||
|
listener.onPackDownloadError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void retryRequest(Call call, Request request, Callback callback) {
|
||||||
|
retryCount.incrementAndGet();
|
||||||
|
|
||||||
|
if (retryCount.get() > MAX_RETRIES) {
|
||||||
|
Log.e(TAG, "retryRequest: max retries");
|
||||||
|
callback.a(call, new IOException());
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "retryRequest");
|
||||||
|
sClient.a(request).a(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveStickerImage(Bitmap image, File file) throws TSGException {
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||||
|
image.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new TSGException("Unable to save sticker image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getPackInfo(String packName, File packFolder, String installedVersion, PackDownloadListener listener) {
|
||||||
|
String getStickerSetUrl = String.format(GET_STICKER_SET_URL, botApiKey, packName);
|
||||||
|
Request packInfoRequest = new Request.a()
|
||||||
|
.b(getStickerSetUrl)
|
||||||
|
.a();
|
||||||
|
sClient.a(packInfoRequest).a(new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(NetCall call, IOException e) {
|
public void a(Call call, IOException e) {
|
||||||
|
Log.d(TAG, e.getMessage());
|
||||||
listener.onPackDownloadError(e);
|
listener.onPackDownloadError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(NetCall call, NetResponse response) {
|
public void a(Call call, Response response) throws IOException {
|
||||||
if (!response.isSuccessful()) {
|
Log.d(TAG, response.toString());
|
||||||
if (response.getData() != null)
|
|
||||||
listener.onPackDownloadError(new TSGException(response.getDataString() + "\nURL: " + call.request().url()));
|
|
||||||
else
|
|
||||||
listener.onPackDownloadError(new TSGException("Unknown Exception, no response body"));
|
|
||||||
|
|
||||||
|
if (!response.h()) {
|
||||||
|
String errorMessage = response.a() != null ? response.a().g() + "\nURL: " + call.m0().g() : "Unknown Exception, no response body";
|
||||||
|
listener.onPackDownloadError(new TSGException(errorMessage));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StickerSet set;
|
try (ResponseBody responseBody = response.a()) {
|
||||||
try {
|
JSONObject packInfoResponse = new JSONObject(responseBody.g()).getJSONObject("result");
|
||||||
JSONObject jPackInfo = new JSONObject(response.getDataString()).getJSONObject("result");
|
String packVersion = new String(sha256.digest(packInfoResponse.toString().getBytes(StandardCharsets.UTF_8)));
|
||||||
String packVersion = new String(sha256.digest(jPackInfo.toString().getBytes(StandardCharsets.UTF_8)));
|
|
||||||
|
|
||||||
if (packVersion.equals(installedVersion)) {
|
if (packVersion.equals(installedVersion)) {
|
||||||
listener.onPackDownloaded(null, false);
|
listener.onPackDownloaded(null, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONArray jStickers = jPackInfo.getJSONArray("stickers");
|
JSONArray stickersArray = packInfoResponse.getJSONArray(STICKERS_KEY);
|
||||||
|
|
||||||
if (jStickers.length() == 0) {
|
if (stickersArray.length() == 0) {
|
||||||
listener.onPackDownloadError(new TSGException("No stickers in pack"));
|
listener.onPackDownloadError(new TSGException("No stickers in pack"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayList<Sticker> stickers = new ArrayList<>(jStickers.length());
|
ArrayList<Sticker> stickers = parseStickers(stickersArray);
|
||||||
|
|
||||||
Log.d(TAG, String.format("Parsing stickers in pack %s", packName));
|
StickerSet set = new StickerSet(packInfoResponse.getString(NAME_KEY), packInfoResponse.getString(TITLE_KEY), packVersion, stickers);
|
||||||
|
|
||||||
for (int i = 0; i < jStickers.length(); i++) {
|
if (!packFolder.exists() && !packFolder.mkdirs()) {
|
||||||
JSONObject jSticker = jStickers.getJSONObject(i);
|
// Handle the folder creation failure case
|
||||||
if (jSticker.optBoolean("is_animated") || jSticker.optBoolean("is_video")) {
|
listener.onPackDownloadError(new IOException("Can't create folder for the pack!"));
|
||||||
if (jSticker.has("thumb")) {
|
return;
|
||||||
stickers.add(new Sticker(jSticker.getJSONObject("thumb").getString("file_id"), jSticker.getString("emoji")));
|
|
||||||
} else {
|
|
||||||
listener.onPackDownloadError(new TSGException("Animated and video stickerpacks without thumbs are not supported!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stickers.add(new Sticker(jSticker.getString("file_id"), jSticker.getString("emoji")));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set = new StickerSet(jPackInfo.getString("name"), jPackInfo.getString("title"), packVersion, stickers);
|
Log.d(TAG, String.format("Got info for pack %s, now downloading stickers to %s", packName, packFolder.getAbsolutePath()));
|
||||||
|
|
||||||
|
TelegramStickersPackInfo packInfo = new TelegramStickersPackInfo(packName, set.name, set.stickers.size(), set.version);
|
||||||
|
|
||||||
|
listener.onGotPackInfo(packInfo);
|
||||||
|
|
||||||
|
getPack(set, packFolder, packInfo, listener);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
listener.onPackDownloadError(e);
|
listener.onPackDownloadError(e);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!packFolder.exists() && !packFolder.mkdirs()) {
|
|
||||||
listener.onPackDownloadError(new IOException("Can't create folder for the pack!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, String.format("Got info for pack %s, now downloading stickers to %s", packName, packFolder.getAbsolutePath()));
|
|
||||||
|
|
||||||
TelegramStickersPackInfo packInfo = new TelegramStickersPackInfo(packName, set.name, set.stickers.size(), set.version);
|
|
||||||
listener.onGotPackInfo(packInfo);
|
|
||||||
getPack(set, packFolder, packInfo, listener);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getPack(final StickerSet set, final File packFolder, final TelegramStickersPackInfo packInfo, final PackDownloadListener listener) {
|
private ArrayList<Sticker> parseStickers(JSONArray stickersArray) throws JSONException, TSGException {
|
||||||
final GoalCounter downloadedStickers = new GoalCounter(set.stickers.size(), () -> {
|
ArrayList<Sticker> stickers = new ArrayList<>(stickersArray.length());
|
||||||
Log.d(TAG, String.format("Pack %s has been downloaded to %s", set.id, packFolder.getAbsolutePath()));
|
|
||||||
|
|
||||||
listener.onPackDownloaded(packInfo, true);
|
for (int i = 0; i < stickersArray.length(); i++) {
|
||||||
});
|
JSONObject stickerObject = stickersArray.getJSONObject(i);
|
||||||
final Flag deathFlag = new Flag();
|
boolean isAnimated = stickerObject.optBoolean(IS_ANIMATED_KEY);
|
||||||
|
boolean isVideo = stickerObject.optBoolean(IS_VIDEO_KEY);
|
||||||
|
String fileId = stickerObject.getString(FILE_ID_KEY);
|
||||||
|
String emoji = stickerObject.getString(EMOJI_KEY);
|
||||||
|
|
||||||
AtomicInteger retryed = new AtomicInteger();
|
if (isAnimated || isVideo) {
|
||||||
for (int i = 0; i < set.stickers.size(); i++) {
|
if (stickerObject.has(THUMB_KEY)) {
|
||||||
final Sticker sticker = set.stickers.get(i);
|
fileId = stickerObject.getJSONObject(THUMB_KEY).getString(FILE_ID_KEY);
|
||||||
NetRequest fileInfoRequest = new NetRequest.Builder().get().url(String.format(GET_FILE_URL, botApiKey, sticker.fileId)).build();
|
} else {
|
||||||
|
throw new TSGException("Animated and video stickerpacks without thumbs are not supported!");
|
||||||
final int stickerIndex = i + 1;
|
|
||||||
|
|
||||||
AtomicBoolean sync = new AtomicBoolean();
|
|
||||||
httpClient.newCall(fileInfoRequest).enqueue(new NetCallback() {
|
|
||||||
@Override
|
|
||||||
public void onFailure(NetCall call, IOException e) {
|
|
||||||
if (deathFlag.up())
|
|
||||||
return;
|
|
||||||
|
|
||||||
deathFlag.raise();
|
|
||||||
listener.onPackDownloadError(e);
|
|
||||||
|
|
||||||
sync.set(true);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
stickers.add(new Sticker(fileId, emoji));
|
||||||
public void onResponse(NetCall call, NetResponse response) throws IOException {
|
|
||||||
if (deathFlag.up())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (response.code() != 200) {
|
|
||||||
retryed.incrementAndGet();
|
|
||||||
|
|
||||||
if (retryed.get() > 5)
|
|
||||||
onFailure(call, new IOException());
|
|
||||||
else httpClient.newCall(fileInfoRequest)
|
|
||||||
.enqueue(this);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String filePath;
|
|
||||||
try {
|
|
||||||
JSONObject jFileInfo = new JSONObject(response.getDataString()).getJSONObject("result");
|
|
||||||
filePath = jFileInfo.getString("file_path");
|
|
||||||
} catch (Exception e) {
|
|
||||||
deathFlag.raise();
|
|
||||||
listener.onPackDownloadError(e);
|
|
||||||
sync.set(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (botApiKey == null) botApiKey = "";
|
|
||||||
|
|
||||||
NetRequest fileDownloadRequest = new NetRequest.Builder().get().url(String.format(BOT_API_BASE_FILE_URL, botApiKey, filePath)).build();
|
|
||||||
|
|
||||||
httpClient.newCall(fileDownloadRequest).enqueue(new NetCallback() {
|
|
||||||
@Override
|
|
||||||
public void onFailure(NetCall call, IOException e) {
|
|
||||||
if (deathFlag.up())
|
|
||||||
return;
|
|
||||||
|
|
||||||
deathFlag.raise();
|
|
||||||
listener.onPackDownloadError(e);
|
|
||||||
|
|
||||||
sync.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(NetCall call, NetResponse response) throws IOException {
|
|
||||||
if (deathFlag.up())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (response.code() != 200) {
|
|
||||||
retryed.incrementAndGet();
|
|
||||||
|
|
||||||
if (retryed.get() > 5)
|
|
||||||
onFailure(call, new IOException());
|
|
||||||
else httpClient.newCall(fileDownloadRequest)
|
|
||||||
.enqueue(this);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.getData() == null) {
|
|
||||||
deathFlag.raise();
|
|
||||||
listener.onPackDownloadError(new TSGException("Response body for a sticker is null :/"));
|
|
||||||
sync.set(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream stickerInputStream = response.getDataStream();
|
|
||||||
|
|
||||||
Bitmap stickerImage = BitmapFactory.decodeStream(stickerInputStream);
|
|
||||||
|
|
||||||
try {
|
|
||||||
stickerInputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
deathFlag.raise();
|
|
||||||
listener.onPackDownloadError(e);
|
|
||||||
sync.set(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stickerImage == null) {
|
|
||||||
deathFlag.raise();
|
|
||||||
listener.onPackDownloadError(new TSGException("Unable to decode sticker image"));
|
|
||||||
sync.set(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File stickerFile = new File(packFolder, String.format("%03d.png", stickerIndex));
|
|
||||||
|
|
||||||
FileOutputStream fileOutputStream;
|
|
||||||
try {
|
|
||||||
fileOutputStream = new FileOutputStream(stickerFile);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
deathFlag.raise();
|
|
||||||
listener.onPackDownloadError(e);
|
|
||||||
sync.set(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stickerImage.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)) {
|
|
||||||
stickerImage.recycle();
|
|
||||||
deathFlag.raise();
|
|
||||||
listener.onPackDownloadError(new TSGException("Unable to compress sticker to a png file at path: " + stickerFile.getAbsolutePath()));
|
|
||||||
sync.set(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stickerImage.recycle();
|
|
||||||
|
|
||||||
try {
|
|
||||||
fileOutputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
deathFlag.raise();
|
|
||||||
listener.onPackDownloadError(e);
|
|
||||||
sync.set(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
listener.onStickerDownloaded(set.name, stickerFile, sticker.emoji, stickerIndex, downloadedStickers.value() + 1, set.stickers.size());
|
|
||||||
downloadedStickers.increase();
|
|
||||||
|
|
||||||
sync.set(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
while (!sync.get())
|
|
||||||
try {
|
|
||||||
Thread.sleep(100);
|
|
||||||
} catch (InterruptedException e1) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return stickers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runOnUiThread(Runnable r) {
|
private void runOnUiThread(Runnable r) {
|
||||||
@@ -431,15 +345,13 @@ public class TelegramStickersGrabber {
|
|||||||
void onNetError();
|
void onNetError();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TSGException extends Exception {
|
public static class TSGException extends Exception {
|
||||||
private static final long serialVersionUID = 7866915509988422944L;
|
|
||||||
|
|
||||||
private TSGException(String message) {
|
private TSGException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StickerSet {
|
private static class StickerSet {
|
||||||
String id;
|
String id;
|
||||||
String name;
|
String name;
|
||||||
String version;
|
String version;
|
||||||
@@ -453,7 +365,7 @@ public class TelegramStickersGrabber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Sticker {
|
private static class Sticker {
|
||||||
String emoji;
|
String emoji;
|
||||||
String fileId;
|
String fileId;
|
||||||
|
|
||||||
|
|||||||
@@ -130,9 +130,9 @@ public class BinaryResourceValue implements SerializableResource {
|
|||||||
*/
|
*/
|
||||||
public static final int SIZE = 8;
|
public static final int SIZE = 8;
|
||||||
|
|
||||||
private final int size;
|
public int size;
|
||||||
private final Type type;
|
public Type type;
|
||||||
private final int data;
|
public int data;
|
||||||
|
|
||||||
public static BinaryResourceValue create(ByteBuffer buffer) {
|
public static BinaryResourceValue create(ByteBuffer buffer) {
|
||||||
int size = (buffer.getShort() & 0xFFFF);
|
int size = (buffer.getShort() & 0xFFFF);
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import com.vk.api.base.ApiRequest;
|
|||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vk.log.L;
|
import com.vk.log.L;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.music.Genius;
|
import ru.vtosters.lite.music.Genius;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ import com.vtosters.lite.R;
|
|||||||
import com.vtosters.lite.ViewUtils;
|
import com.vtosters.lite.ViewUtils;
|
||||||
import com.vtosters.lite.ui.MusicErrorViewHelper;
|
import com.vtosters.lite.ui.MusicErrorViewHelper;
|
||||||
import com.vtosters.lite.ui.holder.RecyclerHolder;
|
import com.vtosters.lite.ui.holder.RecyclerHolder;
|
||||||
import ru.vtosters.lite.music.Genius;
|
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
|
import ru.vtosters.lite.music.Genius;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|||||||
@@ -19661,6 +19661,9 @@ public final class R {
|
|||||||
public static final int cache_size = 0x7f12188d; // 2131892365
|
public static final int cache_size = 0x7f12188d; // 2131892365
|
||||||
public static final int cache_size_summ = 0x7f12188e; // 2131892366
|
public static final int cache_size_summ = 0x7f12188e; // 2131892366
|
||||||
public static final int clear_all_cache = 0x7f12188f; // 2131892367
|
public static final int clear_all_cache = 0x7f12188f; // 2131892367
|
||||||
|
public static final int invalidate_theme_cache_each_update_title = 0x7f121890; // 2131892368
|
||||||
|
public static final int invertFilters = 0x7f121891; // 2131892369
|
||||||
|
public static final int invertFilters_summ = 0x7f121892; // 2131892370
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class style {
|
public static final class style {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package ru.vtosters.hooks;
|
|||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.music.catalog.inject.CatalogJsonInjector;
|
import ru.vtosters.hooks.music.injectors.CatalogJsonInjector;
|
||||||
|
|
||||||
public class CatalogsHook {
|
public class CatalogsHook {
|
||||||
public static JSONObject injectCatalogGetAudio(JSONObject json) {
|
public static JSONObject injectCatalogGetAudio(JSONObject json) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class GcmHook {
|
|||||||
private static final String agent = String.format("Android-GCM/1.5 (%s %s)", Build.MODEL, Build.MODEL);
|
private static final String agent = String.format("Android-GCM/1.5 (%s %s)", Build.MODEL, Build.MODEL);
|
||||||
private static KeyPair pair;
|
private static KeyPair pair;
|
||||||
private static int rid = 0;
|
private static int rid = 0;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
genNewKey();
|
genNewKey();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import android.content.Context;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import com.google.firebase.iid.FirebaseInstanceIdReceiver;
|
import com.google.firebase.iid.FirebaseInstanceIdReceiver;
|
||||||
import com.google.firebase.iid.FirebaseInstanceIdReceiver2;
|
import com.google.firebase.iid.FirebaseInstanceIdReceiver2;
|
||||||
|
import com.google.firebase.iid.FirebaseInstanceIdReceiver3;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
public class GmsHook {
|
public class GmsHook {
|
||||||
private static final boolean needToSpoof = !isGmsInstalled() && isFakeGmsInstalled();
|
private static final boolean needToSpoof = !isGmsInstalled() && (isFakeGmsInstalled() || isFakeGms2Installed());
|
||||||
|
|
||||||
public static boolean isGmsInstalled() {
|
public static boolean isGmsInstalled() {
|
||||||
try {
|
try {
|
||||||
@@ -30,8 +31,17 @@ public class GmsHook {
|
|||||||
}
|
}
|
||||||
} // Microg Google Market Services check
|
} // Microg Google Market Services check
|
||||||
|
|
||||||
|
public static boolean isFakeGms2Installed() {
|
||||||
|
try {
|
||||||
|
AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo("app.revanced.android.gms", 0);
|
||||||
|
return true;
|
||||||
|
} catch (Exception unused) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // Microg Google Market Services check
|
||||||
|
|
||||||
public static boolean isAnyServicesInstalled() {
|
public static boolean isAnyServicesInstalled() {
|
||||||
return isGmsInstalled() || isFakeGmsInstalled();
|
return isGmsInstalled() || isFakeGmsInstalled() || isFakeGms2Installed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void fixGapps() {
|
public static void fixGapps() {
|
||||||
@@ -45,7 +55,7 @@ public class GmsHook {
|
|||||||
} // Music channels fix
|
} // Music channels fix
|
||||||
|
|
||||||
public static String replaceGMSPackage(String str) {
|
public static String replaceGMSPackage(String str) {
|
||||||
return needToSpoof ? str.replaceAll("com.google", "com.mgoogle") : str;
|
return needToSpoof ? str.replaceAll("com.google", (isFakeGms2Installed() ? "app.revanced" : "com.mgoogle")) : str;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getFirebaseInstanceIdReceiver() {
|
public static String getFirebaseInstanceIdReceiver() {
|
||||||
@@ -53,6 +63,6 @@ public class GmsHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Class getFirebaseInstanceIdReceiverClass() {
|
public static Class getFirebaseInstanceIdReceiverClass() {
|
||||||
return needToSpoof ? FirebaseInstanceIdReceiver2.class : FirebaseInstanceIdReceiver.class;
|
return needToSpoof ? (isFakeGms2Installed() ? FirebaseInstanceIdReceiver3.class : FirebaseInstanceIdReceiver2.class) : FirebaseInstanceIdReceiver.class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Activity;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import b.h.g.k.VKProgressDialog;
|
import b.h.g.k.VKProgressDialog;
|
||||||
|
import com.aefyr.tsg.g2.TelegramStickersService;
|
||||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
@@ -13,7 +14,6 @@ import ru.vtosters.lite.concurrent.VTExecutors;
|
|||||||
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
||||||
import ru.vtosters.lite.ssfs.UsersList;
|
import ru.vtosters.lite.ssfs.UsersList;
|
||||||
import ru.vtosters.lite.themes.ThemesManager;
|
import ru.vtosters.lite.themes.ThemesManager;
|
||||||
import ru.vtosters.lite.ui.dialogs.DisableBattery;
|
|
||||||
import ru.vtosters.lite.ui.dialogs.InstallGMS;
|
import ru.vtosters.lite.ui.dialogs.InstallGMS;
|
||||||
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
||||||
import ru.vtosters.lite.ui.dialogs.Start;
|
import ru.vtosters.lite.ui.dialogs.Start;
|
||||||
@@ -23,14 +23,12 @@ import ru.vtosters.lite.utils.NavigatorUtils;
|
|||||||
import ru.vtosters.lite.utils.VTVerifications;
|
import ru.vtosters.lite.utils.VTVerifications;
|
||||||
|
|
||||||
import static ru.vtosters.hooks.other.Preferences.checkupdates;
|
import static ru.vtosters.hooks.other.Preferences.checkupdates;
|
||||||
import static ru.vtosters.lite.ui.dialogs.ServerDialog.sendRequest;
|
|
||||||
import static ru.vtosters.lite.utils.CacheUtils.getInstance;
|
import static ru.vtosters.lite.utils.CacheUtils.getInstance;
|
||||||
import static ru.vtosters.lite.utils.NewsFeedFiltersUtils.setupFilters;
|
import static ru.vtosters.lite.utils.NewsFeedFiltersUtils.setupFilters;
|
||||||
|
|
||||||
public class MainActivityInjector {
|
public class MainActivityInjector {
|
||||||
public static void inject(Activity activity) {
|
public static void inject(Activity activity) {
|
||||||
SystemThemeChangerHook.themeOnStart(activity);
|
SystemThemeChangerHook.themeOnStart(activity);
|
||||||
sendRequest();
|
|
||||||
UsersList.getUsersList();
|
UsersList.getUsersList();
|
||||||
VTVerifications.load(activity);
|
VTVerifications.load(activity);
|
||||||
|
|
||||||
@@ -46,7 +44,9 @@ public class MainActivityInjector {
|
|||||||
NotificationChannels.createChannels();
|
NotificationChannels.createChannels();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Preferences.isNewBuild() && !ThemesUtils.isMonetTheme() && ThemesManager.canApplyCustomAccent() && ThemesUtils.useNewColorEngine()) {
|
if (Preferences.isNewBuild()
|
||||||
|
&& !ThemesUtils.isMonetTheme()
|
||||||
|
&& ThemesManager.canApplyCustomAccent()) {
|
||||||
Preferences.updateBuildNumber();
|
Preferences.updateBuildNumber();
|
||||||
updateBinsAndTmpArchive(activity);
|
updateBinsAndTmpArchive(activity);
|
||||||
}
|
}
|
||||||
@@ -60,8 +60,9 @@ public class MainActivityInjector {
|
|||||||
|
|
||||||
Start.alert(activity);
|
Start.alert(activity);
|
||||||
InstallGMS.alert(activity);
|
InstallGMS.alert(activity);
|
||||||
DisableBattery.alert(activity);
|
|
||||||
// VKIDProtection.alert(activity);
|
// VKIDProtection.alert(activity);
|
||||||
|
//needs to show selected tgs pack count in settings after cold launch
|
||||||
|
TelegramStickersService.getInstance(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void updateBinsAndTmpArchive(Activity activity) {
|
private static void updateBinsAndTmpArchive(Activity activity) {
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package ru.vtosters.hooks;
|
|
||||||
|
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class MusicCacheFilesHook {
|
|
||||||
public static File getTrackFile(String trackId) {
|
|
||||||
return new File(FileCacheImplementation.getTrackFolder(trackId), "track.mp3");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTrackExist(String trackId) {
|
|
||||||
return CacheDatabaseDelegate.isCached(trackId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,9 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import com.vk.core.preference.Preference;
|
import com.vk.core.preference.Preference;
|
||||||
import com.vk.discover.DiscoverItemDecorator;
|
import com.vk.discover.DiscoverItemDecorator;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static java.lang.Long.MAX_VALUE;
|
import static java.lang.Long.MAX_VALUE;
|
||||||
import static ru.vtosters.hooks.other.Preferences.*;
|
import static ru.vtosters.hooks.other.Preferences.*;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package ru.vtosters.hooks;
|
package ru.vtosters.hooks;
|
||||||
|
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.utils.RenameTool;
|
|
||||||
|
|
||||||
public class RenameHook {
|
public class RenameHook {
|
||||||
public static void injectIntoJson(JSONObject obj) throws JSONException {
|
public static void injectIntoJson(JSONObject obj) throws JSONException {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class VKUIHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void applyVKUIStyles(WebView webView) {
|
private static void applyVKUIStyles(WebView webView) {
|
||||||
if (getBoolValue("VKUI_INJ", true) && !webView.getUrl().contains("static.vk.com/memories")) {
|
if (getBoolValue("VKUI_INJ", true) && webView != null && webView.getUrl() != null && !webView.getUrl().contains("static.vk.com/memories")) {
|
||||||
if (!WebViewColoringUtils.isLoaded) {
|
if (!WebViewColoringUtils.isLoaded) {
|
||||||
WebViewColoringUtils.load();
|
WebViewColoringUtils.load();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class VerificationsHook {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getBoolValue("VT_Verification", true)) {
|
if (!getBoolValue("VT_Verification", true) || getBoolValue("serverFeaturesDisable", false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ public class VerificationsHook {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getBoolValue("VT_Fire", true)) {
|
if (!getBoolValue("VT_Fire", true) || getBoolValue("serverFeaturesDisable", false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,10 +40,6 @@ public class VerificationsHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasDeveloper(JSONObject jSONObject) {
|
public static boolean hasDeveloper(JSONObject jSONObject) {
|
||||||
if (!getBoolValue("VT_Dev", true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return VTVerifications.isDeveloper(VTVerifications.getId(jSONObject));
|
return VTVerifications.isDeveloper(VTVerifications.getId(jSONObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.hook;
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import com.vk.music.fragment.MusicFragment;
|
import com.vk.music.fragment.MusicFragment;
|
||||||
+14
-32
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.hook;
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
||||||
@@ -8,45 +8,29 @@ import com.vk.music.bottomsheets.a.MusicAction;
|
|||||||
import com.vk.music.common.MusicPlaybackLaunchContext;
|
import com.vk.music.common.MusicPlaybackLaunchContext;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.VKXUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.*;
|
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.*;
|
||||||
import static ru.vtosters.hooks.other.Preferences.milkshake;
|
import static ru.vtosters.hooks.other.Preferences.milkshake;
|
||||||
import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.isCached;
|
|
||||||
import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.isVkxCached;
|
|
||||||
import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected;
|
import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected;
|
||||||
|
|
||||||
public class MusicBottomSheetHook {
|
public class MusicBottomSheetHook {
|
||||||
public static ArrayList<MusicAction> hook(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
|
public static ArrayList<MusicAction> hook(final ArrayList<MusicAction> actions, final MusicTrack musicTrack) {
|
||||||
if (musicTrack.F1()) return actions;
|
if (musicTrack.F1()) return actions;
|
||||||
|
final var trackId = asId(musicTrack);
|
||||||
var trackid = asId(musicTrack);
|
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
|
||||||
|
|
||||||
if (isVkxInstalled()) {
|
|
||||||
actions.add(getPlayInVKXAction());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isIntegrationEnabled()) {
|
if (isIntegrationEnabled()) {
|
||||||
if (isVkxCached(trackid)) {
|
if (VKXUtils.isVkxCached(trackId)) actions.add(getRemoveCacheTrackVkxAction());
|
||||||
actions.add(getRemoveCacheTrackVkxAction());
|
else if (isNetworkConnected()) actions.add(addToCacheTrackVkxAction());
|
||||||
} else if (isNetworkConnected()) {
|
|
||||||
actions.add(addToCacheTrackVkxAction());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (isCached(trackid)) {
|
if (MusicCacheImpl.isCachedTrack(trackId)) actions.add(getRemoveCacheTrackAction());
|
||||||
actions.add(getRemoveCacheTrackAction());
|
else if (isNetworkConnected()) actions.add(addToCacheTrackAction());
|
||||||
} else if (isNetworkConnected()) {
|
|
||||||
actions.add(addToCacheTrackAction());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (isNetworkConnected()) actions.add(downloadAsMp3Action());
|
||||||
if (isNetworkConnected()) {
|
|
||||||
actions.add(downloadAsMp3Action());
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,9 +38,7 @@ public class MusicBottomSheetHook {
|
|||||||
public static ArrayList<MusicAction> hookDownloadBTN(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
|
public static ArrayList<MusicAction> hookDownloadBTN(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
|
||||||
if (musicTrack.F1()) return actions;
|
if (musicTrack.F1()) return actions;
|
||||||
|
|
||||||
var trackid = asId(musicTrack);
|
if (MusicCacheImpl.isCachedTrack(musicTrack.y1())) {
|
||||||
|
|
||||||
if (isCached(trackid)) {
|
|
||||||
actions.add(new MusicAction(
|
actions.add(new MusicAction(
|
||||||
R.id.music_action_toggle_download,
|
R.id.music_action_toggle_download,
|
||||||
milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24,
|
milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24,
|
||||||
@@ -85,7 +67,7 @@ public class MusicBottomSheetHook {
|
|||||||
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
|
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
|
||||||
|
|
||||||
if (isIntegrationEnabled()) {
|
if (isIntegrationEnabled()) {
|
||||||
if (isVkxCached(playlist.a, playlist.b)) {
|
if (VKXUtils.isVkxCached(playlist.a, playlist.b)) {
|
||||||
actions.add(getRemoveCacheTrackVkxAction());
|
actions.add(getRemoveCacheTrackVkxAction());
|
||||||
} else if (isNetworkConnected()) {
|
} else if (isNetworkConnected()) {
|
||||||
actions.add(addToCacheTrackVkxAction());
|
actions.add(addToCacheTrackVkxAction());
|
||||||
@@ -124,7 +106,7 @@ public class MusicBottomSheetHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (actionId == R.id.remove_from_cache) {
|
if (actionId == R.id.remove_from_cache) {
|
||||||
CacheDatabaseDelegate.removeTrackFromCache(asId(track));
|
MusicCacheImpl.removeTrack(asId(track));
|
||||||
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
||||||
return true;
|
return true;
|
||||||
} else if (actionId == R.id.add_to_cache) {
|
} else if (actionId == R.id.add_to_cache) {
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class MusicCacheFilesHook {
|
||||||
|
public static File getTrackFile(String trackId) {
|
||||||
|
return MusicCacheStorageUtils.getTrackFile(trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isTrackExist(String trackId) {
|
||||||
|
return MusicCacheImpl.isCachedTrack(trackId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.hook;
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.AttrRes;
|
import androidx.annotation.AttrRes;
|
||||||
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.catalog.inject;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
@@ -12,15 +12,15 @@ import org.json.JSONException;
|
|||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static ru.vtosters.hooks.DateHook.getLocale;
|
import static ru.vtosters.hooks.DateHook.getLocale;
|
||||||
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
|
||||||
import static ru.vtosters.hooks.GroupsCatalogInjector.injectIntoCatalog;
|
import static ru.vtosters.hooks.GroupsCatalogInjector.injectIntoCatalog;
|
||||||
|
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
||||||
import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.*;
|
import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.*;
|
||||||
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
|
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
|
||||||
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
|
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
|
||||||
@@ -48,7 +48,7 @@ public class CatalogJsonInjector {
|
|||||||
setDefaultAudioPage(oldItems, catalog);
|
setDefaultAudioPage(oldItems, catalog);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
||||||
var noPlaylists = !json.has("playlists");
|
var noPlaylists = !json.has("playlists");
|
||||||
|
|
||||||
if (noPlaylists) {
|
if (noPlaylists) {
|
||||||
@@ -91,7 +91,7 @@ public class CatalogJsonInjector {
|
|||||||
|
|
||||||
fixDailyMix(blocks);
|
fixDailyMix(blocks);
|
||||||
|
|
||||||
if (!isUsersCatalog || !CacheDatabaseDelegate.hasTracks() || LibVKXClient.isIntegrationEnabled()) {
|
if (!isUsersCatalog || MusicCacheImpl.isEmpty() || LibVKXClient.isIntegrationEnabled()) {
|
||||||
return; // early return if not users catalog or no tracks or integration enabled
|
return; // early return if not users catalog or no tracks or integration enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.cache.injectors;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -12,7 +12,7 @@ import io.reactivex.ObservableOnSubscribe;
|
|||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
||||||
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ public class PlaylistInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean eligibleForOfflineCaching() {
|
public static boolean eligibleForOfflineCaching() {
|
||||||
return CacheDatabaseDelegate.hasTracks();
|
return !MusicCacheImpl.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
|
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.cache.injectors;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vk.music.model.IpcPlayerModelImpl;
|
import com.vk.music.model.IpcPlayerModelImpl;
|
||||||
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.cache.injectors;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
@@ -11,7 +11,7 @@ import io.reactivex.ObservableOnSubscribe;
|
|||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
||||||
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
||||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||||
@@ -56,7 +56,7 @@ public class TracklistInjector {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!CacheDatabaseDelegate.hasTracks()) {
|
if (MusicCacheImpl.isEmpty()) {
|
||||||
observableEmitter.b(parser.c(getEmptyCatalog()));
|
observableEmitter.b(parser.c(getEmptyCatalog()));
|
||||||
} else {
|
} else {
|
||||||
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
|
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
|
||||||
@@ -135,7 +135,7 @@ public class TracklistInjector {
|
|||||||
|
|
||||||
button.put("action", action);
|
button.put("action", action);
|
||||||
button.put("block_id", "shuffle");
|
button.put("block_id", "shuffle");
|
||||||
button.put("ref_items_count", CacheDatabaseDelegate.getTrackCount());
|
button.put("ref_items_count", MusicCacheImpl.getTracksCount());
|
||||||
button.put("ref_layout_name", "list");
|
button.put("ref_layout_name", "list");
|
||||||
button.put("ref_data_type", "music_audios");
|
button.put("ref_data_type", "music_audios");
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ public class TracklistInjector {
|
|||||||
|
|
||||||
JSONArray blocks = firstSection.getJSONArray("blocks");
|
JSONArray blocks = firstSection.getJSONArray("blocks");
|
||||||
|
|
||||||
if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
||||||
boolean noPlaylists = hasNoPlaylists(blocks);
|
boolean noPlaylists = hasNoPlaylists(blocks);
|
||||||
|
|
||||||
if (noPlaylists) {
|
if (noPlaylists) {
|
||||||
@@ -263,7 +263,7 @@ public class Preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean stories() {
|
public static boolean stories() {
|
||||||
return getBoolValue("stories", true);
|
return getBoolValue("stories", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean swipe() {
|
public static boolean swipe() {
|
||||||
@@ -288,16 +288,20 @@ public class Preferences {
|
|||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
public static boolean checkupdates() {
|
public static boolean checkupdates() {
|
||||||
return !getBoolValue("isRoamingState", false) && isValidSignature() && BuildConfig.BUILD_TYPE.equals("release");
|
return !getBoolValue("isRoamingState", false) && isValidSignature() && BuildConfig.BUILD_TYPE.equals("release") && getBoolValue("autoupdates", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isNewBuild() {
|
public static boolean isNewBuild() {
|
||||||
try {
|
if (Preferences.getBoolValue("invalidate_theme_cache_each_update", true))
|
||||||
return getPreferences().getLong("setupTime", 0L) != AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo(AndroidUtils.getPackageName(), 0).lastUpdateTime;
|
try {
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
return AndroidUtils.getGlobalContext()
|
||||||
e.printStackTrace();
|
.getPackageManager()
|
||||||
return false;
|
.getPackageInfo(AndroidUtils.getPackageName(), 0)
|
||||||
}
|
.lastUpdateTime != getPreferences().getLong("setupTime", 0L);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateBuildNumber() {
|
public static void updateBuildNumber() {
|
||||||
|
|||||||
@@ -16,12 +16,10 @@ import android.webkit.WebView;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
import com.vk.articles.preload.WebCachePreloader;
|
import com.vk.articles.preload.WebCachePreloader;
|
||||||
import com.vk.core.drawable.RecoloredDrawable;
|
import com.vk.core.drawable.RecoloredDrawable;
|
||||||
import com.vk.core.preference.Preference;
|
|
||||||
import com.vk.core.ui.themes.MilkshakeHelper;
|
import com.vk.core.ui.themes.MilkshakeHelper;
|
||||||
import com.vk.core.ui.themes.VKTheme;
|
import com.vk.core.ui.themes.VKTheme;
|
||||||
import com.vk.core.ui.themes.VKThemeHelper;
|
import com.vk.core.ui.themes.VKThemeHelper;
|
||||||
@@ -30,6 +28,7 @@ import com.vtosters.lite.data.ThemeTracker;
|
|||||||
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
||||||
import ru.vtosters.lite.themes.ThemesCore;
|
import ru.vtosters.lite.themes.ThemesCore;
|
||||||
import ru.vtosters.lite.themes.ThemesHacks;
|
import ru.vtosters.lite.themes.ThemesHacks;
|
||||||
|
import ru.vtosters.lite.themes.ThemesManager;
|
||||||
import ru.vtosters.lite.ui.wallpapers.WallpapersHooks;
|
import ru.vtosters.lite.ui.wallpapers.WallpapersHooks;
|
||||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||||
import ru.vtosters.lite.utils.WebViewColoringUtils;
|
import ru.vtosters.lite.utils.WebViewColoringUtils;
|
||||||
@@ -48,10 +47,6 @@ public class ThemesUtils {
|
|||||||
}
|
}
|
||||||
} // Apply VKTheme and ImTheme (hard applying without dynamic theme changing)
|
} // Apply VKTheme and ImTheme (hard applying without dynamic theme changing)
|
||||||
|
|
||||||
public static boolean isCustomAccentEnabled() {
|
|
||||||
return ThemesUtils.isMonetTheme() || !useNewColorEngine();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ColorStateList getAccenedColorStateList() {
|
public static ColorStateList getAccenedColorStateList() {
|
||||||
return ColorStateList.valueOf(getAccentColor());
|
return ColorStateList.valueOf(getAccentColor());
|
||||||
}
|
}
|
||||||
@@ -65,7 +60,7 @@ public class ThemesUtils {
|
|||||||
activity = LifecycleUtils.getCurrentActivity();
|
activity = LifecycleUtils.getCurrentActivity();
|
||||||
}
|
}
|
||||||
VKThemeHelper.theme(theme, activity, fl);
|
VKThemeHelper.theme(theme, activity, fl);
|
||||||
if (isMonetTheme()) ThemesCore.clear();
|
if (isMonetTheme() || ThemesManager.canApplyCustomAccent()) ThemesCore.clear();
|
||||||
if (restartActivity) activity.recreate();
|
if (restartActivity) activity.recreate();
|
||||||
ThemeTracker.a();
|
ThemeTracker.a();
|
||||||
WebViewColoringUtils.isLoaded = false;
|
WebViewColoringUtils.isLoaded = false;
|
||||||
@@ -85,13 +80,8 @@ public class ThemesUtils {
|
|||||||
return getBoolValue("amoledtheme", false);
|
return getBoolValue("amoledtheme", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean useNewColorEngine() {
|
|
||||||
return getBoolValue("useNewColorEngine", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getAccentColor() {
|
public static int getAccentColor() {
|
||||||
var color = Preferences.getPreferences().getInt("accent_color", getColorFromAttr(R.attr.accent));
|
return getColorFromAttr(R.attr.accent);
|
||||||
return color == 0 || useNewColorEngine() || isMonetTheme() ? getColorFromAttr(R.attr.accent) : color;
|
|
||||||
} // Color accent
|
} // Color accent
|
||||||
|
|
||||||
//region Used for migrating accent color to new version
|
//region Used for migrating accent color to new version
|
||||||
@@ -309,7 +299,7 @@ public class ThemesUtils {
|
|||||||
} // Recolor drawable to accent color
|
} // Recolor drawable to accent color
|
||||||
|
|
||||||
public static Drawable recolorToolbarDrawable(Drawable drawable) {
|
public static Drawable recolorToolbarDrawable(Drawable drawable) {
|
||||||
if (!ThemesUtils.isCustomAccentEnabled()) return drawable;
|
if (!ThemesUtils.isMonetTheme()) return drawable;
|
||||||
if (drawable == null) return null;
|
if (drawable == null) return null;
|
||||||
return new RecoloredDrawable(drawable, (ThemesUtils.isMilkshake() && !ThemesUtils.isDarkTheme()) ? ThemesUtils.getAccentColor() : ThemesUtils.getHeaderText());
|
return new RecoloredDrawable(drawable, (ThemesUtils.isMilkshake() && !ThemesUtils.isDarkTheme()) ? ThemesUtils.getAccentColor() : ThemesUtils.getHeaderText());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package ru.vtosters.hooks.ssfs;
|
package ru.vtosters.hooks.ssfs;
|
||||||
|
|
||||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.ssfs.ImBanner;
|
import ru.vtosters.lite.ssfs.ImBanner;
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ public class ProfileHook {
|
|||||||
|
|
||||||
if (UsersList.hasDescription(userID)) {
|
if (UsersList.hasDescription(userID)) {
|
||||||
try {
|
try {
|
||||||
extendedUserProfile.z2 = Handler.getDescription(userID);
|
String description = Handler.getDescription(userID);
|
||||||
|
if (!TextUtils.isEmpty(description)) {
|
||||||
|
extendedUserProfile.z2 = description;
|
||||||
|
}
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
Log.e(TAG, "null \"profiles\" node\n" + e);
|
Log.e(TAG, "null \"profiles\" node\n" + e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import com.vk.core.util.ToastUtils;
|
|||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vk.dto.music.Playlist;
|
import com.vk.dto.music.Playlist;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.hooks.MusicCacheFilesHook;
|
import ru.vtosters.hooks.music.MusicCacheFilesHook;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
|
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
|
||||||
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
|
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
|
||||||
import ru.vtosters.lite.music.downloader.AudioGet;
|
import ru.vtosters.lite.music.downloader.AudioGet;
|
||||||
@@ -58,10 +58,10 @@ public class AudioDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void cacheTrack(MusicTrack track) {
|
public static void cacheTrack(MusicTrack track) {
|
||||||
var trackId = track.y1();
|
var trackId = LibVKXClient.asId(track);
|
||||||
|
|
||||||
if (CacheDatabaseDelegate.isCached(trackId)) {
|
if (MusicCacheImpl.isCachedTrack(trackId)) {
|
||||||
CacheDatabaseDelegate.removeTrackFromCache(LibVKXClient.asId(track));
|
MusicCacheImpl.removeTrack(trackId);
|
||||||
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
package ru.vtosters.lite.foaf;
|
package ru.vtosters.lite.foaf;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||||
import com.vk.core.network.Network;
|
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import okhttp3.Headers;
|
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -15,13 +12,9 @@ import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
|||||||
import ru.vtosters.lite.net.Request;
|
import ru.vtosters.lite.net.Request;
|
||||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -34,43 +27,15 @@ import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
|||||||
|
|
||||||
public class FoafBase {
|
public class FoafBase {
|
||||||
private static final Pattern FOAF_REGEX = Pattern.compile("<ya:created dc:date=\"(.+?)\"");
|
private static final Pattern FOAF_REGEX = Pattern.compile("<ya:created dc:date=\"(.+?)\"");
|
||||||
private static final Pattern FOAF_REGEX_LAST_SEEN = Pattern.compile("<ya:lastLoggedIn dc:date=\"(.*)(((\\+|-)\\d\\d):(\\d\\d))\"");
|
|
||||||
private static final String API_VKNEXT = "https://api.vtosters.app/v1/getBypassedOnlineInfo?json=1&ids=";
|
private static final String API_VKNEXT = "https://api.vtosters.app/v1/getBypassedOnlineInfo?json=1&ids=";
|
||||||
private static final OkHttpClient client = VtOkHttpClient.getInstance();
|
private static final OkHttpClient client = VtOkHttpClient.getInstance();
|
||||||
|
|
||||||
public static long getLastSeen(long origtime, int id) throws ParseException {
|
|
||||||
var request = new okhttp3.Request.a()
|
|
||||||
.b(getLink(id))
|
|
||||||
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
|
|
||||||
|
|
||||||
String response = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = client.a(request).execute().a().g();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e("FoafBase", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
Matcher matcher = FOAF_REGEX_LAST_SEEN.matcher(response);
|
|
||||||
|
|
||||||
if (!matcher.find()) {
|
|
||||||
return origtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
|
|
||||||
sdf.setTimeZone(TimeZone.getTimeZone("Europe/Moscow"));
|
|
||||||
Date date = sdf.parse(Objects.requireNonNull(matcher.group(1)));
|
|
||||||
sdf.setTimeZone(TimeZone.getDefault());
|
|
||||||
return sdf.parse(sdf.format(date)).getTime() / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static JSONObject getBypassedOnlineInfo(int id) throws JSONException {
|
public static JSONObject getBypassedOnlineInfo(int id) throws JSONException {
|
||||||
var request = new okhttp3.Request.a()
|
var request = new okhttp3.Request.a()
|
||||||
.b(API_VKNEXT + id)
|
.b(API_VKNEXT + id)
|
||||||
.a();
|
.a();
|
||||||
|
|
||||||
String response = null;
|
String response;
|
||||||
|
|
||||||
var dummy = new JSONObject();
|
var dummy = new JSONObject();
|
||||||
dummy.put("last_seen", 0);
|
dummy.put("last_seen", 0);
|
||||||
@@ -86,7 +51,6 @@ public class FoafBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static JSONObject getBypassedOnlineInfo(String ids) {
|
public static JSONObject getBypassedOnlineInfo(String ids) {
|
||||||
|
|
||||||
var request = new okhttp3.Request.a()
|
var request = new okhttp3.Request.a()
|
||||||
.b(API_VKNEXT + ids)
|
.b(API_VKNEXT + ids)
|
||||||
.a();
|
.a();
|
||||||
@@ -135,7 +99,15 @@ public class FoafBase {
|
|||||||
progressDialog.cancel();
|
progressDialog.cancel();
|
||||||
Matcher matcher = FOAF_REGEX.matcher(str);
|
Matcher matcher = FOAF_REGEX.matcher(str);
|
||||||
matcher.find();
|
matcher.find();
|
||||||
String normalHumanDate = getNormalHumanDate(Objects.requireNonNull(matcher.group(1)));
|
|
||||||
|
String m = matcher.group(1);
|
||||||
|
|
||||||
|
if (m == null) {
|
||||||
|
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> makeText(getGlobalContext(), context.getString(R.string.foaferr), LENGTH_SHORT).show());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalHumanDate = getNormalHumanDate(m);
|
||||||
|
|
||||||
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> new VkAlertDialog.Builder(context)
|
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> new VkAlertDialog.Builder(context)
|
||||||
.setTitle(R.string.addinf)
|
.setTitle(R.string.addinf)
|
||||||
@@ -147,17 +119,14 @@ public class FoafBase {
|
|||||||
.show());
|
.show());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> makeText(getGlobalContext(), context.getString(R.string.foaferr), LENGTH_SHORT).show());
|
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> makeText(getGlobalContext(), context.getString(R.string.foaferr), LENGTH_SHORT).show());
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long daysPassedFromFoafDate(String str) {
|
private static long daysPassedFromFoafDate(String str) {
|
||||||
try {
|
try {
|
||||||
@SuppressLint("SimpleDateFormat")
|
|
||||||
Date parse = new SimpleDateFormat("yyyy-MM-dd").parse(str.split(" ")[1]);
|
Date parse = new SimpleDateFormat("yyyy-MM-dd").parse(str.split(" ")[1]);
|
||||||
return TimeUnit.DAYS.convert(new Date().getTime() - parse.getTime(), TimeUnit.MILLISECONDS);
|
return TimeUnit.DAYS.convert(new Date().getTime() - parse.getTime(), TimeUnit.MILLISECONDS);
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
e.printStackTrace();
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import androidx.annotation.Nullable;
|
|||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import okhttp3.Callback;
|
|
||||||
import okhttp3.*;
|
import okhttp3.*;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -47,7 +46,7 @@ public class LastFMScrobbler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Preferences.autocache() && !CacheDatabaseDelegate.isCached(uid)) {
|
if (Preferences.autocache() && !MusicCacheImpl.isCachedTrack(uid)) {
|
||||||
if (LibVKXClient.isIntegrationEnabled()) {
|
if (LibVKXClient.isIntegrationEnabled()) {
|
||||||
LibVKXClient.getInstance().runOnService(service -> {
|
LibVKXClient.getInstance().runOnService(service -> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
package ru.vtosters.lite.music;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import java8.util.concurrent.CompletableFuture;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
|
||||||
import ru.vtosters.lite.music.converter.ts.FFMpeg;
|
|
||||||
import ru.vtosters.lite.music.converter.ts.TSMerger;
|
|
||||||
import ru.vtosters.lite.music.downloader.ThumbnailDownloader;
|
|
||||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
|
||||||
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
public class M3UDownloader implements ITrackDownloader {
|
|
||||||
private static final OkHttpClient client = VtOkHttpClient.getInstance();
|
|
||||||
|
|
||||||
public static M3UDownloader getInstance() {
|
|
||||||
return Holder.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getTitle(MusicTrack track) {
|
|
||||||
String title = null;
|
|
||||||
|
|
||||||
if (track.f != null) {
|
|
||||||
title = track.f;
|
|
||||||
if (!track.g.isEmpty()) {
|
|
||||||
title += " (" + track.g + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void downloadTrack(MusicTrack track, File outDir, Callback callback, boolean cache) {
|
|
||||||
if (track.D.isEmpty()) {
|
|
||||||
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
|
|
||||||
callback.onFailure();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Request request = new Request.a().b(track.D).a();
|
|
||||||
String response = client.a(request).execute().a().g();
|
|
||||||
parse(response, outDir, callback, track, cache);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
callback.onFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parse(String payload, File outDir, Callback callback, MusicTrack track, boolean cache) throws UnsupportedEncodingException {
|
|
||||||
if (cache) downloadThumbnails(track);
|
|
||||||
|
|
||||||
VKM3UParser parser = new VKM3UParser(track.D.substring(0, track.D.lastIndexOf("/") + 1), payload);
|
|
||||||
AtomicInteger progress = new AtomicInteger(0);
|
|
||||||
List<CompletableFuture<Void>> futures = createFutures(parser, progress, callback, track);
|
|
||||||
|
|
||||||
var resultTs = getResultTsFile(track);
|
|
||||||
var resultMp3 = getResultMp3File(outDir, cache, track);
|
|
||||||
|
|
||||||
callback.onProgress(5);
|
|
||||||
var future = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
|
|
||||||
future.join();
|
|
||||||
future.exceptionally(throwable -> {
|
|
||||||
callback.onFailure();
|
|
||||||
return null;
|
|
||||||
}).thenApply(v -> TSMerger.merge(getTsesDir(track), resultTs))
|
|
||||||
.thenApply(mergeResult -> mergeResult && FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track))
|
|
||||||
.thenApply(convertResult -> {
|
|
||||||
if (convertResult)
|
|
||||||
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / parser.mTransportStreams.size()));
|
|
||||||
else callback.onFailure();
|
|
||||||
return convertResult;
|
|
||||||
}).thenRun(() -> IOUtils.deleteRecursive(getTsesDir(track))).thenRun(() -> {
|
|
||||||
callback.onSuccess();
|
|
||||||
if (cache) CacheDatabaseDelegate.insertTrack(track);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadThumbnails(MusicTrack track) {
|
|
||||||
try {
|
|
||||||
ThumbnailDownloader.downloadThumbnails(track);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<CompletableFuture<Void>> createFutures(VKM3UParser parser, AtomicInteger progress, Callback callback, MusicTrack track) {
|
|
||||||
List<CompletableFuture<Void>> futures = new ArrayList<>(parser.mTransportStreams.size());
|
|
||||||
|
|
||||||
var tses = parser.mTransportStreams;
|
|
||||||
for (var i = 0; i < tses.size(); ++i) {
|
|
||||||
final var ts = tses.get(i);
|
|
||||||
final var filename = i + ".ts";
|
|
||||||
futures.add(CompletableFuture.runAsync(() -> processTransportStream(parser, ts, filename, progress, tses.size(), callback, track)));
|
|
||||||
}
|
|
||||||
return futures;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processTransportStream(VKM3UParser parser, TransportStream ts, String filename, AtomicInteger progress, int size, Callback callback, MusicTrack track) {
|
|
||||||
try {
|
|
||||||
InputStream mediaIs = IOUtils.openStream(ts.getMediaSegmentUri());
|
|
||||||
byte[] content;
|
|
||||||
if (ts.needDecoding()) {
|
|
||||||
InputStream keyPubIs = IOUtils.openStream(ts.getKeyURL());
|
|
||||||
content = IOUtils.decodeStream(mediaIs, IOUtils.readFully(keyPubIs));
|
|
||||||
keyPubIs.close();
|
|
||||||
} else {
|
|
||||||
content = IOUtils.readFully(mediaIs);
|
|
||||||
}
|
|
||||||
File tsDump = new File(getTsesDir(track), filename);
|
|
||||||
IOUtils.writeToFile(tsDump, content);
|
|
||||||
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / size));
|
|
||||||
callback.onSizeReceived((long) content.length * size, parser.mHeapSize);
|
|
||||||
mediaIs.close();
|
|
||||||
} catch (IOException | NoSuchPaddingException | NoSuchAlgorithmException |
|
|
||||||
InvalidAlgorithmParameterException | InvalidKeyException e) {
|
|
||||||
callback.onFailure();
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getTsesDir(MusicTrack track) {
|
|
||||||
var filesDir = AndroidUtils.getGlobalContext().getFilesDir();
|
|
||||||
var tsesDir = new File(filesDir, String.valueOf(track.d));
|
|
||||||
tsesDir.mkdirs();
|
|
||||||
return tsesDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getResultTsFile(MusicTrack track) {
|
|
||||||
return new File(getTsesDir(track), "result.ts");
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getResultMp3File(File outDir, boolean cache, MusicTrack track) {
|
|
||||||
var fileName = IOUtils.getValidFileName((cache ? "track" : track.C + " - " + getTitle(track)) + ".mp3");
|
|
||||||
return new File(outDir, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialization-on-demand
|
|
||||||
private static class Holder {
|
|
||||||
private static final M3UDownloader INSTANCE = new M3UDownloader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package ru.vtosters.lite.music;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
public class TransportStream {
|
|
||||||
|
|
||||||
private String mKeyUri;
|
|
||||||
private final String mMediaSegmentUri;
|
|
||||||
private String mMediaSegmentName;
|
|
||||||
|
|
||||||
public TransportStream(String baseURI, String mediaSegmentName) {
|
|
||||||
this.mMediaSegmentUri = baseURI + mediaSegmentName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransportStream(String keyURI, String baseURI, String mediaSegmentName) {
|
|
||||||
this.mKeyUri = keyURI;
|
|
||||||
this.mMediaSegmentUri = baseURI + mediaSegmentName;
|
|
||||||
this.mMediaSegmentName = mediaSegmentName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean needDecoding() {
|
|
||||||
return !TextUtils.isEmpty(mKeyUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKeyURL() {
|
|
||||||
return mKeyUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMediaSegmentUri() {
|
|
||||||
return this.mMediaSegmentUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMediaSegmentName() {
|
|
||||||
return this.mMediaSegmentName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package ru.vtosters.lite.music;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parser for extended M3U playlist format used by VK.
|
|
||||||
*
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#ref-M3U">M3U</a>
|
|
||||||
**/
|
|
||||||
public class VKM3UParser {
|
|
||||||
//Playlist types
|
|
||||||
static public int PLAYLIST_TYPE_VOD = 0;
|
|
||||||
static public int PLAYLIST_TYPE_EVENT = 1;
|
|
||||||
|
|
||||||
//region Extended M3U global extensions
|
|
||||||
public List<TransportStream> mTransportStreams = new ArrayList<>();
|
|
||||||
private final String mData;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.1.2">EXT-X-VERSION</a>
|
|
||||||
**/
|
|
||||||
public int mVersion = -1;
|
|
||||||
/**
|
|
||||||
* The EXT-X-ALLOW-CACHE tag is used in protocol version 6 or lower.
|
|
||||||
* If the protocol version is upgraded by VK, we will not support that.
|
|
||||||
**/
|
|
||||||
public boolean mAllowCache = false;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.1">EXT-X-TARGETDURATION</a>
|
|
||||||
**/
|
|
||||||
public int mTargetDuration = -1;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.2">EXT-X-MEDIA-SEQUENCE</a>
|
|
||||||
**/
|
|
||||||
public int mMediaSequence = -1;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.3">EXT-X-DISCONTINUITY-SEQUENCE</a>
|
|
||||||
**/
|
|
||||||
public int mDiscontinuitySequence = -1;
|
|
||||||
//endregion
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.5">EXT-X-PLAYLIST-TYPE</a>
|
|
||||||
**/
|
|
||||||
public int mPlaylistType = -1;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.6">EXT-X-I-FRAMES-ONLY</a>
|
|
||||||
**/
|
|
||||||
public boolean mIFramesOnly = false;
|
|
||||||
public long mHeapSize = 0;
|
|
||||||
|
|
||||||
public VKM3UParser(String baseUri, String data) {
|
|
||||||
mData = data;
|
|
||||||
init(baseUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTS(String str) {
|
|
||||||
return str.matches(".+\\.ts.*");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(String baseUri) {
|
|
||||||
if (TextUtils.isEmpty(mData)) throw new NullPointerException("mData==null");
|
|
||||||
Scanner scanner = new Scanner(mData);
|
|
||||||
String line = scanner.nextLine();
|
|
||||||
if (!"#EXTM3U".equals(line))
|
|
||||||
throw new IllegalStateException(String.format("Unknown initial M3U tag: %s", line));
|
|
||||||
boolean aes128 = false;
|
|
||||||
String keyUri = "";
|
|
||||||
while (scanner.hasNextLine()) {
|
|
||||||
line = scanner.nextLine();
|
|
||||||
if ('#' == line.charAt(0)) {
|
|
||||||
parseTag(line);
|
|
||||||
if (line.startsWith("#EXT-X-KEY:") && line.contains("METHOD=AES-128")) {
|
|
||||||
aes128 = true;
|
|
||||||
keyUri = getKeyUri(line, baseUri);
|
|
||||||
}
|
|
||||||
} else if (isTS(line)) {
|
|
||||||
addTransportStream(aes128, keyUri, baseUri, line);
|
|
||||||
aes128 = false;
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException(String.format("Failed to parse: %s", line));
|
|
||||||
}
|
|
||||||
mHeapSize += line.getBytes().length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseTag(String line) {
|
|
||||||
if (line.startsWith("#EXT-X-VERSION:"))
|
|
||||||
mVersion = Integer.parseInt(line.substring(15));
|
|
||||||
else if (line.startsWith("#EXT-X-ALLOW-CACHE:"))
|
|
||||||
mAllowCache = "YES".equalsIgnoreCase(line.substring(19));
|
|
||||||
else if (line.startsWith("#EXT-X-TARGETDURATION:"))
|
|
||||||
mTargetDuration = Integer.parseInt(line.substring(22));
|
|
||||||
else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE:"))
|
|
||||||
mMediaSequence = Integer.parseInt(line.substring(22));
|
|
||||||
else if (line.startsWith("#EXT-X-DISCONTINUITY-SEQUENCE:"))
|
|
||||||
mDiscontinuitySequence = Integer.parseInt(line.substring(30));
|
|
||||||
else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:"))
|
|
||||||
parsePlaylistType(line.substring(21));
|
|
||||||
else if (line.startsWith("#EXT-X-I-FRAMES-ONLY"))
|
|
||||||
mIFramesOnly = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parsePlaylistType(String type) {
|
|
||||||
switch (type) {
|
|
||||||
case "VOD" -> mPlaylistType = PLAYLIST_TYPE_VOD;
|
|
||||||
case "EVENT" -> mPlaylistType = PLAYLIST_TYPE_EVENT;
|
|
||||||
default -> throw new IllegalStateException(String.format("Unknown playlist type: %s", type));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getKeyUri(String line, String baseUri) {
|
|
||||||
String substr = line.substring(11);
|
|
||||||
substr = substr.substring(substr.indexOf('"') + 1, substr.lastIndexOf('"'));
|
|
||||||
//TODO: supposed endpoint
|
|
||||||
return substr.startsWith("http") ? substr : baseUri + substr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addTransportStream(boolean aes128, String keyUri, String baseUri, String line) {
|
|
||||||
TransportStream ts = aes128
|
|
||||||
? new TransportStream(keyUri, baseUri, line)
|
|
||||||
: new TransportStream(baseUri, line);
|
|
||||||
mTransportStreams.add(ts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
package ru.vtosters.lite.music.cache;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.DatabaseUtils;
|
|
||||||
import android.database.SQLException;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
|
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
|
||||||
import com.vk.core.util.ToastUtils;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import com.vtosters.lite.R;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static ru.vtosters.lite.music.cache.FileCacheImplementation.getCacheDir;
|
|
||||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
|
||||||
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
|
|
||||||
import static ru.vtosters.lite.utils.IOUtils.deleteRecursive;
|
|
||||||
|
|
||||||
public class CacheDatabaseDelegate {
|
|
||||||
private static final int DB_VERSION = 0x1;
|
|
||||||
private static final String DB_NAME = "vt_lite_cache.db";
|
|
||||||
private static final String TABLE_NAME = "tracks";
|
|
||||||
private static final String COLUMN_ID = "id";
|
|
||||||
private static final String COLUMN_TRACK_ID = "trackId";
|
|
||||||
private static final String COLUMN_ALBUM_ID = "albumId";
|
|
||||||
private static final String COLUMN_TITLE = "title";
|
|
||||||
private static final String COLUMN_SUBTITLE = "subtitle";
|
|
||||||
private static final String COLUMN_ARTIST = "artist";
|
|
||||||
private static final String COLUMN_ALBUM_TITLE = "albumTitle";
|
|
||||||
private static final String COLUMN_EXPLICIT = "explicit";
|
|
||||||
private static final String COLUMN_HAS_ARTWORK = "hasArtwork";
|
|
||||||
private static final String COLUMN_DURATION = "duration";
|
|
||||||
private static DbHelper helperInstance;
|
|
||||||
|
|
||||||
protected static DbHelper getHelper() {
|
|
||||||
if (helperInstance == null) helperInstance = new DbHelper(getGlobalContext());
|
|
||||||
return helperInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// You MUST call this method ONLY AFTER TRACK IS DOWNLOADED!
|
|
||||||
public static void insertTrack(MusicTrack track) {
|
|
||||||
try {
|
|
||||||
var json = track.J();
|
|
||||||
var albumNode = json.optJSONObject("album");
|
|
||||||
|
|
||||||
var hasArtwork = false;
|
|
||||||
var albumId = -1;
|
|
||||||
var albumName = "";
|
|
||||||
|
|
||||||
if (albumNode != null) {
|
|
||||||
hasArtwork = albumNode.has("thumb");
|
|
||||||
albumId = albumNode.getInt("id");
|
|
||||||
albumName = albumNode.getString("title");
|
|
||||||
}
|
|
||||||
|
|
||||||
var formula = String.format(
|
|
||||||
"INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES ('%s', %s, '%s', '%s', '%s', '%s', %s, %s, %s)",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_TRACK_ID, COLUMN_ALBUM_ID, COLUMN_TITLE, COLUMN_SUBTITLE, COLUMN_ARTIST, COLUMN_ALBUM_TITLE, COLUMN_EXPLICIT, COLUMN_DURATION, COLUMN_HAS_ARTWORK,
|
|
||||||
track.y1(),
|
|
||||||
albumId,
|
|
||||||
URLEncoder.encode(json.getString("title"), "UTF-8"),
|
|
||||||
URLEncoder.encode(json.optString("subtitle"), "UTF-8"),
|
|
||||||
URLEncoder.encode(json.getString("artist"), "UTF-8"),
|
|
||||||
URLEncoder.encode(albumName, "UTF-8"),
|
|
||||||
json.optInt("is_explicit"),
|
|
||||||
json.getInt("duration"),
|
|
||||||
hasArtwork ? 1 : 0
|
|
||||||
);
|
|
||||||
getHelper().getReadableDatabase().execSQL(formula);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
ToastUtils.a(AndroidUtils.getString(R.string.track_bd_insert_err) + " [" + e.getMessage() + "]", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void insertTracks(List<MusicTrack> tracks) {
|
|
||||||
for (var track : tracks) insertTrack(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear() {
|
|
||||||
getHelper().getReadableDatabase().execSQL(String.format("DELETE FROM %s", TABLE_NAME));
|
|
||||||
deleteRecursive(getCacheDir());
|
|
||||||
sendToast(AndroidUtils.getString(R.string.cached_tracks_deleted));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static JSONObject reconstructFromDb(Cursor cursor) {
|
|
||||||
try {
|
|
||||||
var trackId = cursor.getString(getColumnIndex(cursor, COLUMN_TRACK_ID));
|
|
||||||
var splits = trackId.split("_");
|
|
||||||
var id = Integer.parseInt(splits[1]);
|
|
||||||
var ownerId = Integer.parseInt(splits[0]);
|
|
||||||
|
|
||||||
return new JSONObject()
|
|
||||||
.put("url", FileCacheImplementation.getServerPath())
|
|
||||||
.put("id", id)
|
|
||||||
.put("owner_id", ownerId)
|
|
||||||
.put("title", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_TITLE)), "UTF-8"))
|
|
||||||
.put("subtitle", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_SUBTITLE)), "UTF-8"))
|
|
||||||
.put("artist", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_ARTIST)), "UTF-8"))
|
|
||||||
.put("duration", cursor.getInt(getColumnIndex(cursor, COLUMN_DURATION)))
|
|
||||||
.put("is_explicit", cursor.getInt(getColumnIndex(cursor, COLUMN_EXPLICIT)))
|
|
||||||
.put("access_key", 2281488)
|
|
||||||
.putOpt("album", reconstructAlbumFromDb(cursor, trackId));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return new JSONObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static JSONObject reconstructAlbumFromDb(Cursor cursor, String trackId) {
|
|
||||||
try {
|
|
||||||
if (!FileCacheImplementation.getTrackThumbnail(trackId, 300).exists()
|
|
||||||
&& !FileCacheImplementation.getTrackThumbnail(trackId, 600).exists()
|
|
||||||
&& !FileCacheImplementation.getTrackThumbnail(trackId, 1200).exists()
|
|
||||||
) {
|
|
||||||
var ownerId = Integer.parseInt(trackId.split("_")[1]);
|
|
||||||
return new JSONObject()
|
|
||||||
.put("id", cursor.getInt(getColumnIndex(cursor, COLUMN_ALBUM_ID)))
|
|
||||||
.put("owner_id", ownerId)
|
|
||||||
.put("title", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_ALBUM_TITLE)), "UTF-8"))
|
|
||||||
.putOpt("thumb", reconstructAlbumThumbFromDb(cursor, trackId));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static JSONObject reconstructAlbumThumbFromDb(Cursor cursor, String trackId) {
|
|
||||||
try {
|
|
||||||
if (cursor.getInt(getColumnIndex(cursor, COLUMN_HAS_ARTWORK)) != 0)
|
|
||||||
new JSONObject()
|
|
||||||
.put("height", 600)
|
|
||||||
.put("width", 600)
|
|
||||||
.putOpt("photo_300", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 300)))
|
|
||||||
.putOpt("photo_600", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 600)))
|
|
||||||
.putOpt("photo_1200", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 1200)));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getColumnIndex(Cursor cursor, String name) {
|
|
||||||
return cursor.getColumnIndexOrThrow(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean hasTracks() {
|
|
||||||
if (LibVKXClient.isIntegrationEnabled()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var formula = String.format(
|
|
||||||
"SELECT COUNT(%s) FROM %s",
|
|
||||||
COLUMN_ID,
|
|
||||||
TABLE_NAME
|
|
||||||
);
|
|
||||||
var cursor = getHelper().getReadableDatabase().rawQuery(formula, null);
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.moveToFirst();
|
|
||||||
int count = cursor.getInt(0);
|
|
||||||
cursor.close();
|
|
||||||
return count > 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks() {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT * FROM %s",
|
|
||||||
TABLE_NAME
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksAsPlaylist() {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT * FROM %s WHERE %s > -1 GROUP BY %s HAVING COUNT(*) > 1 ORDER by id DESC",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ALBUM_ID,
|
|
||||||
COLUMN_ALBUM_ID
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksByAlbum(String id) {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT COUNT(%s) FROM %s WHERE %s = %s",
|
|
||||||
COLUMN_ID,
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ALBUM_ID,
|
|
||||||
id
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksByAlbumOne(String id) {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT * FROM %s WHERE %s = %s LIMIT 1",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ALBUM_ID,
|
|
||||||
id
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks(String query) {
|
|
||||||
List<MusicTrack> list = new ArrayList<>();
|
|
||||||
try (Cursor cursor = getHelper().getReadableDatabase().rawQuery(query, new String[0])) {
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
list.add(new MusicTrack(reconstructFromDb(cursor)));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isCached(String trackId) {
|
|
||||||
var formula = String.format(
|
|
||||||
"SELECT COUNT(%s) FROM %s WHERE %s = '%s'",
|
|
||||||
COLUMN_ID,
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_TRACK_ID,
|
|
||||||
trackId
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
Cursor size = getHelper().getReadableDatabase().rawQuery(formula, null);
|
|
||||||
if (size != null) {
|
|
||||||
size.moveToFirst();
|
|
||||||
int count = size.getInt(0);
|
|
||||||
size.close();
|
|
||||||
return count != 0;
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isVkxCached(int playlist_id, int owner_id) {
|
|
||||||
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
|
|
||||||
@Override
|
|
||||||
public Boolean run(ILibVkxService service) {
|
|
||||||
try {
|
|
||||||
return service.isPlaylistCached(playlist_id, owner_id);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean defaultValue() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isVkxCached(String trackId) {
|
|
||||||
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
|
|
||||||
@Override
|
|
||||||
public Boolean run(ILibVkxService service) {
|
|
||||||
try {
|
|
||||||
var splits = trackId.split("_");
|
|
||||||
var id = Integer.parseInt(splits[1]);
|
|
||||||
var owner = Integer.parseInt(splits[0]);
|
|
||||||
return service.isTrackCached(id, owner);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean defaultValue() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeTrackFromCache(String trackId) {
|
|
||||||
deleteRecursive(FileCacheImplementation.getTrackFolder(trackId));
|
|
||||||
|
|
||||||
var formula = String.format(
|
|
||||||
"DELETE FROM %s WHERE %s = '%s'",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_TRACK_ID,
|
|
||||||
trackId
|
|
||||||
);
|
|
||||||
getHelper().getWritableDatabase().execSQL(formula);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long getTrackCount() {
|
|
||||||
if (!LibVKXClient.isIntegrationEnabled())
|
|
||||||
return DatabaseUtils.queryNumEntries(getHelper().getReadableDatabase(), TABLE_NAME);
|
|
||||||
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<Long>() {
|
|
||||||
@Override
|
|
||||||
public Long run(ILibVkxService service) {
|
|
||||||
try {
|
|
||||||
return (long) service.getCache().size();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return defaultValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long defaultValue() {
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DbHelper extends SQLiteOpenHelper {
|
|
||||||
public DbHelper(Context context) {
|
|
||||||
super(context, DB_NAME, null, DB_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCreate(SQLiteDatabase db) {
|
|
||||||
var formula = String.format(
|
|
||||||
"CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s TEXT, %s INTEGER, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s INTEGER, %s INTEGER, %s INTEGER)",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ID, COLUMN_TRACK_ID, COLUMN_ALBUM_ID, COLUMN_TITLE, COLUMN_SUBTITLE, COLUMN_ARTIST, COLUMN_ALBUM_TITLE, COLUMN_EXPLICIT, COLUMN_DURATION, COLUMN_HAS_ARTWORK
|
|
||||||
);
|
|
||||||
db.execSQL(formula);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package ru.vtosters.lite.music.cache;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import ru.vtosters.hooks.MusicCacheFilesHook;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class FileCacheImplementation {
|
|
||||||
|
|
||||||
public static void clear() {
|
|
||||||
CacheDatabaseDelegate.clear();
|
|
||||||
IOUtils.deleteRecursive(getCacheDir(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getCacheDir() {
|
|
||||||
return AndroidUtils.getGlobalContext().getExternalFilesDir("vt_tracks");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getTrackFolder(String trackId) {
|
|
||||||
var dir = new File(getCacheDir(), trackId);
|
|
||||||
dir.mkdirs();
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getThumbnailsFolder(String trackId) {
|
|
||||||
var dir = new File(getTrackFolder(trackId), "thumbs");
|
|
||||||
dir.mkdirs();
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getTrackFile(MusicTrack track) {
|
|
||||||
return MusicCacheFilesHook.getTrackFile(track.y1());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getTrackThumbnail(String trackId, int factor) {
|
|
||||||
return new File(getThumbnailsFolder(trackId), "thumb_" + factor + ".png");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getFileUri(File file) {
|
|
||||||
if (!file.exists()) return null;
|
|
||||||
return Uri.fromFile(file).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getServerPath() {
|
|
||||||
return "https://vtosters.app";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package ru.vtosters.lite.music.cache;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MusicCacheDbDelegate {
|
||||||
|
static synchronized private MusicCacheDb connectToDb(Context context) {
|
||||||
|
synchronized (MusicCacheDb.class) {
|
||||||
|
return new MusicCacheDb(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addTrack(Context context,
|
||||||
|
String trackId,
|
||||||
|
String albumId,
|
||||||
|
String title,
|
||||||
|
String subtitle,
|
||||||
|
String artist,
|
||||||
|
String albumTitle,
|
||||||
|
boolean explicit,
|
||||||
|
int duration,
|
||||||
|
boolean hasArtwork) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
db.addTrack(
|
||||||
|
trackId,
|
||||||
|
albumId,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
artist,
|
||||||
|
albumTitle,
|
||||||
|
explicit,
|
||||||
|
duration,
|
||||||
|
hasArtwork);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeTrack(Context context, String trackId) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
db.deleteTrack(trackId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getAllTracks(Context context) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
return db.getAllTracks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getAlbumById(Context context, String albumId) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
return db.getAlbum(albumId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getFirstAlbumTrack(Context context, String albumId) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
return db.getFirstAlbumTrack(albumId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getPlaylist(Context context) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
return db.getPlaylist();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getTracksCount(Context context) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
return db.getTracksCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isCachedTrack(Context context, String trackId) {
|
||||||
|
try (var db = connectToDb(context)) {
|
||||||
|
return db.isCachedTrack(trackId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void drop(Context context) {
|
||||||
|
context.deleteDatabase(MusicCacheDb.Constants.DB_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package ru.vtosters.lite.music.cache;
|
||||||
|
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MusicCacheImpl {
|
||||||
|
public static void addTrack(MusicTrack track) {
|
||||||
|
MusicCacheDbDelegate.addTrack(
|
||||||
|
AndroidUtils.getGlobalContext(),
|
||||||
|
track.y1(),
|
||||||
|
track.I != null ? track.I.getId() + "" : "-1",
|
||||||
|
track.f,
|
||||||
|
track.g,
|
||||||
|
track.C,
|
||||||
|
track.I != null ? track.I.getTitle() : "",
|
||||||
|
track.K,
|
||||||
|
track.h,
|
||||||
|
track.I != null && track.I.u1() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeTrack(String trackId) {
|
||||||
|
MusicCacheDbDelegate.removeTrack(AndroidUtils.getGlobalContext(), trackId);
|
||||||
|
MusicCacheStorageUtils.removeTrackDirById(trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getAllTracks() {
|
||||||
|
return MusicCacheDbDelegate.getAllTracks(AndroidUtils.getGlobalContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getAlbumById(String albumId) {
|
||||||
|
return MusicCacheDbDelegate.getAlbumById(AndroidUtils.getGlobalContext(), albumId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getFirstAlbumTrack(String albumId) {
|
||||||
|
return MusicCacheDbDelegate.getFirstAlbumTrack(AndroidUtils.getGlobalContext(), albumId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MusicTrack> getPlaylist() {
|
||||||
|
return MusicCacheDbDelegate.getPlaylist(AndroidUtils.getGlobalContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getTracksCount() {
|
||||||
|
return !LibVKXClient.isIntegrationEnabled()
|
||||||
|
? MusicCacheDbDelegate.getTracksCount(AndroidUtils.getGlobalContext())
|
||||||
|
: LibVKXClient.getInstance().runOnServiceSync(
|
||||||
|
new LibVKXClientImpl.LibVKXActionGeneric<Long>() {
|
||||||
|
@Override
|
||||||
|
public Long run(ILibVkxService service) {
|
||||||
|
try {
|
||||||
|
return (long) service.getCache().size();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return defaultValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long defaultValue() {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isCachedTrack(String trackId) {
|
||||||
|
return MusicCacheDbDelegate.isCachedTrack(AndroidUtils.getGlobalContext(), trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEmpty() {
|
||||||
|
return !LibVKXClient.isIntegrationEnabled() && getTracksCount() == 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
MusicCacheDbDelegate.drop(AndroidUtils.getGlobalContext());
|
||||||
|
MusicCacheStorageUtils.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
package ru.vtosters.lite.music.cache.db;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.DatabaseUtils;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.vk.dto.music.AlbumLink;
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
import com.vk.dto.music.Thumb;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p class="note"><strong>Note:</strong> the {@link AutoCloseable} interface was
|
||||||
|
* first added in the {@link android.os.Build.VERSION_CODES#Q} release for the {@link SQLiteOpenHelper} </p>
|
||||||
|
*/
|
||||||
|
public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //for pre-Q
|
||||||
|
public MusicCacheDb(Context context) {
|
||||||
|
super(context, Constants.DB_NAME, null, Constants.DV_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MusicTrack fromCursor(Cursor cur) {
|
||||||
|
try {
|
||||||
|
int trackIdIndex = cur.getColumnIndex(Constants.COLUMN_TRACK_ID);
|
||||||
|
int titleIndex = cur.getColumnIndex(Constants.COLUMN_TITLE);
|
||||||
|
int subtitleIndex = cur.getColumnIndex(Constants.COLUMN_SUBTITLE);
|
||||||
|
int artistIndex = cur.getColumnIndex(Constants.COLUMN_ARTIST);
|
||||||
|
int durationIndex = cur.getColumnIndex(Constants.COLUMN_DURATION);
|
||||||
|
int explicitIndex = cur.getColumnIndex(Constants.COLUMN_EXPLICIT);
|
||||||
|
int albumIdIndex = cur.getColumnIndex(Constants.COLUMN_ALBUM_ID);
|
||||||
|
int albumTitleIndex = cur.getColumnIndex(Constants.COLUMN_ALBUM_TITLE);
|
||||||
|
|
||||||
|
if (trackIdIndex == -1 || titleIndex == -1 || subtitleIndex == -1 || artistIndex == -1 || durationIndex == -1 || explicitIndex == -1 || albumIdIndex == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String trackId = cur.getString(trackIdIndex);
|
||||||
|
String[] splits = trackId.split("_");
|
||||||
|
|
||||||
|
if (splits.length != 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MusicTrack res = new MusicTrack();
|
||||||
|
res.J = Integer.MAX_VALUE + ""; //access_key
|
||||||
|
res.D = "https://vtosters.app"; //url
|
||||||
|
res.d = Integer.parseInt(splits[1]); //id
|
||||||
|
res.e = Integer.parseInt(splits[0]); //owner_id
|
||||||
|
res.f = URLDecoder.decode(cur.getString(titleIndex), "UTF-8"); //title
|
||||||
|
res.g = URLDecoder.decode(cur.getString(subtitleIndex), "UTF-8"); //subtitle
|
||||||
|
res.C = URLDecoder.decode(cur.getString(artistIndex), "UTF-8"); //artist
|
||||||
|
res.h = cur.getInt(durationIndex); //duration
|
||||||
|
res.K = cur.getInt(explicitIndex) > 0; //is_explicit
|
||||||
|
|
||||||
|
int albumId = Integer.parseInt(cur.getString(albumIdIndex));
|
||||||
|
String albumTitle = albumTitleIndex != -1 ? cur.getString(albumTitleIndex) : null;
|
||||||
|
|
||||||
|
JSONObject node = new JSONObject();
|
||||||
|
|
||||||
|
File f300 = MusicCacheStorageUtils.getTrackThumb(trackId, 300);
|
||||||
|
File f600 = MusicCacheStorageUtils.getTrackThumb(trackId, 600);
|
||||||
|
File f1200 = MusicCacheStorageUtils.getTrackThumb(trackId, 1200);
|
||||||
|
|
||||||
|
if (f300.exists()) node.putOpt("photo_300", Uri.fromFile(f300).toString());
|
||||||
|
if (f600.exists()) node.putOpt("photo_600", Uri.fromFile(f600).toString());
|
||||||
|
if (f1200.exists()) node.putOpt("photo_1200", Uri.fromFile(f1200).toString());
|
||||||
|
|
||||||
|
res.I = new AlbumLink(
|
||||||
|
albumId,
|
||||||
|
res.e,
|
||||||
|
res.J,
|
||||||
|
albumTitle,
|
||||||
|
node.length() > 0
|
||||||
|
? new Thumb(node.put("width", 600)
|
||||||
|
.put("height", 600))
|
||||||
|
: null
|
||||||
|
); //album
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL(Constants.CREATE_QUERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
if (oldVersion < newVersion) {
|
||||||
|
List<MusicTrack> tracks = getTracksWithCursor(db.query(Constants.TABLE_NAME, null, null, null, null, null, null));
|
||||||
|
|
||||||
|
db.execSQL(Constants.DROP_QUERY);
|
||||||
|
|
||||||
|
onCreate(db);
|
||||||
|
|
||||||
|
for (MusicTrack track : tracks) {
|
||||||
|
ContentValues vals = new ContentValues();
|
||||||
|
vals.put(Constants.COLUMN_TRACK_ID, track.y1());
|
||||||
|
vals.put(Constants.COLUMN_ALBUM_ID, track.I != null ? track.I.getId() + "" : "-1");
|
||||||
|
vals.put(Constants.COLUMN_TITLE, track.f);
|
||||||
|
vals.put(Constants.COLUMN_SUBTITLE, track.g);
|
||||||
|
vals.put(Constants.COLUMN_ARTIST, track.C);
|
||||||
|
vals.put(Constants.COLUMN_ALBUM_TITLE, track.I != null ? track.I.getTitle() : "");
|
||||||
|
vals.put(Constants.COLUMN_EXPLICIT, Boolean.compare(track.K, true));
|
||||||
|
vals.put(Constants.COLUMN_DURATION, track.h);
|
||||||
|
vals.put(Constants.COLUMN_HAS_ARTWORK, track.I != null && track.I.u1() != null);
|
||||||
|
db.insert(Constants.TABLE_NAME, null, vals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTrack(String trackId, String albumId, String title, String subtitle, String artist, String albumTitle, boolean explicit, int duration, boolean hasArtwork) {
|
||||||
|
ContentValues vals = new ContentValues();
|
||||||
|
vals.put(Constants.COLUMN_TRACK_ID, trackId);
|
||||||
|
vals.put(Constants.COLUMN_ALBUM_ID, albumId);
|
||||||
|
vals.put(Constants.COLUMN_TITLE, title);
|
||||||
|
vals.put(Constants.COLUMN_SUBTITLE, subtitle);
|
||||||
|
vals.put(Constants.COLUMN_ARTIST, artist);
|
||||||
|
vals.put(Constants.COLUMN_ALBUM_TITLE, albumTitle);
|
||||||
|
vals.put(Constants.COLUMN_EXPLICIT, Boolean.compare(explicit, true));
|
||||||
|
vals.put(Constants.COLUMN_DURATION, duration);
|
||||||
|
vals.put(Constants.COLUMN_HAS_ARTWORK, Boolean.compare(hasArtwork, true));
|
||||||
|
|
||||||
|
long row = getWritableDatabase().insert(Constants.TABLE_NAME, null, vals);
|
||||||
|
|
||||||
|
if (AndroidUtils.isDebuggable()) Log.d("MusicCacheDb", "addTrack(): " + row);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteTrack(String trackId) {
|
||||||
|
getWritableDatabase().delete(Constants.TABLE_NAME, Constants.COLUMN_TRACK_ID + " = ?", new String[]{trackId});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCachedTrack(String trackId) {
|
||||||
|
try (var cur = getReadableDatabase().query(
|
||||||
|
Constants.TABLE_NAME,
|
||||||
|
new String[]{Constants.COLUMN_TRACK_ID},
|
||||||
|
Constants.COLUMN_TRACK_ID + " = ?",
|
||||||
|
new String[]{trackId},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"0,1")) {
|
||||||
|
return cur.moveToFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTracksCount() {
|
||||||
|
return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MusicTrack> getAllTracks() {
|
||||||
|
return getTracksWithCursor(getReadableDatabase().query(Constants.TABLE_NAME, null, null, null, null, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MusicTrack> getAlbum(String albumId) {
|
||||||
|
return getAlbumTracks(albumId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MusicTrack> getFirstAlbumTrack(String albumId) {
|
||||||
|
return getAlbumTracks(albumId, "0,1");
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MusicTrack> getAlbumTracks(String albumId, @Nullable String limit) {
|
||||||
|
return getTracksWithCursor(getReadableDatabase().query(
|
||||||
|
Constants.TABLE_NAME,
|
||||||
|
null,
|
||||||
|
Constants.COLUMN_ALBUM_ID + " = ?",
|
||||||
|
new String[]{albumId},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MusicTrack> getPlaylist() {
|
||||||
|
return getTracksWithCursor(getWritableDatabase().query(
|
||||||
|
Constants.TABLE_NAME,
|
||||||
|
null,
|
||||||
|
Constants.COLUMN_ALBUM_ID + " > -1",
|
||||||
|
null,
|
||||||
|
Constants.COLUMN_ALBUM_ID,
|
||||||
|
"count(*) > 1",
|
||||||
|
Constants.COLUMN_ID + " desc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MusicTrack> getTracksWithCursor(Cursor cur) {
|
||||||
|
List<MusicTrack> res = new ArrayList<>();
|
||||||
|
if (cur != null && cur.moveToFirst()) {
|
||||||
|
try (cur) {
|
||||||
|
do {
|
||||||
|
MusicTrack track = fromCursor(cur);
|
||||||
|
if (track != null) res.add(track);
|
||||||
|
} while (cur.moveToNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface Constants {
|
||||||
|
int DV_VERSION = 0x2;
|
||||||
|
String DB_NAME = "vt_lite_cache.db";
|
||||||
|
String TABLE_NAME = "tracks";
|
||||||
|
//columns
|
||||||
|
String COLUMN_ID = "id";
|
||||||
|
String COLUMN_TRACK_ID = "trackId";
|
||||||
|
String COLUMN_ALBUM_ID = "albumId";
|
||||||
|
String COLUMN_TITLE = "title";
|
||||||
|
String COLUMN_SUBTITLE = "subtitle";
|
||||||
|
String COLUMN_ARTIST = "artist";
|
||||||
|
String COLUMN_ALBUM_TITLE = "albumTitle";
|
||||||
|
String COLUMN_EXPLICIT = "explicit";
|
||||||
|
String COLUMN_DURATION = "duration";
|
||||||
|
String COLUMN_HAS_ARTWORK = "hasArtwork";
|
||||||
|
//queries
|
||||||
|
String CREATE_QUERY = "create table " + TABLE_NAME + "("
|
||||||
|
+ COLUMN_ID + " INTEGER PRIMARY KEY autoincrement,"
|
||||||
|
+ COLUMN_TRACK_ID + " text not null,"
|
||||||
|
+ COLUMN_ALBUM_ID + " text not null,"
|
||||||
|
+ COLUMN_TITLE + " text not null,"
|
||||||
|
+ COLUMN_SUBTITLE + " text not null,"
|
||||||
|
+ COLUMN_ARTIST + " text not null,"
|
||||||
|
+ COLUMN_ALBUM_TITLE + " text not null,"
|
||||||
|
+ COLUMN_EXPLICIT + " int not null,"
|
||||||
|
+ COLUMN_DURATION + " int not null,"
|
||||||
|
+ COLUMN_HAS_ARTWORK + " int not null"
|
||||||
|
+ ")";
|
||||||
|
String DROP_QUERY = "drop table if exists " + TABLE_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
package ru.vtosters.lite.music.cache.helpers;
|
package ru.vtosters.lite.music.cache.helpers;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import com.vk.dto.music.Playlist;
|
import com.vk.dto.music.Playlist;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -44,8 +45,8 @@ public class PlaylistHelper {
|
|||||||
if (!photo.isEmpty())
|
if (!photo.isEmpty())
|
||||||
jPhoto.put("height", 600)
|
jPhoto.put("height", 600)
|
||||||
.put("width", 600)
|
.put("width", 600)
|
||||||
.putOpt("photo_300", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 300)))
|
.putOpt("photo_300", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 300)).toString())
|
||||||
.putOpt("photo_600", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 600)));
|
.putOpt("photo_600", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 600)).toString());
|
||||||
var obj = new JSONObject()
|
var obj = new JSONObject()
|
||||||
.put("id", -2)
|
.put("id", -2)
|
||||||
.put("owner_id", albumId)
|
.put("owner_id", albumId)
|
||||||
@@ -68,7 +69,7 @@ public class PlaylistHelper {
|
|||||||
|
|
||||||
public static List<Playlist> getAlbumPlaylists() {
|
public static List<Playlist> getAlbumPlaylists() {
|
||||||
List<Playlist> list = new ArrayList<>();
|
List<Playlist> list = new ArrayList<>();
|
||||||
for (var track : CacheDatabaseDelegate.getTracksAsPlaylist()) {
|
for (var track : MusicCacheImpl.getPlaylist()) {
|
||||||
var albumLink = track.I;
|
var albumLink = track.I;
|
||||||
var thumb = albumLink.u1();
|
var thumb = albumLink.u1();
|
||||||
list.add(createAlbum(
|
list.add(createAlbum(
|
||||||
|
|||||||
+20
-7
@@ -6,12 +6,14 @@ import com.vk.dto.music.MusicTrack;
|
|||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TracklistHelper {
|
public class TracklistHelper {
|
||||||
@@ -20,22 +22,33 @@ public class TracklistHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks() {
|
public static List<MusicTrack> getTracks() {
|
||||||
return CacheDatabaseDelegate.getTracks();
|
List<MusicTrack> tracks = MusicCacheImpl.getAllTracks();
|
||||||
|
|
||||||
|
boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false);
|
||||||
|
|
||||||
|
if (!doNotinvertOrder) {
|
||||||
|
Collections.reverse(tracks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks(String id) {
|
public static List<MusicTrack> getTracks(String id) {
|
||||||
return CacheDatabaseDelegate.getTracksByAlbum(id);
|
return MusicCacheImpl.getAlbumById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksOne(String id) {
|
public static List<MusicTrack> getTracksOne(String id) {
|
||||||
return CacheDatabaseDelegate.getTracksByAlbumOne(id);
|
return MusicCacheImpl.getFirstAlbumTrack(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
|
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
|
||||||
var arr = new JSONArray();
|
var arr = new JSONArray();
|
||||||
|
|
||||||
for (MusicTrack track : tracks) {
|
for (MusicTrack track : tracks) {
|
||||||
arr.put(track.y1());
|
arr.put(track.y1());
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +56,7 @@ public class TracklistHelper {
|
|||||||
var arr = new JSONArray();
|
var arr = new JSONArray();
|
||||||
for (MusicTrack track : tracks) {
|
for (MusicTrack track : tracks) {
|
||||||
var json = track.J();
|
var json = track.J();
|
||||||
var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track));
|
var folder = MusicCacheStorageUtils.getThumbDirById(LibVKXClient.asId(track));
|
||||||
try {
|
try {
|
||||||
addCachedThumbnails(json, folder);
|
addCachedThumbnails(json, folder);
|
||||||
} catch (JSONException | MalformedURLException e) {
|
} catch (JSONException | MalformedURLException e) {
|
||||||
@@ -58,7 +71,7 @@ public class TracklistHelper {
|
|||||||
List<MusicTrack> tracks = new ArrayList<>();
|
List<MusicTrack> tracks = new ArrayList<>();
|
||||||
for (MusicTrack track : list) {
|
for (MusicTrack track : list) {
|
||||||
var json = track.J();
|
var json = track.J();
|
||||||
var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track));
|
var folder = MusicCacheStorageUtils.getThumbDirById(LibVKXClient.asId(track));
|
||||||
try {
|
try {
|
||||||
addCachedThumbnails(json, folder);
|
addCachedThumbnails(json, folder);
|
||||||
} catch (JSONException | MalformedURLException e) {
|
} catch (JSONException | MalformedURLException e) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import androidx.core.app.NotificationCompat;
|
|||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
||||||
import ru.vtosters.lite.music.Callback;
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
public class MusicCallbackBuilder {
|
public class MusicCallbackBuilder {
|
||||||
|
|||||||
@@ -1,23 +1,11 @@
|
|||||||
package ru.vtosters.lite.music.converter.ts;
|
package ru.vtosters.lite.music.converter.ts;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import com.arthenica.ffmpegkit.FFmpegKit;
|
import com.arthenica.ffmpegkit.FFmpegKit;
|
||||||
import com.arthenica.ffmpegkit.ReturnCode;
|
|
||||||
import com.vk.core.util.ToastUtils;
|
|
||||||
import com.vk.dto.music.Artist;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vtosters.lite.R;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class FFMpeg {
|
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 in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger}
|
||||||
@@ -25,79 +13,47 @@ public class FFMpeg {
|
|||||||
* @param track Track to be converted
|
* @param track Track to be converted
|
||||||
* @return True if success, else false
|
* @return True if success, else false
|
||||||
*/
|
*/
|
||||||
public static boolean convert(File in, String out, MusicTrack track) {
|
public static void convert(File in, String out, MusicTrack track) throws Throwable {
|
||||||
|
|
||||||
// convert .ts file to .mp3 file via ffmpeg
|
// convert .ts file to .mp3 file via ffmpeg
|
||||||
var sb = new StringBuilder();
|
var cmd = " -y -i \"" + in.getAbsolutePath() + '"'
|
||||||
sb.append("-y -i \"").append(in.getAbsolutePath()).append("\"");
|
+ " -map 0 -dn"
|
||||||
sb.append(" -map 0 -dn ");
|
+ " -loglevel error"
|
||||||
//sb.append(" -loglevel error");
|
+ " -hide_banner"
|
||||||
//sb.append(" -hide_banner");
|
+ " -write_id3v2 1"
|
||||||
sb.append(" -write_id3v2 1");
|
// +" -metadata title=\""+IOUtils.getValidFileName(M3UDownloader.getTitle(track))+'"'
|
||||||
|
// +" -metadata artist=\""+normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", ")))+'"'
|
||||||
String title = null;
|
// +" -metadata album=\""+normalizeMetadata(track.I.getTitle())+'"'
|
||||||
|
+ " -c copy \"" + out + '"';
|
||||||
if (track.f != null) {
|
var ses = FFmpegKit.execute(cmd);
|
||||||
title = track.f;
|
if (ses.getReturnCode().isValueCancel()) throw new RuntimeException(ses.getLogsAsString());
|
||||||
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() {
|
private static String normalizeMetadata(String in) {
|
||||||
try {
|
return in.replace('"', '‟');
|
||||||
String[] files = AndroidUtils.getGlobalContext().getAssets().list("ffmpeg");
|
|
||||||
for (String file : files) {
|
|
||||||
if (file.endsWith(".so")) {
|
|
||||||
InputStream in = AndroidUtils.getGlobalContext().getAssets().open("ffmpeg" + File.separator + file);
|
|
||||||
FileOutputStream out = new FileOutputStream(AndroidUtils.getGlobalContext().getFilesDir() + File.separator + file);
|
|
||||||
byte[] buffer = new byte[BUFFER_SIZE];
|
|
||||||
int read;
|
|
||||||
while ((read = in.read(buffer)) != -1) {
|
|
||||||
out.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
in.close();
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkFFMpegLibs() {
|
// private static void copyAssets()
|
||||||
var lib = new File(AndroidUtils.getGlobalContext().getFilesDir(), "libffmpegkit_abidetect.so");
|
// throws IOException
|
||||||
if (!lib.exists()) {
|
// {
|
||||||
ToastUtils.a(R.string.ffmpegkit_copy);
|
// try
|
||||||
copyAssets();
|
// {
|
||||||
}
|
// for (var filename:AndroidUtils.getGlobalContext().getAssets().list("ffmpeg"))
|
||||||
}
|
// if(filename.endsWith(".so"))
|
||||||
|
// IOUtils.writeToFile(
|
||||||
|
// new File(AndroidUtils.getGlobalContext().getFilesDir(),filename),
|
||||||
|
// IOUtils.readFully(AndroidUtils.getGlobalContext().getAssets().open("ffmpeg"+File.separator+filename)));
|
||||||
|
// }
|
||||||
|
// catch(IOException e)
|
||||||
|
// { throw e; }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private static void checkFFMpegLibs()
|
||||||
|
// {
|
||||||
|
// var lib=new File(AndroidUtils.getGlobalContext().getFilesDir(),"libffmpegkit_abidetect.so");
|
||||||
|
// if(!lib.exists())
|
||||||
|
// {
|
||||||
|
// ToastUtils.a(R.string.ffmpegkit_copy);
|
||||||
|
// copyAssets();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,66 +9,62 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
public class TSMerger {
|
public class TSMerger {
|
||||||
/**
|
/**
|
||||||
* Merge multiple .ts files into one .ts file
|
* Merge multiple .ts files into one .ts file
|
||||||
*
|
*
|
||||||
* @param in Directory with .ts files
|
* @param in Directory with .ts files
|
||||||
* @return True if success, else false
|
|
||||||
*/
|
*/
|
||||||
public static boolean merge(File in, File out) {
|
public static void merge(File in, File out) {
|
||||||
var tsesList = in.listFiles();
|
File[] tsesList = in.listFiles();
|
||||||
|
|
||||||
if (tsesList == null) {
|
if (tsesList == null) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mergedByteArrayStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream mergedByteArrayStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
sortTsesList(tsesList);
|
sortTsesList(tsesList);
|
||||||
|
|
||||||
for (var file : tsesList) {
|
for (File file : tsesList) {
|
||||||
if (file.getName().endsWith(".ts")) {
|
if (file.getName().endsWith(".ts")) {
|
||||||
try {
|
try {
|
||||||
mergedByteArrayStream.write(IOUtils.readFully(file));
|
mergedByteArrayStream.write(IOUtils.readFully(file));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
return;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeOutputFile(out, mergedByteArrayStream);
|
writeOutputFile(out, mergedByteArrayStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sortTsesList(File[] tsesList) {
|
private static void sortTsesList(File[] tsesList) {
|
||||||
Arrays.sort(tsesList, (ts1, ts2) -> {
|
Arrays.sort(tsesList, Comparator.comparingInt(TSMerger::getFileIndex));
|
||||||
String ts1Name = ts1.getName();
|
|
||||||
String ts2Name = ts2.getName();
|
|
||||||
int i1 = Integer.parseInt(ts1Name.substring(0, ts1Name.indexOf('.', 1)));
|
|
||||||
int i2 = Integer.parseInt(ts2Name.substring(0, ts2Name.indexOf('.', 1)));
|
|
||||||
return Integer.compare(i1, i2);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) {
|
private static int getFileIndex(File file) {
|
||||||
|
String fileName = file.getName();
|
||||||
|
int dotIndex = fileName.indexOf('.', 1);
|
||||||
|
return Integer.parseInt(fileName.substring(0, dotIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) {
|
||||||
|
byte[] bytes = mergedByteArrayStream.toByteArray();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
try {
|
try {
|
||||||
Files.write(out.toPath(), mergedByteArrayStream.toByteArray());
|
Files.write(out.toPath(), bytes);
|
||||||
} catch (IOException e) {
|
} catch (IOException ignored) {
|
||||||
e.printStackTrace();
|
// ignored
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try (FileOutputStream fos = new FileOutputStream(out)) {
|
try (FileOutputStream fos = new FileOutputStream(out)) {
|
||||||
fos.write(mergedByteArrayStream.toByteArray());
|
fos.write(bytes);
|
||||||
} catch (IOException e) {
|
} catch (IOException ignored) {
|
||||||
e.printStackTrace();
|
// ignored
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package ru.vtosters.lite.music.downloader;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import com.google.android.exoplayer2.source.hls.playlist.e;
|
||||||
|
import com.google.android.exoplayer2.source.hls.playlist.f;
|
||||||
|
import com.google.android.exoplayer2.source.hls.playlist.f.a;
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
import java8.util.concurrent.CompletableFuture;
|
||||||
|
import java8.util.concurrent.CompletionException;
|
||||||
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
|
import ru.vtosters.lite.music.converter.ts.FFMpeg;
|
||||||
|
import ru.vtosters.lite.music.converter.ts.TSMerger;
|
||||||
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
|
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class M3UDownloader
|
||||||
|
implements ITrackDownloader {
|
||||||
|
public static M3UDownloader getInstance() {
|
||||||
|
return Holder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse(
|
||||||
|
File outDir,
|
||||||
|
Callback callback,
|
||||||
|
MusicTrack track,
|
||||||
|
boolean cache)
|
||||||
|
throws IOException {
|
||||||
|
var parser = new com.google.android.exoplayer2.source.hls.playlist.h(e.a(track.D));
|
||||||
|
var baseUri = track.D.substring(0, track.D.lastIndexOf("/") + 1);
|
||||||
|
var segments = ((f) parser.a(Uri.parse(baseUri), IOUtils.openStream(track.D))).o;
|
||||||
|
var progress = new AtomicInteger();
|
||||||
|
callback.onProgress(5);
|
||||||
|
var resultTs = getResultTsFile(track);
|
||||||
|
var resultMp3 = getResultMp3File(outDir, cache, track);
|
||||||
|
try {
|
||||||
|
CompletableFuture.allOf(createFutures(baseUri, segments, progress, callback, track))
|
||||||
|
.thenRun(() ->
|
||||||
|
{
|
||||||
|
if (cache)
|
||||||
|
try {
|
||||||
|
ThumbnailDownloader.downloadThumbnails(track);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to download thumbs", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.thenRun(() ->
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
TSMerger.merge(getTsesDir(track), resultTs);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException("Failed to merge ts files", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.thenRun(() ->
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException("FFmpeg error", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.thenRun(() -> callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / segments.size())))
|
||||||
|
.thenRun(() -> MusicCacheImpl.addTrack(track))
|
||||||
|
.whenComplete((unused, tr) -> {
|
||||||
|
if (tr != null) {
|
||||||
|
IOUtils.deleteRecursive(MusicCacheStorageUtils.getTrackDirById(track.y1()));
|
||||||
|
callback.onFailure();
|
||||||
|
} else callback.onSuccess();
|
||||||
|
IOUtils.deleteRecursive(getTsesDir(track));
|
||||||
|
})
|
||||||
|
.join();
|
||||||
|
} catch (CompletionException e) {
|
||||||
|
Log.e("M3UDownloader", "Failed to download track", e.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
static CompletableFuture[] createFutures(
|
||||||
|
String baseUri,
|
||||||
|
List<a> segments,
|
||||||
|
AtomicInteger progress,
|
||||||
|
Callback callback,
|
||||||
|
MusicTrack track) {
|
||||||
|
var futures = new CompletableFuture[segments.size()];
|
||||||
|
for (var i = 0; i < segments.size(); ++i) {
|
||||||
|
var segment = segments.get(i);
|
||||||
|
var filename = i + ".ts";
|
||||||
|
futures[i] = CompletableFuture.runAsync(() -> processSegment(baseUri, segment, filename, progress, segments.size(), callback, track));
|
||||||
|
}
|
||||||
|
return futures;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processSegment(
|
||||||
|
String baseUri,
|
||||||
|
a segment,
|
||||||
|
String filename,
|
||||||
|
AtomicInteger progress,
|
||||||
|
int size,
|
||||||
|
Callback callback,
|
||||||
|
MusicTrack track) {
|
||||||
|
try {
|
||||||
|
var buff = IOUtils.readFully(IOUtils.openStream(baseUri + segment.a/*url*/));
|
||||||
|
if (!TextUtils.isEmpty(segment.g)) {
|
||||||
|
var cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
|
||||||
|
var cipherKey = new SecretKeySpec(IOUtils.readFully(IOUtils.openStream(segment.g)), "AES");
|
||||||
|
var cipherBytes = segment.h.getBytes();
|
||||||
|
//if IV doesn't pass, it must be created manually and filled with zeros
|
||||||
|
if (cipherBytes.length != 16) Arrays.fill((cipherBytes = new byte[16]), (byte) 0x0);
|
||||||
|
var cipherIv = new IvParameterSpec(cipherBytes);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIv);
|
||||||
|
buff = cipher.doFinal(buff);
|
||||||
|
}
|
||||||
|
IOUtils.writeToFile(new File(getTsesDir(track), filename), buff);
|
||||||
|
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / size));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTitle(MusicTrack track) {
|
||||||
|
return track.f + (!TextUtils.isEmpty(track.g) ? +'(' + track.g + ')' : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static File getTsesDir(MusicTrack track) {
|
||||||
|
var filesDir = AndroidUtils.getGlobalContext().getFilesDir();
|
||||||
|
var tsesDir = new File(filesDir, String.valueOf(track.d));
|
||||||
|
tsesDir.mkdirs();
|
||||||
|
return tsesDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
static File getResultTsFile(MusicTrack track) {
|
||||||
|
return new File(getTsesDir(track), "result.ts");
|
||||||
|
}
|
||||||
|
|
||||||
|
static File getResultMp3File(File outDir, boolean cache, MusicTrack track) {
|
||||||
|
return new File(outDir, IOUtils.getValidFileName((cache ? "track" : track.C + " - " + getTitle(track)) + ".mp3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadTrack(
|
||||||
|
MusicTrack track,
|
||||||
|
File outDir,
|
||||||
|
Callback callback,
|
||||||
|
boolean cache) {
|
||||||
|
if (track.D.isEmpty()) {
|
||||||
|
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
|
||||||
|
callback.onFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parse(outDir, callback, track, cache);
|
||||||
|
} catch (IOException e) {
|
||||||
|
callback.onFailure();
|
||||||
|
Log.e("M3UDownloader", e + "");
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialization-on-demand
|
||||||
|
private static class Holder {
|
||||||
|
private static final M3UDownloader INSTANCE = new M3UDownloader();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -70,7 +70,7 @@
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// @Override
|
// @Override
|
||||||
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.Callback callback) {
|
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.interfaces.Callback callback) {
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
@@ -2,7 +2,7 @@ package ru.vtosters.lite.music.downloader;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.music.Callback;
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -10,16 +10,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
|
|
||||||
public class PlaylistDownloader {
|
public class PlaylistDownloader {
|
||||||
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
|
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
|
||||||
File outDir = new File(path);
|
var outDir = new File(path);
|
||||||
if (!outDir.exists())
|
if (!outDir.exists())
|
||||||
if (outDir.mkdirs())
|
if (outDir.mkdirs()) Log.v("PlaylistDownloader", "Directory created");
|
||||||
Log.v("PlaylistDownloader", "Directory created");
|
else Log.e("PlaylistDownloader", "Directory creation failed");
|
||||||
else
|
var downloadedTracks = new AtomicInteger();
|
||||||
Log.e("PlaylistDownloader", "Directory creation failed");
|
for (var musicTrack : playlist)
|
||||||
|
|
||||||
var downloadedTracks = new AtomicInteger(0);
|
|
||||||
|
|
||||||
for (MusicTrack musicTrack : playlist) {
|
|
||||||
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
|
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(int progress) {
|
public void onProgress(int progress) {
|
||||||
@@ -27,8 +23,7 @@ public class PlaylistDownloader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
var currentProgress = downloadedTracks.incrementAndGet();
|
callback.onProgress(downloadedTracks.incrementAndGet());
|
||||||
callback.onProgress(currentProgress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -40,13 +35,11 @@ public class PlaylistDownloader {
|
|||||||
public void onSizeReceived(long size, long header) {
|
public void onSizeReceived(long size, long header) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback) {
|
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback) {
|
||||||
var downloadedTracks = new AtomicInteger(0);
|
var downloadedTracks = new AtomicInteger();
|
||||||
|
for (var musicTrack : playlist)
|
||||||
for (MusicTrack musicTrack : playlist) {
|
|
||||||
TrackDownloader.cacheTrack(musicTrack, new Callback() {
|
TrackDownloader.cacheTrack(musicTrack, new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(int progress) {
|
public void onProgress(int progress) {
|
||||||
@@ -54,8 +47,7 @@ public class PlaylistDownloader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
var currentProgress = downloadedTracks.incrementAndGet();
|
callback.onProgress(downloadedTracks.incrementAndGet());
|
||||||
callback.onProgress(currentProgress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -67,6 +59,5 @@ public class PlaylistDownloader {
|
|||||||
public void onSizeReceived(long size, long header) {
|
public void onSizeReceived(long size, long header) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,45 +2,42 @@ package ru.vtosters.lite.music.downloader;
|
|||||||
|
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
public class ThumbnailDownloader {
|
public class ThumbnailDownloader {
|
||||||
|
|
||||||
public static void downloadThumbnails(MusicTrack track)
|
public static void downloadThumbnails(MusicTrack track) throws IOException {
|
||||||
throws IOException {
|
JSONObject json = track.J();
|
||||||
var obj = track.J();
|
JSONObject album = json.optJSONObject("album");
|
||||||
var trackId = LibVKXClient.asId(track);
|
if (album == null) return;
|
||||||
|
JSONObject thumb = album.optJSONObject("thumb");
|
||||||
if (!obj.has("album")) return;
|
if (thumb == null) return;
|
||||||
var album = obj.optJSONObject("album");
|
JSONArray sizes = thumb.optJSONArray("sizes");
|
||||||
|
String trackId = LibVKXClient.asId(track);
|
||||||
if (!album.has("thumb")) return;
|
if (sizes == null) {
|
||||||
var thumb = album.optJSONObject("thumb");
|
JSONArray names = thumb.names();
|
||||||
|
if (names == null) return;
|
||||||
if (thumb.has("sizes")) {
|
for (int i = 0; i < names.length(); ++i) {
|
||||||
var sizes = thumb.optJSONArray("sizes");
|
String name = names.optString(i);
|
||||||
for (int i = 0; i < sizes.length(); i++) {
|
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
|
||||||
var size = sizes.optJSONObject(i);
|
|
||||||
var width = size.optInt("width");
|
|
||||||
var src = size.optString("src");
|
|
||||||
downloadThumbnail(src, width, trackId);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var names = thumb.names();
|
for (int i = 0; i < sizes.length(); ++i) {
|
||||||
for (int i = 0; i < names.length(); i++) {
|
JSONObject size = sizes.optJSONObject(i);
|
||||||
var name = names.optString(i);
|
int width = size.optInt("width");
|
||||||
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
|
String src = size.optString("src");
|
||||||
|
downloadThumbnail(src, width, trackId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void downloadThumbnail(String url, int res, String trackId)
|
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
|
||||||
throws IOException {
|
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
|
||||||
var data = IOUtils.readFully(new URL(url).openStream());
|
|
||||||
IOUtils.writeToFile(FileCacheImplementation.getTrackThumbnail(trackId, res), data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import android.util.Log;
|
|||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.concurrent.VTExecutors;
|
import ru.vtosters.lite.concurrent.VTExecutors;
|
||||||
import ru.vtosters.lite.music.Callback;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.M3UDownloader;
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
|
||||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ public class TrackDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void cacheTrack(MusicTrack track, Callback callback) {
|
public static void cacheTrack(MusicTrack track, Callback callback) {
|
||||||
if (CacheDatabaseDelegate.isCached(track.y1())) {
|
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
|
||||||
callback.onSuccess();
|
callback.onSuccess();
|
||||||
return;
|
return;
|
||||||
} else if (track.D.isEmpty()) {
|
} else if (track.D.isEmpty()) {
|
||||||
@@ -33,7 +32,7 @@ public class TrackDownloader {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = FileCacheImplementation.getTrackFolder(LibVKXClient.asId(track)).getAbsolutePath();
|
var path = MusicCacheStorageUtils.getTrackDirById(LibVKXClient.asId(track)).getAbsolutePath();
|
||||||
download(track, path, callback, M3UDownloader.getInstance(), true);
|
download(track, path, callback, M3UDownloader.getInstance(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music;
|
package ru.vtosters.lite.music.interfaces;
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
void onProgress(int progress);
|
void onProgress(int progress);
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package ru.vtosters.lite.music.interfaces;
|
package ru.vtosters.lite.music.interfaces;
|
||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.music.Callback;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,19 @@ package ru.vtosters.lite.net;
|
|||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
public final class NetCall {
|
public final class NetCall {
|
||||||
|
// Constants for the request methods, content types, and encoding
|
||||||
|
private static final String GET = "GET";
|
||||||
|
private static final String POST = "POST";
|
||||||
|
private static final String FORM = "application/x-www-form-urlencoded";
|
||||||
|
private static final String UTF_8 = "UTF-8";
|
||||||
|
|
||||||
private final NetRequest req;
|
private final NetRequest req;
|
||||||
private final NetClient cl;
|
private final NetClient cl;
|
||||||
|
|
||||||
@@ -22,13 +25,17 @@ public final class NetCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String constructArgs(Map<String, String> args) throws UnsupportedEncodingException {
|
private static String constructArgs(Map<String, String> args) throws UnsupportedEncodingException {
|
||||||
|
// Use a TreeMap to sort the arguments by key
|
||||||
args = new TreeMap<>(args);
|
args = new TreeMap<>(args);
|
||||||
String str = "";
|
// Use a StringBuilder to append the arguments
|
||||||
for (Map.Entry<String, String> en : args.entrySet()) {
|
StringBuilder sb = new StringBuilder();
|
||||||
if (!str.isEmpty()) str += "&";
|
for (Map.Entry<String, String> entry : args.entrySet()) {
|
||||||
str += URLEncoder.encode(en.getKey(), "UTF-8") + "=" + URLEncoder.encode(en.getValue(), "UTF-8");
|
if (sb.length() > 0) sb.append("&");
|
||||||
|
sb.append(URLEncoder.encode(entry.getKey(), UTF_8))
|
||||||
|
.append("=")
|
||||||
|
.append(URLEncoder.encode(entry.getValue(), UTF_8));
|
||||||
}
|
}
|
||||||
return str;
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetRequest request() {
|
public NetRequest request() {
|
||||||
@@ -38,38 +45,52 @@ public final class NetCall {
|
|||||||
public NetResponse execute() throws IOException {
|
public NetResponse execute() throws IOException {
|
||||||
Proxy proxy = cl.getProxy();
|
Proxy proxy = cl.getProxy();
|
||||||
PasswordAuthentication auth = cl.getAuthenticator();
|
PasswordAuthentication auth = cl.getAuthenticator();
|
||||||
HttpURLConnection con = (HttpURLConnection) new URL(req.url()).openConnection(proxy != null ? proxy : Proxy.NO_PROXY);
|
HttpURLConnection connection = (HttpURLConnection) new URL(req.url()).openConnection(proxy != null ? proxy : Proxy.NO_PROXY);
|
||||||
if (proxy != null && auth != null) {
|
if (proxy != null && auth != null) {
|
||||||
|
// Set the proxy authentication header
|
||||||
String authStr = "Basic " + Base64.encodeToString((auth.getUserName() + new String(auth.getPassword())).getBytes(), Base64.NO_WRAP);
|
String authStr = "Basic " + Base64.encodeToString((auth.getUserName() + new String(auth.getPassword())).getBytes(), Base64.NO_WRAP);
|
||||||
con.setRequestProperty("Proxy-Connection", "Keep-Alive");
|
connection.setRequestProperty("Proxy-Connection", "Keep-Alive");
|
||||||
con.setRequestProperty("Proxy-Authorization", authStr);
|
connection.setRequestProperty("Proxy-Authorization", authStr);
|
||||||
}
|
}
|
||||||
con.setConnectTimeout((int) cl.getTimeout());
|
connection.setConnectTimeout((int) cl.getTimeout());
|
||||||
con.setRequestMethod(req.getRequestMethod());
|
connection.setRequestMethod(req.getRequestMethod());
|
||||||
if (req.getRequestMethod().equals("POST")) {
|
if (req.getRequestMethod().equals(POST)) {
|
||||||
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
// Set the content type for POST requests
|
||||||
|
connection.setRequestProperty("Content-Type", FORM);
|
||||||
}
|
}
|
||||||
Map<String, String> params = req.getRequestParams();
|
Map<String, String> params = req.getRequestParams();
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
String strArgs = constructArgs(params);
|
// Write the request parameters to the output stream
|
||||||
con.setRequestProperty("Content-Length", String.valueOf(strArgs.getBytes(StandardCharsets.UTF_8).length));
|
writeParams(connection, params);
|
||||||
con.setDoOutput(true);
|
}
|
||||||
con.getOutputStream().write(strArgs.getBytes(StandardCharsets.UTF_8));
|
connection.connect();
|
||||||
con.getOutputStream().close();
|
InputStream input;
|
||||||
|
if (connection.getResponseCode() == 200) input = connection.getInputStream();
|
||||||
|
else input = connection.getErrorStream();
|
||||||
|
// Use try-with-resources to close the streams automatically
|
||||||
|
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||||
|
byte[] buffer = new byte[1024 * 1024];
|
||||||
|
int count;
|
||||||
|
while ((count = input.read(buffer)) != -1) output.write(buffer, 0, count);
|
||||||
|
input.close();
|
||||||
|
NetResponse response = new NetResponse(output.toByteArray());
|
||||||
|
response.setCode(connection.getResponseCode());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeParams(HttpURLConnection connection, Map<String, String> params) throws IOException {
|
||||||
|
// Get the encoded arguments string
|
||||||
|
String args = constructArgs(params);
|
||||||
|
// Set the content length header
|
||||||
|
connection.setRequestProperty("Content-Length", String.valueOf(args.getBytes(StandardCharsets.UTF_8).length));
|
||||||
|
// Enable output mode
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
// Write the arguments to the output stream
|
||||||
|
try (OutputStream output = connection.getOutputStream()) {
|
||||||
|
output.write(args.getBytes(StandardCharsets.UTF_8));
|
||||||
|
output.flush();
|
||||||
}
|
}
|
||||||
con.connect();
|
|
||||||
InputStream in;
|
|
||||||
if (con.getResponseCode() == 200) in = con.getInputStream();
|
|
||||||
else in = con.getErrorStream();
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
byte[] buffer = new byte[1024 * 1024];
|
|
||||||
int c;
|
|
||||||
while ((c = in.read(buffer)) != -1) bos.write(buffer, 0, c);
|
|
||||||
in.close();
|
|
||||||
bos.close();
|
|
||||||
NetResponse r = new NetResponse(bos.toByteArray());
|
|
||||||
r.setCode(con.getResponseCode());
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enqueue(NetCallback cb) {
|
public void enqueue(NetCallback cb) {
|
||||||
@@ -82,12 +103,16 @@ public final class NetCall {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
cb.onFailure(NetCall.this, e);
|
cb.onFailure(NetCall.this, e);
|
||||||
}
|
}
|
||||||
|
// try {
|
||||||
|
//// interrupt();
|
||||||
try {
|
try {
|
||||||
interrupt();
|
|
||||||
join();
|
join();
|
||||||
} catch (Exception e) {
|
} catch (InterruptedException e) {
|
||||||
Log.e("NetCall", "Failed to interrupt and join thread", e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// Log.e("NetCall", "Failed to interrupt and join thread", e);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ public final class NetResponse {
|
|||||||
private final byte[] data;
|
private final byte[] data;
|
||||||
private int code;
|
private int code;
|
||||||
|
|
||||||
NetResponse(byte[] data) {
|
public NetResponse(byte[] data) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCode(int code) {
|
public void setCode(int code) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class Request {
|
|||||||
httpURLConnection.setRequestProperty("Content-Type", mIsVTostersRequest ? "Content-Type" : "application/x-www-form-urlencoded");
|
httpURLConnection.setRequestProperty("Content-Type", mIsVTostersRequest ? "Content-Type" : "application/x-www-form-urlencoded");
|
||||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()));
|
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()));
|
||||||
|
|
||||||
StringBuffer stringBuffer = new StringBuffer();
|
StringBuilder stringBuffer = new StringBuilder();
|
||||||
while (true) {
|
while (true) {
|
||||||
String readLine = bufferedReader.readLine();
|
String readLine = bufferedReader.readLine();
|
||||||
if (readLine != null) {
|
if (readLine != null) {
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ public class ImBanner {
|
|||||||
private static final String TAG = "ImBanner";
|
private static final String TAG = "ImBanner";
|
||||||
|
|
||||||
public static JSONObject convertToBanner(JSONObject original) {
|
public static JSONObject convertToBanner(JSONObject original) {
|
||||||
final String PEER = "peer";
|
String PEER = "peer";
|
||||||
final String ID = "id";
|
String ID = "id";
|
||||||
final String PICTURE = "picture";
|
String PICTURE = "picture";
|
||||||
final String TEXT = "text";
|
String TEXT = "text";
|
||||||
final String LINK = "link";
|
String LINK = "link";
|
||||||
final String LINK_TEXT = "link_text";
|
String LINK_TEXT = "link_text";
|
||||||
final String NAME = "name";
|
String NAME = "name";
|
||||||
final String BUTTONS = "buttons";
|
String BUTTONS = "buttons";
|
||||||
final String ICON = "icon";
|
String ICON = "icon";
|
||||||
|
|
||||||
Optional<JSONObject> user = Optional.ofNullable(original.optJSONObject(PEER));
|
Optional<JSONObject> user = Optional.ofNullable(original.optJSONObject(PEER));
|
||||||
int userId = user.map(u -> u.optInt(ID)).orElse(0);
|
int userId = user.map(u -> u.optInt(ID)).orElse(0);
|
||||||
|
|||||||
@@ -26,20 +26,20 @@ public class ProfileButtons {
|
|||||||
newItem = orig.getJSONArray("buttons");
|
newItem = orig.getJSONArray("buttons");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!VTVerifications.haveDonateButton() && id == AccountManagerUtils.getUserId()) {
|
// if (!VTVerifications.haveDonateButton() && id == AccountManagerUtils.getUserId()) {
|
||||||
var map = new HashMap<>();
|
// var map = new HashMap<>();
|
||||||
map.put("20", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/20.png");
|
// map.put("20", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/20.png");
|
||||||
map.put("40", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/40.png");
|
// map.put("40", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/40.png");
|
||||||
map.put("80", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/80.png");
|
// map.put("80", "https://raw.githubusercontent.com/vtosters/lite/main/.github/images/donate_icon/80.png");
|
||||||
|
//
|
||||||
newItem.put(generateJsonObject(
|
// newItem.put(generateJsonObject(
|
||||||
AndroidUtils.getString(com.vtosters.lite.R.string.donate_to_vtl),
|
// AndroidUtils.getString(com.vtosters.lite.R.string.donate_to_vtl),
|
||||||
"https://vk.com/vtosters_official",
|
// "https://vk.com/vtosters_official",
|
||||||
"2D81E0",
|
// "2D81E0",
|
||||||
"open_url",
|
// "open_url",
|
||||||
map
|
// map
|
||||||
));
|
// ));
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!orig.has("type") && AccountManagerUtils.isVKTester()) {
|
if (!orig.has("type") && AccountManagerUtils.isVKTester()) {
|
||||||
var map = new HashMap<>();
|
var map = new HashMap<>();
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
||||||
|
|
||||||
public class UsersList {
|
public class UsersList {
|
||||||
private static final String TAG = "UsersList";
|
private static final String TAG = "UsersList";
|
||||||
private static final List<Integer> descriptionsList = new ArrayList<>();
|
private static final List<Integer> descriptionsList = new ArrayList<>();
|
||||||
@@ -25,7 +27,7 @@ public class UsersList {
|
|||||||
public static void getUsersList() {
|
public static void getUsersList() {
|
||||||
var prefs = AndroidUtils.getGlobalContext().getSharedPreferences("vt_another_data", 0);
|
var prefs = AndroidUtils.getGlobalContext().getSharedPreferences("vt_another_data", 0);
|
||||||
|
|
||||||
if ((!NetworkUtils.isNetworkConnected() || NetworkUtils.isInternetSlow()) && prefs.contains("ids")) {
|
if ((!NetworkUtils.isNetworkConnected() || NetworkUtils.isInternetSlow()) && prefs.contains("ids") || getBoolValue("serverFeaturesDisable", false)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,22 +7,7 @@ import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
|||||||
|
|
||||||
public class TGPref {
|
public class TGPref {
|
||||||
private static final SharedPreferences Prefs = PreferenceManager.getDefaultSharedPreferences(getGlobalContext());
|
private static final SharedPreferences Prefs = PreferenceManager.getDefaultSharedPreferences(getGlobalContext());
|
||||||
|
|
||||||
private static final String CNCT_METHOD = "VTGS:CM";
|
|
||||||
private static final String TG_BOTKEY = "VTGS:BKey";
|
private static final String TG_BOTKEY = "VTGS:BKey";
|
||||||
private static final String TG_PROXYIP = "VTGS:PIP";
|
|
||||||
private static final String TG_PROXYPORT = "VTGS:PPT";
|
|
||||||
private static final String TG_PROXYPASS = "VTGS:PPS";
|
|
||||||
private static final String TG_PROXYUSER = "VTGS:PUS";
|
|
||||||
private static final String TG_PROXYUSEPASS = "VTGS:PUsePassword";
|
|
||||||
|
|
||||||
public static int getTGConnectMethod() {
|
|
||||||
return Integer.parseInt(Prefs.getString(CNCT_METHOD, "0"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setTGConnectMethod(int method) {
|
|
||||||
Prefs.edit().putString(CNCT_METHOD, String.valueOf(method)).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getTGBotKey() {
|
public static String getTGBotKey() {
|
||||||
return Prefs.getString(TG_BOTKEY, null);
|
return Prefs.getString(TG_BOTKEY, null);
|
||||||
@@ -31,40 +16,4 @@ public class TGPref {
|
|||||||
public static void setTGBotKey(String key) {
|
public static void setTGBotKey(String key) {
|
||||||
Prefs.edit().putString(TG_BOTKEY, key).apply();
|
Prefs.edit().putString(TG_BOTKEY, key).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getTGProxyIP() {
|
|
||||||
return Prefs.getString(TG_PROXYIP, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setTGProxyIP(String ip) {
|
|
||||||
Prefs.edit().putString(TG_PROXYIP, ip).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getTGProxyPort() {
|
|
||||||
return Prefs.getInt(TG_PROXYPORT, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setTGProxyPort(int port) {
|
|
||||||
Prefs.edit().putInt(TG_PROXYPORT, port).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getTGProxyPass() {
|
|
||||||
return Prefs.getString(TG_PROXYPASS, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getTGProxyUser() {
|
|
||||||
return Prefs.getString(TG_PROXYUSER, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setTGProxyUserPass(String user, String pass) {
|
|
||||||
Prefs.edit().putString(TG_PROXYUSER, user).putString(TG_PROXYPASS, pass).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTGProxyPassEnabled() {
|
|
||||||
return Prefs.getBoolean(TG_PROXYUSEPASS, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setTGProxyPassEnabled(boolean en) {
|
|
||||||
Prefs.edit().putBoolean(TG_PROXYUSEPASS, en).apply();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ package ru.vtosters.lite.tgs;
|
|||||||
|
|
||||||
import com.aefyr.tsg.g2.TelegramStickersPack;
|
import com.aefyr.tsg.g2.TelegramStickersPack;
|
||||||
import com.vk.dto.stickers.StickerStockItem;
|
import com.vk.dto.stickers.StickerStockItem;
|
||||||
import com.vtosters.lite.R;
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
|
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
@@ -15,14 +13,6 @@ public class TGRoot {
|
|||||||
public static final int X = 976820000;
|
public static final int X = 976820000;
|
||||||
public final static Stack<Integer> pendingStickers = new Stack<>();
|
public final static Stack<Integer> pendingStickers = new Stack<>();
|
||||||
|
|
||||||
public static String getSummary() {
|
|
||||||
return switch (TGPref.getTGConnectMethod()) {
|
|
||||||
case 0 -> AndroidUtils.getString(R.string.ctypedirect);
|
|
||||||
case 2 -> AndroidUtils.getString(R.string.ctypesocks);
|
|
||||||
default -> AndroidUtils.getString(R.string.ctypedisabled);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StickerStockItem toStickerPack(TelegramStickersPack p) throws JSONException {
|
public static StickerStockItem toStickerPack(TelegramStickersPack p) throws JSONException {
|
||||||
JSONArray arr = new JSONArray();
|
JSONArray arr = new JSONArray();
|
||||||
|
|
||||||
|
|||||||
@@ -166,26 +166,25 @@ public class ThemesCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasThemedAttr(Context context, int attrID) {
|
public static boolean hasThemedAttr(Context context, int attrID) {
|
||||||
if (ThemesUtils.isCustomAccentEnabled()) {
|
if (Preferences.getBoolValue("logColors", false)) {
|
||||||
if (Preferences.getBoolValue("logColors", false)) {
|
try {
|
||||||
try {
|
Log.d(TAG, "Requesting color by attr " + context.getResources().getResourceName(attrID));
|
||||||
Log.d(TAG, "Requesting color by attr " + context.getResources().getResourceName(attrID));
|
} catch (Exception ignored) {
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ThemesUtils.isMonetTheme() || ThemesManager.canApplyCustomAccent()) {
|
||||||
if (isCachedAccents()) {
|
if (isCachedAccents()) {
|
||||||
if (ThemesUtils.isDarkTheme()) {
|
boolean isDark = ThemesUtils.isDarkTheme();
|
||||||
return (themedColors.get(attrID) != 0 && ACCENT_THEME_ONLY_DARK.get(attrID, true) && (
|
boolean isMilkshake = ThemesUtils.isMilkshake();
|
||||||
ThemesUtils.isMilkshake() ?
|
SparseBooleanArray themeOnlyMap = isDark ? ACCENT_THEME_ONLY_DARK : ACCENT_THEME_ONLY_LIGHT;
|
||||||
ACCENT_THEME_ONLY_MILK_DARK.get(attrID, true) :
|
SparseBooleanArray milkshakeMap = isDark ? ACCENT_THEME_ONLY_MILK_DARK : ACCENT_THEME_ONLY_MILK_LIGHT;
|
||||||
ACCENT_THEME_ONLY_NOMILK_DARK.get(attrID, true)));
|
SparseBooleanArray noMilkshakeMap = isDark ? ACCENT_THEME_ONLY_NOMILK_DARK : ACCENT_THEME_ONLY_NOMILK_LIGHT;
|
||||||
} else {
|
|
||||||
return (themedColors.get(attrID) != 0 && ACCENT_THEME_ONLY_LIGHT.get(attrID, true) && (
|
return (themedColors.get(attrID) != 0 && themeOnlyMap.get(attrID, true) && (
|
||||||
ThemesUtils.isMilkshake() ?
|
isMilkshake ?
|
||||||
ACCENT_THEME_ONLY_MILK_LIGHT.get(attrID, true) :
|
milkshakeMap.get(attrID, true) :
|
||||||
ACCENT_THEME_ONLY_NOMILK_LIGHT.get(attrID, true)));
|
noMilkshakeMap.get(attrID, true)));
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return themedColors.get(attrID) != 0;
|
return themedColors.get(attrID) != 0;
|
||||||
}
|
}
|
||||||
@@ -194,7 +193,6 @@ public class ThemesCore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void clear() {
|
public static void clear() {
|
||||||
cachedAccents = false;
|
cachedAccents = false;
|
||||||
themedColors.clear();
|
themedColors.clear();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class ThemesHacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int getHackedColor(@NonNull Context context, @ColorRes int color) {
|
public static int getHackedColor(@NonNull Context context, @ColorRes int color) {
|
||||||
if (ThemesUtils.isCustomAccentEnabled()) {
|
if (ThemesUtils.isMonetTheme() || ThemesManager.canApplyCustomAccent()) {
|
||||||
if (Preferences.getBoolValue("logColors", false)) {
|
if (Preferences.getBoolValue("logColors", false)) {
|
||||||
try {
|
try {
|
||||||
Log.d("ThemesCore", "Requesting color by color " + context.getResources().getResourceName(color));
|
Log.d("ThemesCore", "Requesting color by color " + context.getResources().getResourceName(color));
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package ru.vtosters.lite.themes;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.util.Log;
|
|
||||||
import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;
|
import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
@@ -20,7 +19,7 @@ import java.util.zip.ZipOutputStream;
|
|||||||
|
|
||||||
public class ThemesManager {
|
public class ThemesManager {
|
||||||
|
|
||||||
private final static int[] ACCENT_COLORS = {
|
private static final int[] ACCENT_COLORS = {
|
||||||
R.color.vk_sky_300,
|
R.color.vk_sky_300,
|
||||||
R.color.sky_300,
|
R.color.sky_300,
|
||||||
R.color.viewer_retry_button_text_color,
|
R.color.viewer_retry_button_text_color,
|
||||||
@@ -96,7 +95,7 @@ public class ThemesManager {
|
|||||||
R.color.header_blue_opacity40
|
R.color.header_blue_opacity40
|
||||||
};
|
};
|
||||||
// modify it if "doNotCompress" in apktool.yml has been edited
|
// modify it if "doNotCompress" in apktool.yml has been edited
|
||||||
private final static String[] DO_NOT_COMPRESS = {
|
private static final String[] DO_NOT_COMPRESS = {
|
||||||
".png",
|
".png",
|
||||||
".jpg",
|
".jpg",
|
||||||
".mp3",
|
".mp3",
|
||||||
@@ -119,19 +118,18 @@ public class ThemesManager {
|
|||||||
initPaths(app);
|
initPaths(app);
|
||||||
validateModApk();
|
validateModApk();
|
||||||
|
|
||||||
if (!Preferences.isNewBuild() && !ThemesUtils.isMonetTheme() && canApplyCustomAccent() && ThemesUtils.useNewColorEngine()) {
|
if (!Preferences.isNewBuild() && !ThemesUtils.isMonetTheme() && canApplyCustomAccent()) {
|
||||||
ResourcesLoader.init(app);
|
ResourcesLoader.init(app);
|
||||||
ResourcesLoader.load(app, modApk.getAbsolutePath(), false);
|
ResourcesLoader.load(app, modApk.getAbsolutePath(), false);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("ThemesManager", e.getMessage());
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate modded apk before loading
|
// validate modded apk before loading
|
||||||
private static void validateModApk()
|
private static void validateModApk() throws IOException {
|
||||||
throws IOException {
|
var apk = new ZipFile(modApk);
|
||||||
final var apk = new ZipFile(modApk);
|
|
||||||
apk.close();
|
apk.close();
|
||||||
|
|
||||||
validated = true;
|
validated = true;
|
||||||
@@ -153,24 +151,18 @@ public class ThemesManager {
|
|||||||
return validated && modApk.exists() && ThemesUtils.getReservedAccent() != Color.TRANSPARENT;
|
return validated && modApk.exists() && ThemesUtils.getReservedAccent() != Color.TRANSPARENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void generateModApk(int accentColor)
|
public static void generateModApk(int accentColor) throws Throwable {
|
||||||
throws Throwable {
|
try (var apk = new ZipFile(baseApkPath)) {
|
||||||
try (
|
var arscEntry = apk.getEntry("resources.arsc");
|
||||||
final var apk = new ZipFile(baseApkPath)
|
var arscBis = new BufferedInputStream(apk.getInputStream(arscEntry));
|
||||||
) {
|
var arsc = BinaryResourceFile.fromInputStream(arscBis);
|
||||||
final var arscEntry = apk.getEntry("resources.arsc");
|
ArscEditor.changeColors(arsc, ACCENT_COLORS, accentColor);
|
||||||
final var arscBis = new BufferedInputStream(apk.getInputStream(arscEntry));
|
|
||||||
final var arsc = BinaryResourceFile.fromInputStream(arscBis);
|
|
||||||
if (!ArscEditor.changeColors(arsc, ACCENT_COLORS, accentColor))
|
|
||||||
throw new IllegalStateException("Not all colors have been changed");
|
|
||||||
|
|
||||||
try (
|
try (var zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(modApk)))) {
|
||||||
final var zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(modApk)))
|
var arscBuff = arsc.toByteArray();
|
||||||
) {
|
var newArscEntry = new ZipEntry("resources.arsc");
|
||||||
final var arscBuff = arsc.toByteArray();
|
|
||||||
final var newArscEntry = new ZipEntry("resources.arsc");
|
|
||||||
|
|
||||||
final var crc = new CRC32();
|
var crc = new CRC32();
|
||||||
crc.update(arscBuff);
|
crc.update(arscBuff);
|
||||||
|
|
||||||
newArscEntry.setSize(arscBuff.length);
|
newArscEntry.setSize(arscBuff.length);
|
||||||
@@ -181,10 +173,10 @@ public class ThemesManager {
|
|||||||
zos.write(arscBuff);
|
zos.write(arscBuff);
|
||||||
zos.closeEntry();
|
zos.closeEntry();
|
||||||
|
|
||||||
final var entries = apk.entries();
|
var entries = apk.entries();
|
||||||
while (entries.hasMoreElements()) {
|
while (entries.hasMoreElements()) {
|
||||||
final var entry = entries.nextElement();
|
var entry = entries.nextElement();
|
||||||
final var name = entry.getName();
|
var name = entry.getName();
|
||||||
|
|
||||||
if (!name.startsWith("res/") && !name.startsWith("assets/") || name.equals("resources.arsc"))
|
if (!name.startsWith("res/") && !name.startsWith("assets/") || name.equals("resources.arsc"))
|
||||||
continue;
|
continue;
|
||||||
@@ -197,8 +189,8 @@ public class ThemesManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final var entryBis = new BufferedInputStream(apk.getInputStream(entry));
|
var entryBis = new BufferedInputStream(apk.getInputStream(entry));
|
||||||
final var newEntry = new ZipEntry(name);
|
var newEntry = new ZipEntry(name);
|
||||||
|
|
||||||
if (doNotCompress) {
|
if (doNotCompress) {
|
||||||
newEntry.setMethod(ZipEntry.STORED);
|
newEntry.setMethod(ZipEntry.STORED);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class ViewInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static View inject(View view, int i, boolean z) {
|
public static View inject(View view, int i, boolean z) {
|
||||||
if (ThemesUtils.isCustomAccentEnabled()) {
|
if (ThemesUtils.isMonetTheme()) {
|
||||||
for (BaseHook hook : hooks) {
|
for (BaseHook hook : hooks) {
|
||||||
hook.inject(view, i, z);
|
hook.inject(view, i, z);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,42 @@
|
|||||||
package ru.vtosters.lite.themes.loaders;
|
package ru.vtosters.lite.themes.loaders;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import ru.vtosters.lite.utils.ReflectionUtils;
|
import ru.vtosters.lite.utils.ReflectionUtils;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
|
@SuppressLint("PrivateApi")
|
||||||
public class AssetManagerHelper {
|
public class AssetManagerHelper {
|
||||||
|
public static String getApkPath(Object assetManager, String packagename) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException {
|
||||||
|
Object[] apkAssetsArr = (Object[]) ReflectionUtils.findMethod(Class.forName("android.content.res.ApkAssets"), "getAssetPath").invoke(assetManager);
|
||||||
// methods
|
|
||||||
private final static Method getAssetPathMtd;
|
|
||||||
private final static Method getApkAssetsMtd;
|
|
||||||
private final static Method addAssetPathMtd;
|
|
||||||
private final static Method addAssetPathAsSharedLibraryMtd;
|
|
||||||
private final static Method addOverlayPathMtd;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
getAssetPathMtd = ReflectionUtils.findMethod(Class.forName("android.content.res.ApkAssets"), "getAssetPath");
|
|
||||||
getApkAssetsMtd = ReflectionUtils.findMethod(AssetManager.class, "getApkAssets");
|
|
||||||
addAssetPathMtd = AssetManager.class.getMethod("addAssetPath", String.class);
|
|
||||||
addAssetPathAsSharedLibraryMtd = AssetManager.class.getMethod("addAssetPathAsSharedLibrary", String.class);
|
|
||||||
addOverlayPathMtd = AssetManager.class.getMethod("addOverlayPath", String.class);
|
|
||||||
} catch (ClassNotFoundException | NoSuchMethodException e) {
|
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getApkPath(Object assetManager, String packagename)
|
|
||||||
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
|
||||||
Object[] apkAssetsArr = (Object[]) getAssetPathMtd.invoke(assetManager);
|
|
||||||
// By default, base apk is the last element of array if is not, for execution speed, we should iterate array from the end to the beginning
|
// By default, base apk is the last element of array if is not, for execution speed, we should iterate array from the end to the beginning
|
||||||
for (int i = apkAssetsArr.length - 1; i >= 0; i--) {
|
for (int i = apkAssetsArr.length - 1; i >= 0; i--) {
|
||||||
final String pathname = (String) getApkAssetsMtd.invoke(apkAssetsArr[i]);
|
String pathname = (String) ReflectionUtils.findMethod(AssetManager.class, "getApkAssets").invoke(apkAssetsArr[i]);
|
||||||
if (pathname.contains(packagename)) return pathname;
|
if (pathname != null && pathname.contains(packagename)) return pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
}
|
}
|
||||||
|
|
||||||
//region Uses for adding own assets or getting existing cookies
|
//region Uses for adding own assets or getting existing cookies
|
||||||
public static int addAssetPath(AssetManager manager, String path)
|
public static void addAssetPath(AssetManager manager, String path) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
manager.getClass().getMethod("addAssetPath", String.class).invoke(manager, path);
|
||||||
return (int) addAssetPathMtd.invoke(manager, path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int addAssetPathAsSharedLibrary(AssetManager manager, String path)
|
public static int addAssetPathAsSharedLibrary(AssetManager manager, String path) {
|
||||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
try {
|
||||||
return (int) addAssetPathAsSharedLibraryMtd.invoke(manager, path);
|
return (int) AssetManager.class.getMethod("addAssetPathAsSharedLibrary", String.class).invoke(manager, path);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int addOverlayPath(AssetManager manager, String path)
|
public static int addOverlayPath(AssetManager manager, String path) {
|
||||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
try {
|
||||||
return (int) addOverlayPathMtd.invoke(manager, path);
|
return (int) AssetManager.class.getMethod("addOverlayPath", String.class).invoke(manager, path);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//endregion
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package ru.vtosters.lite.themes.loaders;
|
package ru.vtosters.lite.themes.loaders;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
@@ -14,16 +15,17 @@ import ru.vtosters.lite.utils.ReflectionUtils;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SuppressLint("PrivateApi")
|
||||||
public class ResourcesLoader {
|
public class ResourcesLoader {
|
||||||
|
|
||||||
private static Collection<WeakReference<Resources>> sResourceReferences = null;
|
private static Collection<WeakReference<Resources>> sResourceReferences = null;
|
||||||
private static Object sCurrentActivityThread = null;
|
private static Object sCurrentActivityThread = null;
|
||||||
private static AssetManager sAssetManager = null;
|
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
private static Method ensureStringBlocksMtd = null;
|
private static Method ensureStringBlocksMtd = null;
|
||||||
@@ -42,7 +44,7 @@ public class ResourcesLoader {
|
|||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
// API version 8 has PackageInfo, 10 has LoadedApk. Needs to check 9
|
// API version 8 has PackageInfo, 10 has LoadedApk. Needs to check 9
|
||||||
final Class<?> activityThreadClz = Class.forName("android.app.ActivityThread");
|
Class<?> activityThreadClz = Class.forName("android.app.ActivityThread");
|
||||||
Class<?> loadedApkClz;
|
Class<?> loadedApkClz;
|
||||||
try {
|
try {
|
||||||
loadedApkClz = Class.forName("android.app.LoadedApk");
|
loadedApkClz = Class.forName("android.app.LoadedApk");
|
||||||
@@ -87,43 +89,40 @@ public class ResourcesLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void init(Context context)
|
public static void init(Context context) throws Exception {
|
||||||
throws Exception {
|
|
||||||
sCurrentActivityThread = ReflectionUtils.getActivityThread(context, null);
|
sCurrentActivityThread = ReflectionUtils.getActivityThread(context, null);
|
||||||
|
|
||||||
//pre-N
|
//pre-N
|
||||||
// Find the singleton instance of ResourcesManager
|
// Find the singleton instance of ResourcesManager
|
||||||
final Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");
|
Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");
|
||||||
final Method mGetInstance = ReflectionUtils.findMethod(resourcesManagerClass, "getInstance");
|
Method mGetInstance = ReflectionUtils.findMethod(resourcesManagerClass, "getInstance");
|
||||||
final Object resourcesManager = mGetInstance.invoke(null);
|
Object resourcesManager = mGetInstance.invoke(null);
|
||||||
try {
|
try {
|
||||||
Field fMActiveResources = ReflectionUtils.findField(resourcesManagerClass, "mActiveResources");
|
Field fMActiveResources = ReflectionUtils.findField(resourcesManagerClass, "mActiveResources");
|
||||||
final ArrayMap<?, WeakReference<Resources>> activeResources19 =
|
ArrayMap<?, WeakReference<Resources>> activeResources19 =
|
||||||
(ArrayMap<?, WeakReference<Resources>>) fMActiveResources.get(resourcesManager);
|
(ArrayMap<?, WeakReference<Resources>>) fMActiveResources.get(resourcesManager);
|
||||||
sResourceReferences = activeResources19.values();
|
sResourceReferences = activeResources19.values();
|
||||||
} catch (NoSuchFieldException ignore) {
|
} catch (NoSuchFieldException ignore) {
|
||||||
// N moved the resources to mResourceReferences
|
// N moved the resources to mResourceReferences
|
||||||
final Field mResourceReferences = ReflectionUtils.findField(resourcesManagerClass, "mResourceReferences");
|
Field mResourceReferences = ReflectionUtils.findField(resourcesManagerClass, "mResourceReferences");
|
||||||
sResourceReferences = (Collection<WeakReference<Resources>>) mResourceReferences.get(resourcesManager);
|
sResourceReferences = (Collection<WeakReference<Resources>>) mResourceReferences.get(resourcesManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void load(Context context, String resPath, boolean reinject)
|
public static void load(Context context, String resPath, boolean reinject) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException, NoSuchMethodException {
|
||||||
throws Exception {
|
ApplicationInfo appInfo = context.getApplicationInfo();
|
||||||
final ApplicationInfo appInfo = context.getApplicationInfo();
|
|
||||||
|
|
||||||
final Field[] packagesFlds = Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1
|
Field[] packagesFlds = Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1
|
||||||
? new Field[]{packagesFld, resourcePackagesFld}
|
? new Field[]{packagesFld, resourcePackagesFld}
|
||||||
: new Field[]{packagesFld};
|
: new Field[]{packagesFld};
|
||||||
for (var fld : packagesFlds) {
|
for (var fld : packagesFlds) {
|
||||||
final var value = fld.get(sCurrentActivityThread);
|
var value = fld.get(sCurrentActivityThread);
|
||||||
|
|
||||||
for (var entry
|
for (var entry : ((Map<String, WeakReference<?>>) value).entrySet()) {
|
||||||
: ((Map<String, WeakReference<?>>) value).entrySet()) {
|
var loadedApk = entry.getValue().get();
|
||||||
final var loadedApk = entry.getValue().get();
|
|
||||||
if (loadedApk == null) continue;
|
if (loadedApk == null) continue;
|
||||||
|
|
||||||
final var resDirPath = (String) resDirFld.get(loadedApk);
|
var resDirPath = (String) resDirFld.get(loadedApk);
|
||||||
if (appInfo.sourceDir.equals(resDirPath))
|
if (appInfo.sourceDir.equals(resDirPath))
|
||||||
resDirFld.set(loadedApk, resPath);
|
resDirFld.set(loadedApk, resPath);
|
||||||
}
|
}
|
||||||
@@ -134,10 +133,10 @@ public class ResourcesLoader {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sAssetManager = AssetManager.class.newInstance();
|
|
||||||
// Create a new AssetManager instance and point it to the resources installed under
|
// Create a new AssetManager instance and point it to the resources installed under
|
||||||
if (AssetManagerHelper.addAssetPath(sAssetManager, resPath) == 0)
|
AssetManager sAssetManager = AssetManager.class.newInstance();
|
||||||
throw new IllegalStateException("Could not create new AssetManager");
|
|
||||||
|
AssetManagerHelper.addAssetPath(sAssetManager, resPath);
|
||||||
|
|
||||||
// Add SharedLibraries to AssetManager for resolve system resources not found issue
|
// Add SharedLibraries to AssetManager for resolve system resources not found issue
|
||||||
// This influence SharedLibrary Package ID
|
// This influence SharedLibrary Package ID
|
||||||
@@ -157,7 +156,7 @@ public class ResourcesLoader {
|
|||||||
ensureStringBlocksMtd.invoke(sAssetManager);
|
ensureStringBlocksMtd.invoke(sAssetManager);
|
||||||
}
|
}
|
||||||
for (var wr : sResourceReferences) {
|
for (var wr : sResourceReferences) {
|
||||||
final var resources = wr.get();
|
var resources = wr.get();
|
||||||
if (resources == null) continue;
|
if (resources == null) continue;
|
||||||
|
|
||||||
// Set the AssetManager of the Resources instance to our brand new one
|
// Set the AssetManager of the Resources instance to our brand new one
|
||||||
@@ -166,9 +165,9 @@ public class ResourcesLoader {
|
|||||||
assetsFld.set(resources, sAssetManager);
|
assetsFld.set(resources, sAssetManager);
|
||||||
} catch (Throwable ignore) {
|
} catch (Throwable ignore) {
|
||||||
// N
|
// N
|
||||||
final var resourceImpl = resourcesImplFld.get(resources);
|
var resourceImpl = resourcesImplFld.get(resources);
|
||||||
// for Huawei HwResourcesImpl
|
// for Huawei HwResourcesImpl
|
||||||
final Field implAssets = ReflectionUtils.findField(resourceImpl, "mAssets");
|
Field implAssets = ReflectionUtils.findField(resourceImpl, "mAssets");
|
||||||
implAssets.set(resourceImpl, sAssetManager);
|
implAssets.set(resourceImpl, sAssetManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,13 +195,13 @@ public class ResourcesLoader {
|
|||||||
|
|
||||||
private static void installResourceInsuranceHacks(Context context, String patchedResApkPath) {
|
private static void installResourceInsuranceHacks(Context context, String patchedResApkPath) {
|
||||||
try {
|
try {
|
||||||
final Object activityThread = ReflectionUtils.getActivityThread(context, null);
|
Object activityThread = ReflectionUtils.getActivityThread(context, null);
|
||||||
final Field mHField = ReflectionUtils.findField(activityThread, "mH");
|
Field mHField = ReflectionUtils.findField(activityThread, "mH");
|
||||||
final Handler mH = (Handler) mHField.get(activityThread);
|
Handler mH = (Handler) mHField.get(activityThread);
|
||||||
final Field mCallbackField = ReflectionUtils.findField(Handler.class, "mCallback");
|
Field mCallbackField = ReflectionUtils.findField(Handler.class, "mCallback");
|
||||||
final Handler.Callback originCallback = (Handler.Callback) mCallbackField.get(mH);
|
Handler.Callback originCallback = (Handler.Callback) mCallbackField.get(mH);
|
||||||
if (!(originCallback instanceof ResourceInsuranceHandlerCallback)) {
|
if (!(originCallback instanceof ResourceInsuranceHandlerCallback)) {
|
||||||
final ResourceInsuranceHandlerCallback hackCallback = new ResourceInsuranceHandlerCallback(
|
ResourceInsuranceHandlerCallback hackCallback = new ResourceInsuranceHandlerCallback(
|
||||||
context, patchedResApkPath, originCallback, mH.getClass());
|
context, patchedResApkPath, originCallback, mH.getClass());
|
||||||
mCallbackField.set(mH, hackCallback);
|
mCallbackField.set(mH, hackCallback);
|
||||||
} else {
|
} else {
|
||||||
@@ -250,9 +249,9 @@ public class ResourcesLoader {
|
|||||||
Log.w("ResourcesLoader", "try to clear typedArray cache!");
|
Log.w("ResourcesLoader", "try to clear typedArray cache!");
|
||||||
// Clear typedArray cache.
|
// Clear typedArray cache.
|
||||||
try {
|
try {
|
||||||
final Field typedArrayPoolField = ReflectionUtils.findField(Resources.class, "mTypedArrayPool");
|
Field typedArrayPoolField = ReflectionUtils.findField(Resources.class, "mTypedArrayPool");
|
||||||
final Object origTypedArrayPool = typedArrayPoolField.get(resources);
|
Object origTypedArrayPool = typedArrayPoolField.get(resources);
|
||||||
final Method acquireMethod = ReflectionUtils.findMethod(origTypedArrayPool, "acquire");
|
Method acquireMethod = ReflectionUtils.findMethod(origTypedArrayPool, "acquire");
|
||||||
while (true) {
|
while (true) {
|
||||||
if (acquireMethod.invoke(origTypedArrayPool) == null) {
|
if (acquireMethod.invoke(origTypedArrayPool) == null) {
|
||||||
break;
|
break;
|
||||||
@@ -268,9 +267,8 @@ public class ResourcesLoader {
|
|||||||
applicationInfo.sharedLibraryFiles != null;
|
applicationInfo.sharedLibraryFiles != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class ResourceInsuranceHandlerCallback implements Handler.Callback {
|
private static class ResourceInsuranceHandlerCallback implements Handler.Callback {
|
||||||
private static final String LAUNCH_ACTIVITY_LIFECYCLE_ITEM_CLASSNAME = "android.app.servertransaction.LaunchActivityItem";
|
private static final String LAUNCH_ACTIVITY_LIFECYCLE_ITEM_CLASSNAME = "android.app.servertransaction.LaunchActivityItem";
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final String mPatchResApkPath;
|
private final String mPatchResApkPath;
|
||||||
private final Handler.Callback mOriginalCallback;
|
private final Handler.Callback mOriginalCallback;
|
||||||
@@ -319,48 +317,56 @@ public class ResourcesLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private boolean hackMessage(Message msg) {
|
// A method to check if the message requires re-injecting patched resources
|
||||||
boolean shouldReInjectPatchedResources = false;
|
private boolean shouldReInjectPatchedResources(Message msg) {
|
||||||
if (!isPatchedResModifiedAfterLastLoad(mPatchResApkPath)) {
|
if (!isPatchedResModifiedAfterLastLoad(mPatchResApkPath)) {
|
||||||
shouldReInjectPatchedResources = false;
|
return false;
|
||||||
} else {
|
}
|
||||||
if (msg.what == LAUNCH_ACTIVITY || msg.what == RELAUNCH_ACTIVITY) {
|
if (msg.what == LAUNCH_ACTIVITY || msg.what == RELAUNCH_ACTIVITY) {
|
||||||
shouldReInjectPatchedResources = true;
|
return true;
|
||||||
} else if (msg.what == EXECUTE_TRANSACTION) {
|
}
|
||||||
do {
|
// If the message is executing a transaction, check the callbacks
|
||||||
if (mSkipInterceptExecuteTransaction) {
|
if (msg.what == EXECUTE_TRANSACTION) {
|
||||||
break;
|
// Skip the logic if the method is not found or an error occurs
|
||||||
}
|
if (mSkipInterceptExecuteTransaction) {
|
||||||
final Object transaction = msg.obj;
|
return false;
|
||||||
if (transaction == null) {
|
}
|
||||||
Log.w("ResourcesLoader", "transaction is null, skip rest insurance logic.");
|
// Get the transaction object from the message
|
||||||
break;
|
Object transaction = msg.obj;
|
||||||
}
|
if (transaction == null) {
|
||||||
if (mGetCallbacksMethod == null) {
|
Log.w("ResourcesLoader", "transaction is null, skip rest insurance logic.");
|
||||||
try {
|
return false;
|
||||||
mGetCallbacksMethod = ReflectionUtils.findMethod(transaction, "getCallbacks");
|
}
|
||||||
} catch (Throwable ignored) {
|
// Get the getCallbacks method from the transaction class
|
||||||
// Ignored.
|
if (mGetCallbacksMethod == null) {
|
||||||
}
|
try {
|
||||||
}
|
mGetCallbacksMethod = ReflectionUtils.findMethod(transaction, "getCallbacks");
|
||||||
if (mGetCallbacksMethod == null) {
|
} catch (Throwable ignored) {
|
||||||
Log.e("ResourcesLoader", "fail to find getLifecycleStateRequest method, skip rest insurance logic.");
|
// Ignored.
|
||||||
mSkipInterceptExecuteTransaction = true;
|
}
|
||||||
break;
|
}
|
||||||
}
|
if (mGetCallbacksMethod == null) {
|
||||||
try {
|
Log.e("ResourcesLoader", "fail to find getCallbacks method, skip rest insurance logic.");
|
||||||
final List<Object> req = (List<Object>) mGetCallbacksMethod.invoke(transaction);
|
mSkipInterceptExecuteTransaction = true;
|
||||||
if (req != null && req.size() > 0) {
|
return false;
|
||||||
final Object cb = req.get(0);
|
}
|
||||||
shouldReInjectPatchedResources = cb != null && cb.getClass().getName().equals(LAUNCH_ACTIVITY_LIFECYCLE_ITEM_CLASSNAME);
|
try {
|
||||||
}
|
List<Object> callbacks = (List<Object>) mGetCallbacksMethod.invoke(transaction);
|
||||||
} catch (Throwable ignored) {
|
if (callbacks != null && !callbacks.isEmpty()) {
|
||||||
Log.e("ResourcesLoader", "fail to call getLifecycleStateRequest method, skip rest insurance logic.");
|
Object cb = callbacks.get(0);
|
||||||
}
|
return cb != null && cb.getClass().getName().equals(LAUNCH_ACTIVITY_LIFECYCLE_ITEM_CLASSNAME);
|
||||||
} while (false);
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
Log.e("ResourcesLoader", "fail to call getCallbacks method, skip rest insurance logic.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shouldReInjectPatchedResources) {
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A method to hack the message and re-inject patched resources if needed
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private boolean hackMessage(Message msg) {
|
||||||
|
if (shouldReInjectPatchedResources(msg)) {
|
||||||
try {
|
try {
|
||||||
load(mContext, mPatchResApkPath, true);
|
load(mContext, mPatchResApkPath, true);
|
||||||
} catch (Throwable thr) {
|
} catch (Throwable thr) {
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
|||||||
|
|
||||||
public class PalettesManager {
|
public class PalettesManager {
|
||||||
|
|
||||||
private final static File PALETTES_DIR = new File(Environment.getExternalStorageDirectory(), "VTL/palettes");
|
private static final File PALETTES_DIR = new File(Environment.getExternalStorageDirectory(), "VTL/palettes");
|
||||||
private final static String ASSETS_DIR = "vtl_themes/palettes/";
|
private static final String ASSETS_DIR = "vtl_themes/palettes/";
|
||||||
|
|
||||||
private static PalettesManager sInstance;
|
private static PalettesManager sInstance;
|
||||||
|
|
||||||
@@ -51,17 +51,17 @@ public class PalettesManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final var palettes = PALETTES_DIR.listFiles((dir, name) -> name.endsWith(".json"));
|
var palettes = PALETTES_DIR.listFiles((dir, name) -> name.endsWith(".json"));
|
||||||
if (palettes == null || palettes.length == 0)
|
if (palettes == null || palettes.length == 0)
|
||||||
copyPaletteFromAssets();
|
copyPaletteFromAssets();
|
||||||
parsePalettes();
|
parsePalettes();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean copyPaletteFromAssets() {
|
private void copyPaletteFromAssets() {
|
||||||
try {
|
try {
|
||||||
final var manager = AndroidUtils.getGlobalContext().getAssets();
|
var manager = AndroidUtils.getGlobalContext().getAssets();
|
||||||
final var assets = manager.list(ASSETS_DIR);
|
var assets = manager.list(ASSETS_DIR);
|
||||||
if (assets == null || assets.length == 0) return false;
|
if (assets == null || assets.length == 0) return;
|
||||||
for (var asset : assets) {
|
for (var asset : assets) {
|
||||||
try {
|
try {
|
||||||
IOUtils.copy(manager.open(ASSETS_DIR + asset), new File(PALETTES_DIR, asset));
|
IOUtils.copy(manager.open(ASSETS_DIR + asset), new File(PALETTES_DIR, asset));
|
||||||
@@ -69,11 +69,9 @@ public class PalettesManager {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parsePalettes() {
|
private void parsePalettes() {
|
||||||
|
|||||||
@@ -1,37 +1,38 @@
|
|||||||
package ru.vtosters.lite.themes.utils;
|
package ru.vtosters.lite.themes.utils;
|
||||||
|
|
||||||
import com.google.devrel.gmscore.tools.apk.arsc.*;
|
import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;
|
||||||
|
import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceValue;
|
||||||
|
import com.google.devrel.gmscore.tools.apk.arsc.ResourceTableChunk;
|
||||||
|
|
||||||
public class ArscEditor {
|
public class ArscEditor {
|
||||||
|
|
||||||
public static boolean changeColors(BinaryResourceFile arsc, int[] colorIds, int color /*int[] colors*/) {
|
public static void changeColors(BinaryResourceFile arsc, int[] colorIds, int color) {
|
||||||
int q = 0;
|
|
||||||
for (var chunk : arsc.getChunks()) {
|
for (var chunk : arsc.getChunks()) {
|
||||||
final var resourceTableChunk = (ResourceTableChunk) chunk;
|
var resTableChunk = (ResourceTableChunk) chunk;
|
||||||
|
for (var packageChunk : resTableChunk.getPackages()) {
|
||||||
for (PackageChunk packageChunk : resourceTableChunk.getPackages()) {
|
for (var typeChunk : packageChunk.getTypeChunks("color")) {
|
||||||
for (TypeSpecChunk typeSpecChunk : packageChunk.getTypeSpecChunks()) {
|
for (var entry : typeChunk.getEntries().entrySet()) {
|
||||||
for (TypeChunk typeChunk : packageChunk.getTypeChunks(typeSpecChunk.getId())) {
|
var value = entry.getValue();
|
||||||
if (!"color".equals(typeChunk.getTypeName())) continue;
|
if (value.isComplex()) continue;
|
||||||
|
var binValue = value.value();
|
||||||
for (var entry : typeChunk.getEntries().entrySet()) {
|
if (!isColorResourceValue(binValue)) continue;
|
||||||
if (entry.getValue().isComplex()) continue;
|
var resId = packageChunk.getId() << 24 | typeChunk.getId() << 16 | entry.getKey();
|
||||||
|
for (var colodId : colorIds) {
|
||||||
final var id = BinaryResourceIdentifier.create(packageChunk.getId(), typeSpecChunk.getId(), entry.getKey());
|
if (resId == colodId) {
|
||||||
|
binValue.data = color;
|
||||||
for (int colorId : colorIds) {
|
break;
|
||||||
if (colorId != id.resId()) continue;
|
|
||||||
|
|
||||||
final var value = entry.getValue();
|
|
||||||
value.updateValue(new BinaryResourceValue(8, value.value().type(), color /*colors[i]*/));
|
|
||||||
++q;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return q == colorIds.length;
|
private static boolean isColorResourceValue(BinaryResourceValue value) {
|
||||||
|
return value.type == BinaryResourceValue.Type.INT_COLOR_ARGB8
|
||||||
|
|| value.type == BinaryResourceValue.Type.INT_COLOR_RGB8
|
||||||
|
|| value.type == BinaryResourceValue.Type.INT_COLOR_ARGB4
|
||||||
|
|| value.type == BinaryResourceValue.Type.INT_COLOR_RGB4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import android.widget.TextView;
|
|||||||
import com.vk.core.drawable.RecoloredDrawable;
|
import com.vk.core.drawable.RecoloredDrawable;
|
||||||
import ru.vtosters.hooks.other.ThemesUtils;
|
import ru.vtosters.hooks.other.ThemesUtils;
|
||||||
import ru.vtosters.lite.themes.ColorReferences;
|
import ru.vtosters.lite.themes.ColorReferences;
|
||||||
|
import ru.vtosters.lite.themes.ThemesManager;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
public class RecolorUtils {
|
public class RecolorUtils {
|
||||||
@@ -43,7 +44,7 @@ public class RecolorUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int recolorHexColor(int i) {
|
public static int recolorHexColor(int i) {
|
||||||
if (!ThemesUtils.isCustomAccentEnabled()) return i;
|
if (!ThemesUtils.isMonetTheme() || !ThemesManager.canApplyCustomAccent()) return i;
|
||||||
var accented = ColorReferences.isAccentedColor(i);
|
var accented = ColorReferences.isAccentedColor(i);
|
||||||
var mutedaccented = ColorReferences.isMutedAccentedColor(i);
|
var mutedaccented = ColorReferences.isMutedAccentedColor(i);
|
||||||
return (accented || mutedaccented) ? (accented ? ThemesUtils.getAccentColor() : ThemesUtils.getMutedAccentColor()) : i;
|
return (accented || mutedaccented) ? (accented ? ThemesUtils.getAccentColor() : ThemesUtils.getMutedAccentColor()) : i;
|
||||||
@@ -57,7 +58,7 @@ public class RecolorUtils {
|
|||||||
|
|
||||||
@SuppressLint("UseCompatLoadingForColorStateLists")
|
@SuppressLint("UseCompatLoadingForColorStateLists")
|
||||||
public static ColorStateList themeCSL(Context context, int color) {
|
public static ColorStateList themeCSL(Context context, int color) {
|
||||||
if (!ThemesUtils.isCustomAccentEnabled()) {
|
if (!ThemesUtils.isMonetTheme()) {
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
return context.getColorStateList(color);
|
return context.getColorStateList(color);
|
||||||
} else {
|
} else {
|
||||||
@@ -83,7 +84,7 @@ public class RecolorUtils {
|
|||||||
} // Recolor ColorStateList
|
} // Recolor ColorStateList
|
||||||
|
|
||||||
public static ColorStateList themeCSL(ColorStateList csl) {
|
public static ColorStateList themeCSL(ColorStateList csl) {
|
||||||
if (!ThemesUtils.isCustomAccentEnabled()) return csl;
|
if (!ThemesUtils.isMonetTheme()) return csl;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int unsel = csl.getColorForState(new int[]{-android.R.attr.state_selected}, Color.BLACK);
|
int unsel = csl.getColorForState(new int[]{-android.R.attr.state_selected}, Color.BLACK);
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ public class PreferenceFragmentUtils {
|
|||||||
return addPreference(screen, key, title, null, null, preference -> {
|
return addPreference(screen, key, title, null, null, preference -> {
|
||||||
LinearLayout linearLayout = new LinearLayout(screen.getContext());
|
LinearLayout linearLayout = new LinearLayout(screen.getContext());
|
||||||
|
|
||||||
final EditText editText = new EditText(screen.getContext());
|
EditText editText = new EditText(screen.getContext());
|
||||||
editText.setText(PreferenceManager.getDefaultSharedPreferences(screen.getContext()).getString(key, ""));
|
editText.setText(PreferenceManager.getDefaultSharedPreferences(screen.getContext()).getString(key, ""));
|
||||||
editText.setHint(title);
|
editText.setHint(title);
|
||||||
editText.setTextColor(ThemesUtils.getTextAttr());
|
editText.setTextColor(ThemesUtils.getTextAttr());
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import ru.vtosters.lite.utils.AndroidUtils;
|
|||||||
import ru.vtosters.lite.utils.LayoutUtils;
|
import ru.vtosters.lite.utils.LayoutUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ public class CategorizedAdapter<T> extends RecyclerView.Adapter<CategorizedAdapt
|
|||||||
private final List<T> mSelectedItems;
|
private final List<T> mSelectedItems;
|
||||||
private final List<T> mDisabledItems;
|
private final List<T> mDisabledItems;
|
||||||
private final List<T> mUnmovedItems;
|
private final List<T> mUnmovedItems;
|
||||||
|
private final List<T> mExceptions = new ArrayList<>();
|
||||||
private final IViewHolderBinder mBinder;
|
private final IViewHolderBinder mBinder;
|
||||||
|
|
||||||
// Min and max counts
|
// Min and max counts
|
||||||
@@ -48,6 +50,12 @@ public class CategorizedAdapter<T> extends RecyclerView.Adapter<CategorizedAdapt
|
|||||||
this.mBinder = binder;
|
this.mBinder = binder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public final void setExceptions(T... exceptions) {
|
||||||
|
mExceptions.clear();
|
||||||
|
mExceptions.addAll(Arrays.asList(exceptions));
|
||||||
|
}
|
||||||
|
|
||||||
public void setMinAndMaxCounts(int min, int max) {
|
public void setMinAndMaxCounts(int min, int max) {
|
||||||
this.mMinSelectedItems = min;
|
this.mMinSelectedItems = min;
|
||||||
this.mMaxSelectedItems = max;
|
this.mMaxSelectedItems = max;
|
||||||
@@ -173,16 +181,25 @@ public class CategorizedAdapter<T> extends RecyclerView.Adapter<CategorizedAdapt
|
|||||||
public boolean onItemMove(int fromPosition, int toPosition) {
|
public boolean onItemMove(int fromPosition, int toPosition) {
|
||||||
if (toPosition == 0 || isUnmovedItem(toPosition))
|
if (toPosition == 0 || isUnmovedItem(toPosition))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
switch (getTabType(fromPosition)) {
|
switch (getTabType(fromPosition)) {
|
||||||
case SELECTED_STATE -> {
|
case SELECTED_STATE -> {
|
||||||
if (getTabType(toPosition) == DISABLED_STATE
|
if ((getTabType(toPosition) == DISABLED_STATE
|
||||||
&& mSelectedItems.size() == mMinSelectedItems)
|
&& mSelectedItems.size() == mMinSelectedItems))
|
||||||
return false;
|
return false;
|
||||||
if (getItemViewType(toPosition) == CATEGORY_TITLE)
|
if (getItemViewType(toPosition) == CATEGORY_TITLE) {
|
||||||
|
if (!mExceptions.isEmpty()) {
|
||||||
|
boolean except = false;
|
||||||
|
int q = 0;
|
||||||
|
for (var ex : mExceptions) {
|
||||||
|
var i = mSelectedItems.indexOf(ex);
|
||||||
|
if (i == -1) continue;
|
||||||
|
if (i + 1 == fromPosition) except = true;
|
||||||
|
++q;
|
||||||
|
}
|
||||||
|
if (except && q == 1) return false;
|
||||||
|
}
|
||||||
mDisabledItems.add(0, mSelectedItems.remove(fromPosition - 1));
|
mDisabledItems.add(0, mSelectedItems.remove(fromPosition - 1));
|
||||||
else
|
} else Collections.swap(mSelectedItems, fromPosition - 1, toPosition - 1);
|
||||||
Collections.swap(mSelectedItems, fromPosition - 1, toPosition - 1);
|
|
||||||
}
|
}
|
||||||
case DISABLED_STATE -> {
|
case DISABLED_STATE -> {
|
||||||
if (getTabType(toPosition) == SELECTED_STATE
|
if (getTabType(toPosition) == SELECTED_STATE
|
||||||
@@ -198,7 +215,6 @@ public class CategorizedAdapter<T> extends RecyclerView.Adapter<CategorizedAdapt
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyItemMoved(fromPosition, toPosition);
|
notifyItemMoved(fromPosition, toPosition);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -208,7 +224,6 @@ public class CategorizedAdapter<T> extends RecyclerView.Adapter<CategorizedAdapt
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class CategorizedViewHolder extends RecyclerView.ViewHolder {
|
public static class CategorizedViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
private final LinearLayout mMovingItemContainer;
|
private final LinearLayout mMovingItemContainer;
|
||||||
private final ImageView mIcon;
|
private final ImageView mIcon;
|
||||||
private final TextView mName;
|
private final TextView mName;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import ru.vtosters.hooks.other.ThemesUtils;
|
|||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
public class MessagesPreviewAdapter extends RecyclerView.Adapter<MessagesPreviewAdapter.MessagePreviewViewHolder> {
|
public class MessagesPreviewAdapter extends RecyclerView.Adapter<MessagesPreviewAdapter.MessagePreviewViewHolder> {
|
||||||
|
|
||||||
private final String[] mMessages;
|
private final String[] mMessages;
|
||||||
|
|
||||||
public MessagesPreviewAdapter(String... messages) {
|
public MessagesPreviewAdapter(String... messages) {
|
||||||
@@ -35,9 +34,9 @@ public class MessagesPreviewAdapter extends RecyclerView.Adapter<MessagesPreview
|
|||||||
public MessagePreviewViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
|
public MessagePreviewViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
|
||||||
Context context = viewGroup.getContext();
|
Context context = viewGroup.getContext();
|
||||||
|
|
||||||
final var inflater = LayoutInflater.from(context);
|
var inflater = LayoutInflater.from(context);
|
||||||
|
|
||||||
final var bubbleFluidLayout = (BubbleFluidLayout) inflater.inflate(R.layout.vkim_msg_list_item_msg_from_user, viewGroup, false);
|
var bubbleFluidLayout = (BubbleFluidLayout) inflater.inflate(R.layout.vkim_msg_list_item_msg_from_user, viewGroup, false);
|
||||||
bubbleFluidLayout.setPadding(0, AndroidUtils.dp2px(4), 0, 0);
|
bubbleFluidLayout.setPadding(0, AndroidUtils.dp2px(4), 0, 0);
|
||||||
bubbleFluidLayout.setGravity(viewType);
|
bubbleFluidLayout.setGravity(viewType);
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ public class MessagesPreviewAdapter extends RecyclerView.Adapter<MessagesPreview
|
|||||||
bubbleFluidLayout.findViewById(R.id.status).setVisibility(View.GONE);
|
bubbleFluidLayout.findViewById(R.id.status).setVisibility(View.GONE);
|
||||||
bubbleFluidLayout.findViewById(R.id.status_space).setVisibility(View.GONE);
|
bubbleFluidLayout.findViewById(R.id.status_space).setVisibility(View.GONE);
|
||||||
|
|
||||||
final var messageBubbleView = (MsgBubbleView) bubbleFluidLayout.findViewById(R.id.bubble);
|
var messageBubbleView = (MsgBubbleView) bubbleFluidLayout.findViewById(R.id.bubble);
|
||||||
messageBubbleView.setContentPaddingLeft(AndroidUtils.dp2px(10));
|
messageBubbleView.setContentPaddingLeft(AndroidUtils.dp2px(10));
|
||||||
messageBubbleView.setContentPaddingTop(AndroidUtils.dp2px(4));
|
messageBubbleView.setContentPaddingTop(AndroidUtils.dp2px(4));
|
||||||
messageBubbleView.setContentPaddingRight(AndroidUtils.dp2px(4));
|
messageBubbleView.setContentPaddingRight(AndroidUtils.dp2px(4));
|
||||||
@@ -59,10 +58,10 @@ public class MessagesPreviewAdapter extends RecyclerView.Adapter<MessagesPreview
|
|||||||
}
|
}
|
||||||
bubbleFluidLayout.setLayoutDirection(viewType);
|
bubbleFluidLayout.setLayoutDirection(viewType);
|
||||||
|
|
||||||
final var msgTextView = inflater.inflate(R.layout.vkim_msg_part_text, viewGroup, false);
|
var msgTextView = inflater.inflate(R.layout.vkim_msg_part_text, viewGroup, false);
|
||||||
msgTextView.setId(R.id.msg_text);
|
msgTextView.setId(R.id.msg_text);
|
||||||
|
|
||||||
final var msgBubbleView = (MsgBubbleView) bubbleFluidLayout.findViewById(R.id.bubble);
|
var msgBubbleView = (MsgBubbleView) bubbleFluidLayout.findViewById(R.id.bubble);
|
||||||
msgBubbleView.setContentView(msgTextView);
|
msgBubbleView.setContentView(msgTextView);
|
||||||
|
|
||||||
return new MessagePreviewViewHolder(bubbleFluidLayout);
|
return new MessagePreviewViewHolder(bubbleFluidLayout);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ru.vtosters.lite.ui.adapters;
|
package ru.vtosters.lite.ui.adapters;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -28,6 +29,7 @@ import ru.vtosters.hooks.SwitchHook;
|
|||||||
import ru.vtosters.lite.tgs.TGPref;
|
import ru.vtosters.lite.tgs.TGPref;
|
||||||
import ru.vtosters.lite.tgs.TGRoot;
|
import ru.vtosters.lite.tgs.TGRoot;
|
||||||
import ru.vtosters.lite.ui.components.IItemMovingListener;
|
import ru.vtosters.lite.ui.components.IItemMovingListener;
|
||||||
|
import ru.vtosters.lite.ui.fragments.VTSettings;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -138,7 +140,11 @@ public class StickerPackAdapter extends RecyclerView.Adapter<StickerPackAdapter.
|
|||||||
});
|
});
|
||||||
mName.setText(pack.title);
|
mName.setText(pack.title);
|
||||||
mSwitch.setChecked(pack.enabled);
|
mSwitch.setChecked(pack.enabled);
|
||||||
mSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> sService.setPackEnabled(pack, isChecked, true));
|
mSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||||
|
sService.setPackEnabled(pack, isChecked, false);
|
||||||
|
buttonView.getContext().sendBroadcast(new Intent(VTSettings.ACTION_INVALIDATE_TGS_COUNT));
|
||||||
|
});
|
||||||
|
|
||||||
mStickersCount.setText(pack.stickersCount + " " + mStickersCount.getContext().getString(R.string.stickerscount));
|
mStickersCount.setText(pack.stickersCount + " " + mStickersCount.getContext().getString(R.string.stickerscount));
|
||||||
|
|
||||||
SwitchHook.setSwitchCompatColors(mSwitch, mSwitch.getContext());
|
SwitchHook.setSwitchCompatColors(mSwitch, mSwitch.getContext());
|
||||||
|
|||||||
@@ -59,71 +59,7 @@ public class DockBarEditorManager {
|
|||||||
: sInstance;
|
: sInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
public static DockBarTab getTabByTag(String tag) {
|
||||||
if (vkme()) {
|
|
||||||
mSelectedTabs.add(getTabByTag("tab_settings"));
|
|
||||||
if (vkme_notifs()) {
|
|
||||||
if (milkshake()) {
|
|
||||||
mSelectedTabs.add(getTabByTag("tab_feedback"));
|
|
||||||
} else {
|
|
||||||
mSelectedTabs.add(getTabByTag("tab_friends"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mSelectedTabs.add(getTabByTag("tab_messages"));
|
|
||||||
mSelectedTabs.add(getTabByTag("tab_profile"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkOldConfig();
|
|
||||||
|
|
||||||
var selectedTabsTags = getPreferences()
|
|
||||||
.getString("dockbar_tabs",
|
|
||||||
"tab_news,tab_superapps,tab_messages,tab_friends,tab_profile")
|
|
||||||
.split(",");
|
|
||||||
parseSelectedTabs(selectedTabsTags);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkOldConfig() {
|
|
||||||
File config = new File(getGlobalContext().getFilesDir(), "dockbar.json");
|
|
||||||
if (config.exists()) config.deleteOnExit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseSelectedTabs(String[] selectedTabsTags) {
|
|
||||||
var allTags = new ArrayList<>(ALL_TAGS);
|
|
||||||
|
|
||||||
for (String tag : selectedTabsTags) {
|
|
||||||
mSelectedTabs.add(getTabByTag(tag));
|
|
||||||
allTags.remove(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String tag : allTags) {
|
|
||||||
mDisabledTabs.add(getTabByTag(tag));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
for (DockBarTab tab : mSelectedTabs)
|
|
||||||
sb.append(tab.tag).append(",");
|
|
||||||
getPreferences().edit().putString("dockbar_tabs", sb.toString()).commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
Preferences.getPreferences()
|
|
||||||
.edit()
|
|
||||||
.putString("dockbar_tabs", "tab_news,tab_superapps,tab_messages,tab_friends,tab_profile")
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<DockBarTab> getSelectedTabs() {
|
|
||||||
return mSelectedTabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<DockBarTab> getDisabledTabs() {
|
|
||||||
return mDisabledTabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DockBarTab getTabByTag(String tag) {
|
|
||||||
return switch (tag) {
|
return switch (tag) {
|
||||||
case "tab_news" -> DockBarTab.valuesOf(
|
case "tab_news" -> DockBarTab.valuesOf(
|
||||||
"tab_news",
|
"tab_news",
|
||||||
@@ -235,4 +171,68 @@ public class DockBarEditorManager {
|
|||||||
useNewSettings());
|
useNewSettings());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
if (vkme()) {
|
||||||
|
mSelectedTabs.add(getTabByTag("tab_settings"));
|
||||||
|
if (vkme_notifs()) {
|
||||||
|
if (milkshake()) {
|
||||||
|
mSelectedTabs.add(getTabByTag("tab_feedback"));
|
||||||
|
} else {
|
||||||
|
mSelectedTabs.add(getTabByTag("tab_friends"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mSelectedTabs.add(getTabByTag("tab_messages"));
|
||||||
|
mSelectedTabs.add(getTabByTag("tab_profile"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkOldConfig();
|
||||||
|
|
||||||
|
var selectedTabsTags = getPreferences()
|
||||||
|
.getString("dockbar_tabs",
|
||||||
|
"tab_news,tab_superapps,tab_messages,tab_friends,tab_profile")
|
||||||
|
.split(",");
|
||||||
|
parseSelectedTabs(selectedTabsTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkOldConfig() {
|
||||||
|
File config = new File(getGlobalContext().getFilesDir(), "dockbar.json");
|
||||||
|
if (config.exists()) config.deleteOnExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseSelectedTabs(String[] selectedTabsTags) {
|
||||||
|
var allTags = new ArrayList<>(ALL_TAGS);
|
||||||
|
|
||||||
|
for (String tag : selectedTabsTags) {
|
||||||
|
mSelectedTabs.add(getTabByTag(tag));
|
||||||
|
allTags.remove(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String tag : allTags) {
|
||||||
|
mDisabledTabs.add(getTabByTag(tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (DockBarTab tab : mSelectedTabs)
|
||||||
|
sb.append(tab.tag).append(",");
|
||||||
|
getPreferences().edit().putString("dockbar_tabs", sb.toString()).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
Preferences.getPreferences()
|
||||||
|
.edit()
|
||||||
|
.putString("dockbar_tabs", "tab_news,tab_superapps,tab_messages,tab_friends,tab_profile")
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DockBarTab> getSelectedTabs() {
|
||||||
|
return mSelectedTabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DockBarTab> getDisabledTabs() {
|
||||||
|
return mDisabledTabs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ public class IconManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
public static List<String> icons() {
|
public static List<String> icons() {
|
||||||
return Preferences.hasVerification() && Preferences.isValidSignature() ? sIconsPlusNames : sIconsNames; // delete later sdk check
|
return sIconsPlusNames; // delete later sdk check
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> iconsValues() {
|
public static List<String> iconsValues() {
|
||||||
return Preferences.hasVerification() && Preferences.isValidSignature() ? sIconsPlus : sIcons; // delete later sdk check
|
return sIconsPlus; // delete later sdk check
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void switchComponent(String icon, String appName) {
|
public static void switchComponent(String icon, String appName) {
|
||||||
@@ -55,7 +55,7 @@ public class IconManager {
|
|||||||
|
|
||||||
Log.d("IconManager", "iconSwitcher: icon = " + icon + ", appname = " + appName);
|
Log.d("IconManager", "iconSwitcher: icon = " + icon + ", appname = " + appName);
|
||||||
|
|
||||||
List<String> iconsList = (Preferences.hasVerification() && Preferences.isValidSignature()) ? sIconsPlus : sIcons;
|
List<String> iconsList = sIconsPlus;
|
||||||
List<String> labelsList = sLabels;
|
List<String> labelsList = sLabels;
|
||||||
|
|
||||||
// check if icon is exist
|
// check if icon is exist
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
package ru.vtosters.lite.ui.dialogs;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
|
||||||
import com.vtosters.lite.R;
|
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
|
||||||
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
|
||||||
|
|
||||||
public class DisableBattery {
|
|
||||||
public static void alert(Activity activity) {
|
|
||||||
if (!(OEMDetector.isOEM() && Build.VERSION.SDK_INT >= 23 && Preferences.getBoolValue("showDoze", true)))
|
|
||||||
return;
|
|
||||||
final String packName = activity.getPackageName();
|
|
||||||
final PowerManager manager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
|
|
||||||
if (manager.isIgnoringBatteryOptimizations(packName)) return;
|
|
||||||
new VkAlertDialog.Builder(activity)
|
|
||||||
.setTitle(R.string.batteryissuetitle)
|
|
||||||
.setMessage(R.string.batteryissuesumm)
|
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton(R.string.batteryissuebtn1, (dialogInterface, i) -> {
|
|
||||||
@SuppressLint("BatteryLife")
|
|
||||||
var intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
|
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
.setData(Uri.parse("package:" + packName));
|
|
||||||
activity.startActivity(intent);
|
|
||||||
})
|
|
||||||
.setNeutralButton(R.string.batteryissuebtn2,
|
|
||||||
(dialogInterface, i) -> Preferences.getPreferences().edit().putBoolean("showDoze", false).apply()
|
|
||||||
)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package ru.vtosters.lite.ui.dialogs;
|
package ru.vtosters.lite.ui.dialogs;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.hooks.other.ThemesUtils;
|
import ru.vtosters.hooks.other.ThemesUtils;
|
||||||
import ru.vtosters.lite.downloaders.OTADownloader;
|
import ru.vtosters.lite.downloaders.OTADownloader;
|
||||||
@@ -16,22 +18,25 @@ public class OTADialog extends ModalBottomSheetWrapper<OTADialog> implements OTA
|
|||||||
|
|
||||||
private final OTAUtils mHelper;
|
private final OTAUtils mHelper;
|
||||||
|
|
||||||
public OTADialog(Activity activity) {
|
public OTADialog(Activity activity, boolean isManualCheck) {
|
||||||
super(activity);
|
super(activity);
|
||||||
mActivity = activity;
|
mActivity = activity;
|
||||||
|
|
||||||
mHelper = new OTAUtils(this);
|
mHelper = new OTAUtils(this);
|
||||||
mHelper.loadData();
|
mHelper.loadData(isManualCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkUpdates(Activity activity) {
|
public static void checkUpdates(Activity activity) {
|
||||||
new OTADialog(activity);
|
new OTADialog(activity, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkUpdatesManual(Activity activity) {
|
||||||
|
new OTADialog(activity, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpdateApplied() {
|
public void onUpdateApplied(boolean isManualCheck) {
|
||||||
mActivity.runOnUiThread(() -> {
|
mActivity.runOnUiThread(() -> {
|
||||||
// Toast.makeText(mActivity, "Обновления найдены", Toast.LENGTH_SHORT).show();
|
|
||||||
setTitle(mActivity.getString(R.string.newversion) + " " + mHelper.getNewVersionName())
|
setTitle(mActivity.getString(R.string.newversion) + " " + mHelper.getNewVersionName())
|
||||||
.setView(makeUpdateInfoView(mHelper.getUpdateDescription()))
|
.setView(makeUpdateInfoView(mHelper.getUpdateDescription()))
|
||||||
.setPositiveButton(mActivity.getString(R.string.updateanddownload), () -> {
|
.setPositiveButton(mActivity.getString(R.string.updateanddownload), () -> {
|
||||||
@@ -41,6 +46,7 @@ public class OTADialog extends ModalBottomSheetWrapper<OTADialog> implements OTA
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
private View makeUpdateInfoView(String changelog) {
|
private View makeUpdateInfoView(String changelog) {
|
||||||
LinearLayout container = new LinearLayout(mActivity);
|
LinearLayout container = new LinearLayout(mActivity);
|
||||||
TextView changelogView = new TextView(mActivity);
|
TextView changelogView = new TextView(mActivity);
|
||||||
@@ -55,9 +61,18 @@ public class OTADialog extends ModalBottomSheetWrapper<OTADialog> implements OTA
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpdateCanceled() {
|
public void onUpdateLatest(boolean isManualCheck) {
|
||||||
// mActivity.runOnUiThread(() -> {
|
if (isManualCheck) {
|
||||||
// // Toast.makeText(mActivity, "Обновлений не найдено", Toast.LENGTH_SHORT).show();
|
mActivity.runOnUiThread(() -> {
|
||||||
// });
|
Toast.makeText(mActivity, "У вас установлена последняя версия приложения", Toast.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdateError() {
|
||||||
|
mActivity.runOnUiThread(() -> {
|
||||||
|
Toast.makeText(mActivity, "Ошибка проверки обновления", Toast.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
package ru.vtosters.lite.ui.dialogs;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.util.Log;
|
|
||||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
|
||||||
import okhttp3.*;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
|
||||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import static android.content.ContentValues.TAG;
|
|
||||||
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
|
||||||
import static ru.vtosters.hooks.other.Preferences.hasVerification;
|
|
||||||
|
|
||||||
public class ServerDialog {
|
|
||||||
private static final OkHttpClient client = VtOkHttpClient.getInstance();
|
|
||||||
public static Boolean showAlert;
|
|
||||||
private static String title;
|
|
||||||
private static String message;
|
|
||||||
private static String positiveButton;
|
|
||||||
private static String neutralButton;
|
|
||||||
private static String link;
|
|
||||||
private static String key;
|
|
||||||
private static Boolean cancelable;
|
|
||||||
private static Boolean isNotForVerified;
|
|
||||||
|
|
||||||
public static void sendRequest() {
|
|
||||||
if (getBoolValue("dialogrecomm", false)) return;
|
|
||||||
|
|
||||||
if (!getBoolValue("isRoamingState", false)) {
|
|
||||||
Request request = new Request.a()
|
|
||||||
.b("https://vtosters.app/dialog.json")
|
|
||||||
.a();
|
|
||||||
|
|
||||||
client.a(request).a(new Callback() {
|
|
||||||
@Override
|
|
||||||
public void a(Call call, IOException e) {
|
|
||||||
Log.d(TAG, e.getMessage());
|
|
||||||
showAlert = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void a(Call call, Response response) {
|
|
||||||
try {
|
|
||||||
getResponse(response.a().g());
|
|
||||||
} catch (Exception e) {
|
|
||||||
showAlert = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean showForNotVerified() {
|
|
||||||
int random = new Random().nextInt(6);
|
|
||||||
|
|
||||||
if (isNotForVerified) {
|
|
||||||
return !hasVerification() && random != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void getResponse(String body) throws Exception {
|
|
||||||
JSONObject jSONObject = new JSONObject(body);
|
|
||||||
|
|
||||||
if (jSONObject.getString("title").equals("")) {
|
|
||||||
showAlert = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
title = jSONObject.getString("title");
|
|
||||||
message = jSONObject.getString("message");
|
|
||||||
positiveButton = jSONObject.getString("positiveButton");
|
|
||||||
neutralButton = jSONObject.getString("neutralButton");
|
|
||||||
link = jSONObject.getString("link");
|
|
||||||
key = jSONObject.getString("key");
|
|
||||||
cancelable = jSONObject.getBoolean("cancelable");
|
|
||||||
isNotForVerified = jSONObject.getBoolean("isNotForVerified");
|
|
||||||
|
|
||||||
showAlert = true;
|
|
||||||
|
|
||||||
Activity activity = LifecycleUtils.getCurrentActivity();
|
|
||||||
if (getBoolValue(key, true) && showForNotVerified() && showAlert) {
|
|
||||||
activity.runOnUiThread(() -> {
|
|
||||||
new VkAlertDialog.Builder(activity)
|
|
||||||
.setTitle(title)
|
|
||||||
.setMessage(message)
|
|
||||||
.setCancelable(cancelable)
|
|
||||||
.setPositiveButton(positiveButton,
|
|
||||||
(dialogInterface, i) -> Preferences.getPreferences().edit().putBoolean(key, false).apply())
|
|
||||||
.setNeutralButton(neutralButton, (dialogInterface, i) -> {
|
|
||||||
Preferences.getPreferences().edit().putBoolean(key, false).apply();
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW)
|
|
||||||
.setData(Uri.parse(link));
|
|
||||||
activity.startActivity(intent);
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -93,6 +93,7 @@ public class DataSettingsFragment extends TrackedMaterialPreferenceToolbarFragme
|
|||||||
FileUtils.l();
|
FileUtils.l();
|
||||||
AutoPlayCacheHolder.d.a();
|
AutoPlayCacheHolder.d.a();
|
||||||
MediaStorage.a();
|
MediaStorage.a();
|
||||||
|
IOUtils.deleteRecursive(AndroidUtils.getGlobalContext().getCacheDir());
|
||||||
updateCacheSize();
|
updateCacheSize();
|
||||||
})
|
})
|
||||||
.setNegativeButton(requireContext().getString(com.vtosters.lite.R.string.cancel), (dialogInterface, i) -> dialogInterface.dismiss())
|
.setNegativeButton(requireContext().getString(com.vtosters.lite.R.string.cancel), (dialogInterface, i) -> dialogInterface.dismiss())
|
||||||
@@ -125,6 +126,7 @@ public class DataSettingsFragment extends TrackedMaterialPreferenceToolbarFragme
|
|||||||
ImEngineExt.a(ImEngine1.a());
|
ImEngineExt.a(ImEngine1.a());
|
||||||
AutoPlayCacheHolder.d.a();
|
AutoPlayCacheHolder.d.a();
|
||||||
MediaStorage.a();
|
MediaStorage.a();
|
||||||
|
IOUtils.deleteRecursive(AndroidUtils.getGlobalContext().getCacheDir());
|
||||||
}
|
}
|
||||||
case 1 -> {
|
case 1 -> {
|
||||||
SharedPreferences prefs2 = getContext().getSharedPreferences("stickers_storage", Context.MODE_PRIVATE);
|
SharedPreferences prefs2 = getContext().getSharedPreferences("stickers_storage", Context.MODE_PRIVATE);
|
||||||
@@ -168,12 +170,18 @@ public class DataSettingsFragment extends TrackedMaterialPreferenceToolbarFragme
|
|||||||
|
|
||||||
var pref = Preferences.getPreferences().getString("autoclearcache", "Default");
|
var pref = Preferences.getPreferences().getString("autoclearcache", "Default");
|
||||||
switch (pref) {
|
switch (pref) {
|
||||||
case "Default" -> findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_disabled));
|
case "Default" ->
|
||||||
case "100mb" -> findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_100mb));
|
findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_disabled));
|
||||||
case "500mb" -> findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_500mb));
|
case "100mb" ->
|
||||||
case "1gb" -> findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_1gb));
|
findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_100mb));
|
||||||
case "2gb" -> findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_2gb));
|
case "500mb" ->
|
||||||
case "5gb" -> findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_5gb));
|
findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_500mb));
|
||||||
|
case "1gb" ->
|
||||||
|
findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_1gb));
|
||||||
|
case "2gb" ->
|
||||||
|
findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_2gb));
|
||||||
|
case "5gb" ->
|
||||||
|
findPreference("autoclearcache_btn").setSummary(requireContext().getString(com.vtosters.lite.R.string.cache_5gb));
|
||||||
}
|
}
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), AndroidUtils.getString("accounts"));
|
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), AndroidUtils.getString("accounts"));
|
||||||
@@ -293,6 +301,20 @@ public class DataSettingsFragment extends TrackedMaterialPreferenceToolbarFragme
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addMaterialSwitchPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"serverFeaturesDisable",
|
||||||
|
"Отключить всю работу с сервером",
|
||||||
|
"Отключает галочки и все дополнительные функции связанные с получением данных с сервера ВТостерс",
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
(preference, o) -> {
|
||||||
|
Preferences.getPreferences().edit().putBoolean("serverFeaturesDisable", (boolean) o).apply();
|
||||||
|
LifecycleUtils.restartApplicationWithTimer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
findPreference("analyticsDisabled").setVisible(Preferences.isValidSignature());
|
findPreference("analyticsDisabled").setVisible(Preferences.isValidSignature());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ public class DockBarEditorFragment extends BaseToolbarFragment {
|
|||||||
holder.bindMovingItem(tab.iconID, tab.titleID);
|
holder.bindMovingItem(tab.iconID, tab.titleID);
|
||||||
});
|
});
|
||||||
adapter.setMinAndMaxCounts(DockBarEditorManager.MIN_SELECTED_TABS, DockBarEditorManager.MAX_SELECTED_TABS);
|
adapter.setMinAndMaxCounts(DockBarEditorManager.MIN_SELECTED_TABS, DockBarEditorManager.MAX_SELECTED_TABS);
|
||||||
|
adapter.setExceptions(
|
||||||
|
DockBarEditorManager.getTabByTag("tab_profile"),
|
||||||
|
DockBarEditorManager.getTabByTag("")/*settings*/);
|
||||||
|
|
||||||
var recyclerView = new RecyclerView(requireContext());
|
var recyclerView = new RecyclerView(requireContext());
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
|||||||
@@ -92,13 +92,6 @@ public class IconsFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
|
|
||||||
this.addPreferencesFromResource(R.xml.empty);
|
this.addPreferencesFromResource(R.xml.empty);
|
||||||
|
|
||||||
if (!Preferences.hasVerification() && !Preferences.getBoolValue("dialogrecomm", false)) {
|
|
||||||
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", requireContext().getString(R.string.icons_warning), requireContext().getString(R.string.icons_warning_info), R.drawable.ic_about_outline_28, preference -> {
|
|
||||||
getContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://vtosters.app/donate/")));
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), requireContext().getString(R.string.icons_title));
|
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), requireContext().getString(R.string.icons_title));
|
||||||
|
|
||||||
for (var i = 0; i < IconManager.icons().size(); i++) {
|
for (var i = 0; i < IconManager.icons().size(); i++) {
|
||||||
@@ -128,37 +121,5 @@ public class IconsFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Preferences.hasVerification() && !Preferences.getBoolValue("dialogrecomm", false)) {
|
|
||||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), requireContext().getString(R.string.unavailable_icons));
|
|
||||||
|
|
||||||
for (var i = 2; i < IconManager.sIconsPlusNames.size(); i++) {
|
|
||||||
if (IconManager.sIconsPlusNames.get(i) == null || IconManager.sIconsPlus.get(i) == null) return;
|
|
||||||
|
|
||||||
String iconname = IconManager.sIconsPlusNames.get(i);
|
|
||||||
String icon = IconManager.sIconsPlus.get(i);
|
|
||||||
|
|
||||||
int iconRes;
|
|
||||||
|
|
||||||
if (icon.equals("vt")) {
|
|
||||||
iconRes = R.mipmap.vt_launcher_round;
|
|
||||||
} else if (icon.contains("standard")) {
|
|
||||||
iconRes = R.mipmap.ic_launcher_round;
|
|
||||||
} else {
|
|
||||||
iconRes = AndroidUtils.getIdentifier("ic_launcher_" + icon, "mipmap");
|
|
||||||
}
|
|
||||||
|
|
||||||
Drawable drawable = ResourcesCompat.getDrawable(getResources(), iconRes, null);
|
|
||||||
|
|
||||||
if (drawable == null) {
|
|
||||||
drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.ic_bug_outline_28, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), icon, iconname, "", drawable, preference -> {
|
|
||||||
AndroidUtils.sendToast(requireContext().getString(R.string.unavailable_icon_warning));
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ru.vtosters.lite.ui.fragments;
|
package ru.vtosters.lite.ui.fragments;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
|
|
||||||
public class InstallGMSFragment extends TrackedMaterialPreferenceToolbarFragment {
|
public class InstallGMSFragment extends TrackedMaterialPreferenceToolbarFragment {
|
||||||
@@ -8,6 +11,24 @@ public class InstallGMSFragment extends TrackedMaterialPreferenceToolbarFragment
|
|||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
addPreferencesFromResource(R.xml.preferences_gms);
|
addPreferencesFromResource(R.xml.preferences_gms);
|
||||||
|
|
||||||
|
findPreference("microg").setOnPreferenceClickListener((preference) -> {
|
||||||
|
String[] items = {"Уменьшенный MicroG (ReVanced)", "Альтернативный MicroG (ReVanced)", "Полноценный MicroG (Замена GMS)"};
|
||||||
|
|
||||||
|
new VkAlertDialog.Builder(requireActivity()).setItems(items, (dialog, which) -> {
|
||||||
|
switch (which) {
|
||||||
|
case 0 ->
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://github.com/TeamVanced/VancedMicroG/releases/latest/download/microg.apk")));
|
||||||
|
case 1 ->
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://github.com/inotia00/mMicroG/releases/latest/download/microg.apk")));
|
||||||
|
case 2 ->
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://github.com/microg/GmsCore/releases")));
|
||||||
|
}
|
||||||
|
dismiss();
|
||||||
|
}).show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import ru.vtosters.lite.concurrent.VTExecutors;
|
|||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.downloaders.VideoDownloader;
|
import ru.vtosters.lite.downloaders.VideoDownloader;
|
||||||
import ru.vtosters.lite.music.LastFMScrobbler;
|
import ru.vtosters.lite.music.LastFMScrobbler;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.proxy.ProxyUtils;
|
import ru.vtosters.lite.proxy.ProxyUtils;
|
||||||
import ru.vtosters.lite.ui.adapters.ImagineArrayAdapter;
|
import ru.vtosters.lite.ui.adapters.ImagineArrayAdapter;
|
||||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||||
@@ -108,9 +108,9 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
findPreference("lastfm_enabled").setEnabled(false);
|
findPreference("lastfm_enabled").setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(R.string.cached_tracks_counter), CacheDatabaseDelegate.getTrackCount()));
|
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(R.string.cached_tracks_counter), MusicCacheImpl.getTracksCount()));
|
||||||
findPreference("cached_tracks").setOnPreferenceClickListener(preference -> {
|
findPreference("cached_tracks").setOnPreferenceClickListener(preference -> {
|
||||||
if (CacheDatabaseDelegate.getTrackCount() == 0) {
|
if (MusicCacheImpl.isEmpty()) {
|
||||||
AndroidUtils.sendToast(requireContext().getString(R.string.no_cache_error));
|
AndroidUtils.sendToast(requireContext().getString(R.string.no_cache_error));
|
||||||
} else {
|
} else {
|
||||||
delcache(requireContext());
|
delcache(requireContext());
|
||||||
@@ -123,6 +123,8 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
findPreference("invertCachedTracks").setVisible(!MusicCacheImpl.isEmpty());
|
||||||
|
|
||||||
if (!LibVKXClient.isVkxInstalled()) {
|
if (!LibVKXClient.isVkxInstalled()) {
|
||||||
findPreference("vkx_sett").setVisible(false);
|
findPreference("vkx_sett").setVisible(false);
|
||||||
}
|
}
|
||||||
@@ -215,7 +217,7 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(R.string.cached_tracks_remove_confirm)
|
.setMessage(R.string.cached_tracks_remove_confirm)
|
||||||
.setPositiveButton(R.string.yes,
|
.setPositiveButton(R.string.yes,
|
||||||
(dialog, which) -> executor.submit(CacheDatabaseDelegate::clear))
|
(dialog, which) -> executor.submit(MusicCacheImpl::clear))
|
||||||
.setNeutralButton(R.string.no,
|
.setNeutralButton(R.string.no,
|
||||||
(dialog, which) -> dialog.cancel())
|
(dialog, which) -> dialog.cancel())
|
||||||
.show();
|
.show();
|
||||||
|
|||||||
@@ -1,40 +1,23 @@
|
|||||||
package ru.vtosters.lite.ui.fragments;
|
package ru.vtosters.lite.ui.fragments;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
import android.widget.RadioButton;
|
|
||||||
import android.widget.RadioGroup;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.appcompat.view.ContextThemeWrapper;
|
|
||||||
import b.h.g.m.FileUtils;
|
|
||||||
import com.vk.auth.api.VKAccount;
|
import com.vk.auth.api.VKAccount;
|
||||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
|
||||||
import com.vk.core.util.ToastUtils;
|
import com.vk.core.util.ToastUtils;
|
||||||
import com.vk.im.engine.ImEngine1;
|
|
||||||
import com.vk.im.engine.ImEngineExt;
|
|
||||||
import com.vk.im.ui.providers.audiomsg.ImAudioMsgPlayerProvider;
|
|
||||||
import com.vk.im.ui.providers.audiomsg.PlayerActionSources;
|
|
||||||
import com.vk.imageloader.VKImageLoader;
|
|
||||||
import com.vk.media.player.cache.AutoPlayCacheHolder;
|
|
||||||
import com.vk.mediastore.MediaStorage;
|
|
||||||
import com.vk.pushes.PushSubscriber;
|
import com.vk.pushes.PushSubscriber;
|
||||||
import com.vk.stickers.Stickers;
|
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import com.vtosters.lite.auth.VKAccountManager;
|
import com.vtosters.lite.auth.VKAccountManager;
|
||||||
import com.vtosters.lite.im.ImEngineProvider;
|
|
||||||
import ru.vtosters.hooks.GmsHook;
|
import ru.vtosters.hooks.GmsHook;
|
||||||
import ru.vtosters.hooks.SwitchHook;
|
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.hooks.other.ThemesUtils;
|
|
||||||
import ru.vtosters.lite.deviceinfo.DeviceInfoCollector;
|
import ru.vtosters.lite.deviceinfo.DeviceInfoCollector;
|
||||||
import ru.vtosters.lite.ssfs.UsersList;
|
import ru.vtosters.lite.ssfs.UsersList;
|
||||||
import ru.vtosters.lite.ui.activities.VKAdminTokenActivity;
|
import ru.vtosters.lite.ui.activities.VKAdminTokenActivity;
|
||||||
import ru.vtosters.lite.utils.*;
|
import ru.vtosters.lite.utils.*;
|
||||||
|
|
||||||
|
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
||||||
|
|
||||||
public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
|
public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
|
||||||
private static final int VK_ADMIN_TOKEN_REQUEST_CODE = 1;
|
private static final int VK_ADMIN_TOKEN_REQUEST_CODE = 1;
|
||||||
|
|
||||||
@@ -84,7 +67,7 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
findPreference("microgsettings").setOnPreferenceClickListener(preference -> {
|
findPreference("microgsettings").setOnPreferenceClickListener(preference -> {
|
||||||
try {
|
try {
|
||||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||||
intent.setComponent(new ComponentName("com.mgoogle.android.gms", "org.microg.gms.ui.SettingsActivity"));
|
intent.setComponent(new ComponentName((GmsHook.isFakeGms2Installed() ? "app.revanced.android.gms" : "com.mgoogle.android.gms"), "org.microg.gms.ui.SettingsActivity"));
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -122,13 +105,6 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
findPreference("datasettings").setOnPreferenceClickListener(preference -> {
|
|
||||||
NavigatorUtils.switchFragment(requireContext(), DataSettingsFragment.class);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("dialogrecomm").setVisible(!Preferences.hasVerification());
|
|
||||||
|
|
||||||
findPreference("updateverifdata").setOnPreferenceClickListener(preference -> {
|
findPreference("updateverifdata").setOnPreferenceClickListener(preference -> {
|
||||||
UsersList.getUsersList();
|
UsersList.getUsersList();
|
||||||
VTVerifications.load(requireContext());
|
VTVerifications.load(requireContext());
|
||||||
@@ -136,6 +112,10 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
findPreference("VT_Verification").setVisible(!getBoolValue("serverFeaturesDisable", false));
|
||||||
|
findPreference("VT_Fire").setVisible(!getBoolValue("serverFeaturesDisable", false));
|
||||||
|
findPreference("updateverifdata").setVisible(!getBoolValue("serverFeaturesDisable", false));
|
||||||
|
|
||||||
var vkAdminTokenPref = findPreference("vk_admin_token");
|
var vkAdminTokenPref = findPreference("vk_admin_token");
|
||||||
vkAdminTokenPref.setVisible(Preferences.getPreferences().getBoolean("new_music_downloading_way", false));
|
vkAdminTokenPref.setVisible(Preferences.getPreferences().getBoolean("new_music_downloading_way", false));
|
||||||
if (vkAdminTokenPref.isVisible()) {
|
if (vkAdminTokenPref.isVisible()) {
|
||||||
@@ -148,7 +128,6 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void copyText(String src) {
|
private void copyText(String src) {
|
||||||
var manager = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
var manager = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
manager.setPrimaryClip(ClipData.newPlainText("MBH-ST", src));
|
manager.setPrimaryClip(ClipData.newPlainText("MBH-ST", src));
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Context;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
|
import ru.vtosters.hooks.GmsHook;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
||||||
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
||||||
@@ -46,6 +47,11 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
boolean isEMUI = OEMDetector.isEMUI();
|
boolean isEMUI = OEMDetector.isEMUI();
|
||||||
boolean hasMiuiIncrCode = OEMDetector.hasMiuiIncrCode();
|
boolean hasMiuiIncrCode = OEMDetector.hasMiuiIncrCode();
|
||||||
|
|
||||||
|
boolean hasGMS = GmsHook.isGmsInstalled();
|
||||||
|
boolean hasVancedMicroG = GmsHook.isFakeGmsInstalled();
|
||||||
|
boolean hasReVancedMicroG = GmsHook.isFakeGms2Installed();
|
||||||
|
String GMSPackageName = hasGMS ? "com.google.android.gms" : hasVancedMicroG ? "com.mgoogle.android.gms" : hasReVancedMicroG ? "app.revanced.android.gms" : "no gms or microg";
|
||||||
|
|
||||||
boolean isValidSignature = Preferences.isValidSignature();
|
boolean isValidSignature = Preferences.isValidSignature();
|
||||||
boolean isTablet = AndroidUtils.isTablet();
|
boolean isTablet = AndroidUtils.isTablet();
|
||||||
boolean isDebuggable = AndroidUtils.isDebuggable();
|
boolean isDebuggable = AndroidUtils.isDebuggable();
|
||||||
@@ -76,6 +82,12 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Google Market Services package", GMSPackageName, 0, preference -> {
|
||||||
|
((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", GMSPackageName));
|
||||||
|
AndroidUtils.sendToast(requireContext().getString(R.string.copied_to_clipboard));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Valid Signature", "Value: " + isValidSignature, 0, null);
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Valid Signature", "Value: " + isValidSignature, 0, null);
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isTablet", "Value: " + isTablet, 0, null);
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isTablet", "Value: " + isTablet, 0, null);
|
||||||
@@ -90,6 +102,12 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledGoogleGMS", "Value: " + hasGMS, 0, null);
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledVancedMicroG", "Value: " + hasVancedMicroG, 0, null);
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledReVancedMicroG", "Value: " + hasReVancedMicroG, 0, null);
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Product Name", productName, 0, preference -> {
|
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Product Name", productName, 0, preference -> {
|
||||||
((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", productName));
|
((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", productName));
|
||||||
AndroidUtils.sendToast(requireContext().getString(R.string.copied_to_clipboard));
|
AndroidUtils.sendToast(requireContext().getString(R.string.copied_to_clipboard));
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import com.vtosters.lite.R;
|
|||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.hooks.other.ThemesUtils;
|
import ru.vtosters.hooks.other.ThemesUtils;
|
||||||
import ru.vtosters.lite.concurrent.VTExecutors;
|
import ru.vtosters.lite.concurrent.VTExecutors;
|
||||||
import ru.vtosters.lite.themes.ThemesCore;
|
|
||||||
import ru.vtosters.lite.themes.ThemesManager;
|
import ru.vtosters.lite.themes.ThemesManager;
|
||||||
import ru.vtosters.lite.themes.palettes.PalettesManager;
|
import ru.vtosters.lite.themes.palettes.PalettesManager;
|
||||||
import ru.vtosters.lite.ui.components.DockBarEditorManager;
|
import ru.vtosters.lite.ui.components.DockBarEditorManager;
|
||||||
@@ -43,15 +42,15 @@ public class ThemesFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
|
|
||||||
findPreference("systememoji").setSummary(AndroidUtils.getGlobalContext().getString(R.string.systememojisum) + " \uD83D\uDE00\uD83D\uDE01\uD83E\uDD11\uD83E\uDD75\uD83D\uDC4D");
|
findPreference("systememoji").setSummary(AndroidUtils.getGlobalContext().getString(R.string.systememojisum) + " \uD83D\uDE00\uD83D\uDE01\uD83E\uDD11\uD83E\uDD75\uD83D\uDC4D");
|
||||||
|
|
||||||
findPreference("useNewColorEngine").setVisible(Preferences.dev());
|
|
||||||
|
|
||||||
var invalidateThemeCache = findPreference("invalidate_theme_cache");
|
var invalidateThemeCache = findPreference("invalidate_theme_cache");
|
||||||
if (ThemesUtils.getReservedAccent() != Color.TRANSPARENT && Preferences.dev() && ThemesUtils.useNewColorEngine())
|
if (ThemesUtils.getReservedAccent() != Color.TRANSPARENT && Preferences.dev()) {
|
||||||
invalidateThemeCache.setOnPreferenceClickListener(preference -> {
|
invalidateThemeCache.setOnPreferenceClickListener(preference -> {
|
||||||
setAccentColor(ThemesUtils.getReservedAccent());
|
setAccentColor(ThemesUtils.getReservedAccent());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
else invalidateThemeCache.setVisible(false);
|
} else {
|
||||||
|
invalidateThemeCache.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
var navBarPreference = findPreference("navbar");
|
var navBarPreference = findPreference("navbar");
|
||||||
navBarPreference.setOnPreferenceClickListener(preference -> {
|
navBarPreference.setOnPreferenceClickListener(preference -> {
|
||||||
@@ -156,12 +155,8 @@ public class ThemesFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.reset, (dialog, which) -> {
|
.setNegativeButton(R.string.reset, (dialog, which) -> {
|
||||||
if (ThemesUtils.useNewColorEngine()) {
|
ThemesManager.deleteModification();
|
||||||
ThemesManager.deleteModification();
|
ThemesUtils.reserveAccentColor(Color.TRANSPARENT, false);
|
||||||
ThemesUtils.reserveAccentColor(Color.TRANSPARENT, false);
|
|
||||||
} else {
|
|
||||||
ThemesUtils.setCustomAccentColor(0, false);
|
|
||||||
}
|
|
||||||
restart();
|
restart();
|
||||||
})
|
})
|
||||||
.setPositiveButton(R.string.cancel, null)
|
.setPositiveButton(R.string.cancel, null)
|
||||||
@@ -207,35 +202,29 @@ public class ThemesFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setAccentColor(int color) {
|
void setAccentColor(int color) {
|
||||||
if (ThemesUtils.useNewColorEngine()) {
|
final VKProgressDialog dialog = new VKProgressDialog(requireContext());
|
||||||
final VKProgressDialog dialog = new VKProgressDialog(requireContext());
|
dialog.setCancelable(false);
|
||||||
dialog.setCancelable(false);
|
dialog.setMessage(AndroidUtils.getString("applying_accent") + "...");
|
||||||
dialog.setMessage(AndroidUtils.getString("applying_accent") + "...");
|
dialog.show();
|
||||||
dialog.show();
|
|
||||||
|
|
||||||
VTExecutors.getIoExecutor().execute(() -> {
|
VTExecutors.getIoExecutor().execute(() -> {
|
||||||
try {
|
try {
|
||||||
ThemesUtils.reserveAccentColor(color, true);
|
ThemesUtils.reserveAccentColor(color, true);
|
||||||
ThemesManager.generateModApk(color);
|
ThemesManager.generateModApk(color);
|
||||||
requireActivity().runOnUiThread(this::restart);
|
requireActivity().runOnUiThread(this::restart);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e("ThemesFragment", e.getMessage());
|
Log.e("ThemesFragment", e.getMessage());
|
||||||
ThemesManager.deleteModification();
|
ThemesManager.deleteModification();
|
||||||
requireActivity().runOnUiThread(() -> {
|
requireActivity().runOnUiThread(() -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
new VkAlertDialog.Builder(requireContext())
|
new VkAlertDialog.Builder(requireContext())
|
||||||
.setTitle(AndroidUtils.getString("error"))
|
.setTitle(AndroidUtils.getString("error"))
|
||||||
.setMessage(AndroidUtils.getString("error_applying_accent") + ":\n" + e)
|
.setMessage(AndroidUtils.getString("error_applying_accent") + ":\n" + e)
|
||||||
.setPositiveButton("OK", null)
|
.setPositiveButton("OK", null)
|
||||||
.show();
|
.show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
ThemesUtils.setCustomAccentColor(color, false);
|
|
||||||
ThemesCore.setThemedColors(color);
|
|
||||||
restart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void restart() {
|
void restart() {
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
package ru.vtosters.lite.ui.fragments;
|
package ru.vtosters.lite.ui.fragments;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.provider.Settings;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import com.aefyr.tsg.g2.TelegramStickersPack;
|
|
||||||
import com.aefyr.tsg.g2.TelegramStickersService;
|
import com.aefyr.tsg.g2.TelegramStickersService;
|
||||||
import com.vk.about.AboutAppFragment;
|
import com.vk.about.AboutAppFragment;
|
||||||
import com.vk.balance.BalanceFragment;
|
import com.vk.balance.BalanceFragment;
|
||||||
|
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||||
import com.vk.identity.fragments.IdentityListFragment;
|
import com.vk.identity.fragments.IdentityListFragment;
|
||||||
import com.vk.medianative.MediaImageEncoder;
|
import com.vk.medianative.MediaImageEncoder;
|
||||||
import com.vk.notifications.settings.NotificationsSettingsFragment;
|
import com.vk.notifications.settings.NotificationsSettingsFragment;
|
||||||
@@ -35,6 +43,7 @@ import ru.vtosters.lite.BuildConfig;
|
|||||||
import ru.vtosters.lite.concurrent.VTExecutors;
|
import ru.vtosters.lite.concurrent.VTExecutors;
|
||||||
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
||||||
import ru.vtosters.lite.ssfs.Utils;
|
import ru.vtosters.lite.ssfs.Utils;
|
||||||
|
import ru.vtosters.lite.themes.utils.RecolorUtils;
|
||||||
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
||||||
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
||||||
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
|
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
|
||||||
@@ -42,7 +51,17 @@ import ru.vtosters.lite.utils.*;
|
|||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class VTSettings extends TrackedMaterialPreferenceToolbarFragment implements TelegramStickersService.StickersEventsListener {
|
public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
||||||
|
public static String ACTION_INVALIDATE_TGS_COUNT = "com.vtosters.lite.intent.action.INVALIDATE_TGS_COUNT";
|
||||||
|
|
||||||
|
BroadcastReceiver mTgsReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (ACTION_INVALIDATE_TGS_COUNT.equals(intent.getAction())) {
|
||||||
|
findPreference("tgs_stickers").setSummary(getTGSsumm());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public static String getValAsString(@StringRes int strRes, Boolean value) {
|
public static String getValAsString(@StringRes int strRes, Boolean value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -76,46 +95,21 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
ThemesUtils.setTheme(isDarkTheme ? ThemesUtils.getDarkTheme() : ThemesUtils.getLightTheme(), requireActivity(), true);
|
ThemesUtils.setTheme(isDarkTheme ? ThemesUtils.getDarkTheme() : ThemesUtils.getLightTheme(), requireActivity(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
public void onPackAdded(TelegramStickersPack pack, int atIndex) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPackRemoved(TelegramStickersPack pack, int atIndex) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPackChanged(TelegramStickersPack pack, int atIndex) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPackDownloadError(TelegramStickersPack pack, Exception error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivePacksListChanged() {
|
|
||||||
findPreference("tgs_stickers").setSummary(getTGSsumm());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInactivePacksListChanged() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
TelegramStickersService.getInstance(requireContext()).addStickersEventsListener(this);
|
requireContext().registerReceiver(mTgsReceiver, new IntentFilter(ACTION_INVALIDATE_TGS_COUNT));
|
||||||
|
|
||||||
this.addPreferencesFromResource(R.xml.empty);
|
this.addPreferencesFromResource(R.xml.empty);
|
||||||
|
|
||||||
AndroidUtils.checkLinksVerified(this.requireActivity());
|
PowerManager manager = (PowerManager) requireActivity().getSystemService(Context.POWER_SERVICE);
|
||||||
|
boolean isLinksUnverified = AndroidUtils.isLinksUnverified(requireActivity());
|
||||||
|
boolean isDozingAvailable = Build.VERSION.SDK_INT >= 23 && !manager.isIgnoringBatteryOptimizations(AndroidUtils.getPackageName());
|
||||||
|
boolean areNotificationsDisabled = !AndroidUtils.areNotificationsEnabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||||
|
boolean isGMSNotInstalled = !GmsHook.isAnyServicesInstalled();
|
||||||
|
|
||||||
if (AccountManagerUtils.isLogin()) {
|
if (AccountManagerUtils.isLogin()) {
|
||||||
Preference accountSwitcher = PreferenceFragmentUtils.addPreference(
|
Preference accountSwitcher = PreferenceFragmentUtils.addPreference(
|
||||||
@@ -128,6 +122,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
try {
|
try {
|
||||||
VKAuth.a("logout", false);
|
VKAuth.a("logout", false);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = new Intent(requireContext(), MainActivity.class)
|
Intent intent = new Intent(requireContext(), MainActivity.class)
|
||||||
@@ -147,18 +142,20 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(
|
if (Preferences.hasSpecialVerif()) {
|
||||||
getPreferenceScreen(),
|
PreferenceFragmentUtils.addPreference(
|
||||||
"",
|
getPreferenceScreen(),
|
||||||
requireContext().getString(R.string.vtssfs),
|
"",
|
||||||
getSSFSsumm(),
|
requireContext().getString(R.string.vtssfs),
|
||||||
R.drawable.ic_link_circle_outline_28,
|
"",
|
||||||
preference -> {
|
R.drawable.ic_link_circle_outline_28,
|
||||||
VKUIwrapper.setLink(Utils.getVKUILink());
|
preference -> {
|
||||||
NavigatorUtils.switchFragment(requireContext(), VKUIwrapper.class);
|
VKUIwrapper.setLink(Utils.getVKUILink());
|
||||||
return false;
|
NavigatorUtils.switchFragment(requireContext(), VKUIwrapper.class);
|
||||||
}
|
return false;
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), R.string.appearance_theme_use_system);
|
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), R.string.appearance_theme_use_system);
|
||||||
@@ -175,8 +172,8 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
AndroidUtils.sendToast(AndroidUtils.getString("systemtheme_enabled"));
|
AndroidUtils.sendToast(AndroidUtils.getString("systemtheme_enabled"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final MaterialSwitchPreference switchPreference = (MaterialSwitchPreference) preference;
|
MaterialSwitchPreference switchPreference = (MaterialSwitchPreference) preference;
|
||||||
final boolean isDarkTheme = !switchPreference.isChecked();
|
boolean isDarkTheme = !switchPreference.isChecked();
|
||||||
switchTheme(isDarkTheme);
|
switchTheme(isDarkTheme);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -191,27 +188,89 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
R.drawable.ic_palette_outline_28,
|
R.drawable.ic_palette_outline_28,
|
||||||
Preferences.systemtheme(),
|
Preferences.systemtheme(),
|
||||||
(preference, o) -> {
|
(preference, o) -> {
|
||||||
Preferences.getPreferences().edit().putBoolean("system_theme", (boolean) o).commit();
|
Preferences.getPreferences().edit().putBoolean("system_theme", (boolean) o).apply();
|
||||||
SystemThemeChangerHook.onThemeChanged(requireActivity().getResources().getConfiguration());
|
SystemThemeChangerHook.onThemeChanged(requireActivity().getResources().getConfiguration());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!GmsHook.isAnyServicesInstalled()) {
|
if ((isLinksUnverified || isDozingAvailable || areNotificationsDisabled || isGMSNotInstalled) && !Preferences.getBoolValue("dialogrecomm", false)) {
|
||||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), R.string.gmsname);
|
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Рекомендации");
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(
|
if (isGMSNotInstalled) {
|
||||||
getPreferenceScreen(),
|
PreferenceFragmentUtils.addPreference(
|
||||||
"",
|
getPreferenceScreen(),
|
||||||
requireContext().getString(R.string.installgms),
|
"",
|
||||||
"",
|
requireContext().getString(R.string.installgms),
|
||||||
R.drawable.ic_about_outline_28,
|
"Отсутствие этих сервисов приводит к поломке фоновых уведомлений и проблемам работы компонентов приложения",
|
||||||
preference -> {
|
RecolorUtils.recolorDrawable(R.drawable.ic_logo_google_28, ThemesUtils.getColor(R.color.red)),
|
||||||
NavigatorUtils.switchFragment(requireContext(), InstallGMSFragment.class);
|
preference -> {
|
||||||
return false;
|
NavigatorUtils.switchFragment(requireContext(), InstallGMSFragment.class);
|
||||||
}
|
return false;
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLinksUnverified) {
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
"Не выбраны ссылки для открытия приложением",
|
||||||
|
"Это помешает открытию внешних ссылок для их открытия с помощью этого приложения\n\nВ некоторых случаях необходимо отключить открытие ссылок официальным приложениям ВКонтакте",
|
||||||
|
RecolorUtils.recolorDrawable(R.drawable.ic_linked_outline_28, ThemesUtils.getColor(R.color.red)),
|
||||||
|
preference -> {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, Uri.parse("package:" + AndroidUtils.getPackageName()));
|
||||||
|
requireActivity().startActivity(intent);
|
||||||
|
} catch (Throwable t1) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + AndroidUtils.getPackageName()));
|
||||||
|
requireActivity().startActivity(intent);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDozingAvailable) {
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
"Отключите экономию батареи",
|
||||||
|
"Экономия батареи мешает получению фоновых сообщений, уведомлений и работе музыки" + (OEMDetector.isMIUI() ? "\n\nДля устройств Xiaomi необходимо вручную включить в настройках системы пункт Автозапуск данному приложению" : ""),
|
||||||
|
RecolorUtils.recolorDrawable(R.drawable.ic_filter_outline_28, ThemesUtils.getColor(R.color.red)),
|
||||||
|
preference -> {
|
||||||
|
@SuppressLint("BatteryLife")
|
||||||
|
var intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.setData(Uri.parse("package:" + AndroidUtils.getPackageName()));
|
||||||
|
requireActivity().startActivity(intent);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (areNotificationsDisabled) {
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
"Включите уведомления",
|
||||||
|
"Без включенных уведомлений не получится получать сообщения и многое другое",
|
||||||
|
RecolorUtils.recolorDrawable(R.drawable.ic_notifications_outline_28, ThemesUtils.getColor(R.color.red)),
|
||||||
|
preference -> {
|
||||||
|
@SuppressLint("BatteryLife")
|
||||||
|
var intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra(Settings.EXTRA_APP_PACKAGE, AndroidUtils.getPackageName());
|
||||||
|
requireActivity().startActivity(intent);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Preferences.devmenu()) {
|
if (Preferences.devmenu()) {
|
||||||
@@ -456,7 +515,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
getPreferenceScreen(),
|
getPreferenceScreen(),
|
||||||
"",
|
"",
|
||||||
requireContext().getString(R.string.vtlinterface),
|
requireContext().getString(R.string.vtlinterface),
|
||||||
getValAsString(R.string.showstories, Preferences.stories()),
|
getValAsString(R.string.show_superapps_title, Preferences.superapp()),
|
||||||
R.drawable.ic_interface_outline_28,
|
R.drawable.ic_interface_outline_28,
|
||||||
preference -> {
|
preference -> {
|
||||||
NavigatorUtils.switchFragment(requireContext(), InterfaceFragment.class);
|
NavigatorUtils.switchFragment(requireContext(), InterfaceFragment.class);
|
||||||
@@ -464,6 +523,18 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"currcache",
|
||||||
|
AndroidUtils.getString(R.string.datasettings_title),
|
||||||
|
getString(com.vtosters.lite.R.string.cache_size_summ) + ": " + CacheUtils.humanReadableByteCountBin(IOUtils.getDirSize(AndroidUtils.getGlobalContext().getCacheDir())),
|
||||||
|
R.drawable.ic_document_outline_28,
|
||||||
|
preference -> {
|
||||||
|
NavigatorUtils.switchFragment(requireContext(), DataSettingsFragment.class);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(
|
PreferenceFragmentUtils.addPreference(
|
||||||
getPreferenceScreen(),
|
getPreferenceScreen(),
|
||||||
"",
|
"",
|
||||||
@@ -496,14 +567,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
AndroidUtils.getString("currentLanguage") + " " + AndroidUtils.upString(Locale.getDefault().getDisplayLanguage()),
|
AndroidUtils.getString("currentLanguage") + " " + AndroidUtils.upString(Locale.getDefault().getDisplayLanguage()),
|
||||||
R.drawable.ic_globe_outline_28,
|
R.drawable.ic_globe_outline_28,
|
||||||
preference -> {
|
preference -> {
|
||||||
try {
|
startActivity(new Intent("android.settings.APP_LOCALE_SETTINGS", Uri.parse("package:" + AndroidUtils.getPackageName())));
|
||||||
Intent intent = new Intent("android.settings.APP_LOCALE_SETTINGS", Uri.parse("package:" + AndroidUtils.getPackageName()));
|
|
||||||
startActivity(intent);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
AndroidUtils.sendToast("Ваша система не поддерживает смену языка приложения");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -535,18 +599,57 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (AndroidUtils.isAdbOrDeveloperOptionsEnabled(requireContext().getContentResolver())) {
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
requireContext().getString(R.string.opencommit),
|
||||||
|
"",
|
||||||
|
R.drawable.ic_link_outline_28,
|
||||||
|
preference -> {
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(AboutHook.getCommitLink())));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(
|
PreferenceFragmentUtils.addPreference(
|
||||||
getPreferenceScreen(),
|
getPreferenceScreen(),
|
||||||
"",
|
"",
|
||||||
requireContext().getString(R.string.opencommit),
|
"Наши чаты",
|
||||||
"",
|
"Активные чаты по обсуждению модификации и многому другому",
|
||||||
R.drawable.ic_link_outline_28,
|
R.drawable.ic_message_outline_28,
|
||||||
preference -> {
|
preference -> {
|
||||||
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(AboutHook.getCommitLink())));
|
var args = new Bundle();
|
||||||
|
var dialog = new AboutInformation();
|
||||||
|
|
||||||
|
args.putString("vk_link", "https://vk.me/join/jKE1bIZczHTF2mLkuoiQcd0qIBYgHuGFPRA=");
|
||||||
|
args.putString("tg_link", "https://t.me/vtosterschat");
|
||||||
|
|
||||||
|
dialog.setArguments(args);
|
||||||
|
dialog.show(requireFragmentManager(), "");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"",
|
||||||
|
"Наши сообщества",
|
||||||
|
"Самая свежая информация об обновлениях и не только!",
|
||||||
|
R.drawable.users_3_outline_28,
|
||||||
|
preference -> {
|
||||||
|
var args = new Bundle();
|
||||||
|
var dialog = new AboutInformation();
|
||||||
|
|
||||||
|
args.putString("vk_link", "https://vk.com/vtosters_official");
|
||||||
|
args.putString("tg_link", "https://t.me/vtosters");
|
||||||
|
|
||||||
|
dialog.setArguments(args);
|
||||||
|
dialog.show(requireFragmentManager(), "");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreference(
|
PreferenceFragmentUtils.addPreference(
|
||||||
getPreferenceScreen(),
|
getPreferenceScreen(),
|
||||||
@@ -582,16 +685,40 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
"",
|
"",
|
||||||
R.drawable.ic_download_outline_28,
|
R.drawable.ic_download_outline_28,
|
||||||
preference -> {
|
preference -> {
|
||||||
OTADialog.checkUpdates(getActivity());
|
OTADialog.checkUpdatesManual(getActivity());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
PreferenceFragmentUtils.addMaterialSwitchPreference(
|
||||||
|
getPreferenceScreen(),
|
||||||
|
"autoupdates",
|
||||||
|
"Автоматическое обновление",
|
||||||
|
"Получать уведомление о выходе новой версии приложения",
|
||||||
|
R.drawable.ic_history_outline_28,
|
||||||
|
true,
|
||||||
|
(preference, o) -> {
|
||||||
|
Preferences.getPreferences().edit().putBoolean("autoupdates", (boolean) o).apply();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
requireContext().unregisterReceiver(mTgsReceiver);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
updateCacheSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCacheSize() {
|
||||||
|
findPreference("currcache").setSummary(getString(com.vtosters.lite.R.string.cache_size_summ) + ": " + CacheUtils.humanReadableByteCountBin(IOUtils.getDirSize(AndroidUtils.getGlobalContext().getCacheDir())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -599,9 +726,24 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment impleme
|
|||||||
return R.string.notification_settings;
|
return R.string.notification_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static class AboutInformation extends DialogFragment {
|
||||||
public void onDestroyView() {
|
@Override
|
||||||
super.onDestroyView();
|
public AlertDialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
TelegramStickersService.getInstance(requireContext()).removeStickersEventsListener(this);
|
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(requireActivity());
|
||||||
|
|
||||||
|
String[] items = {AndroidUtils.getString(R.string.app_name_alter), "Telegram"};
|
||||||
|
|
||||||
|
builder.setItems(items, (dialog, which) -> {
|
||||||
|
switch (which) {
|
||||||
|
case 0 ->
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(getArguments().getString("vk_link"))));
|
||||||
|
case 1 ->
|
||||||
|
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(getArguments().getString("tg_link"))));
|
||||||
|
}
|
||||||
|
dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+118
-171
@@ -20,7 +20,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||||
import com.aefyr.tsg.g2.TelegramStickersService;
|
import com.aefyr.tsg.g2.TelegramStickersService;
|
||||||
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber;
|
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
||||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.hooks.other.ThemesUtils;
|
import ru.vtosters.hooks.other.ThemesUtils;
|
||||||
@@ -29,63 +28,65 @@ import ru.vtosters.lite.ui.adapters.StickerPackAdapter;
|
|||||||
import ru.vtosters.lite.ui.components.StickerTouchHelperCallback;
|
import ru.vtosters.lite.ui.components.StickerTouchHelperCallback;
|
||||||
import ru.vtosters.lite.ui.fragments.BaseToolbarFragment;
|
import ru.vtosters.lite.ui.fragments.BaseToolbarFragment;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
import ru.vtosters.lite.utils.NavigatorUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public class StickersFragment extends BaseToolbarFragment {
|
public class StickersFragment extends BaseToolbarFragment {
|
||||||
public final static String ACTION_RELOAD = "com.vtosters.lite.action.RELOAD_TGS_LIST";
|
public static String ACTION_RELOAD = "com.vtosters.lite.action.RELOAD_TGS_LIST";
|
||||||
|
TelegramStickersGrabber mGrabber;
|
||||||
private final static int TYPE_DIRECT = 0, TYPE_SOCKS = 2;
|
StickerPackAdapter mAdapter;
|
||||||
public int from;
|
boolean movePending = false;
|
||||||
public int to;
|
int from, to;
|
||||||
private TelegramStickersGrabber mGrabber;
|
BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||||
private TelegramStickersService mService;
|
|
||||||
private StickerPackAdapter mAdapter;
|
|
||||||
private boolean movePending = false;
|
|
||||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (intent.getAction().equals(ACTION_RELOAD)) {
|
if (!intent.getAction().equals(ACTION_RELOAD) || mAdapter == null) return;
|
||||||
if (mAdapter != null) {
|
try {
|
||||||
try {
|
if (movePending) {
|
||||||
if (movePending) {
|
movePending = false;
|
||||||
movePending = false;
|
mAdapter.notifyItemMoved(from, to);
|
||||||
mAdapter.notifyItemMoved(from, to);
|
from = to = -1;
|
||||||
from = -1;
|
} else mAdapter.notifyDataSetChanged();
|
||||||
to = -1;
|
} catch (Exception e) {
|
||||||
} else {
|
e.printStackTrace();
|
||||||
mAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static void resetToken(Context ctx) {
|
||||||
|
if (TGPref.getTGBotKey() == null) AndroidUtils.sendToast("Токен отсутствует");
|
||||||
|
new VkAlertDialog.Builder(ctx)
|
||||||
|
.setTitle("Сбросить токен бота?")
|
||||||
|
.setNegativeButton(R.string.cancel, (dialog, which) -> {
|
||||||
|
dialog.cancel();
|
||||||
|
})
|
||||||
|
.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||||
|
TGPref.setTGBotKey(null);
|
||||||
|
AndroidUtils.sendToast("Токен бота сброшен");
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreateMenu(Menu menu) {
|
protected void onCreateMenu(Menu menu) {
|
||||||
MenuItem item = menu.add(0, 0, 0, "");
|
menu.add(0, 0, 0, "")
|
||||||
item.setIcon(ThemesUtils.recolorDrawable(AndroidUtils.getGlobalContext().getDrawable(R.drawable.ic_settings_outline_28)))
|
.setIcon(ThemesUtils.recolorDrawable(requireContext().getDrawable(R.drawable.ic_add_outline_28)))
|
||||||
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
menu.add(0, 1, 0, "")
|
||||||
|
.setIcon(ThemesUtils.recolorDrawable(requireContext().getDrawable(R.drawable.ic_refresh_outline_28)))
|
||||||
|
.setTitle("Сбросить токен бота")
|
||||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
|
||||||
super.onCreateMenu(menu);
|
super.onCreateMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem arg0) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
openMenu(null);
|
switch (item.getItemId()) {
|
||||||
return true;
|
case 0 -> addTgsPack();
|
||||||
}
|
case 1 -> resetToken(requireContext());
|
||||||
|
|
||||||
private void openMenu(String toast) {
|
|
||||||
if (toast != null) {
|
|
||||||
Toast.makeText(requireContext(), toast, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
NavigatorUtils.switchFragment(requireContext(), StickersPreferencesFragment.class);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -94,40 +95,28 @@ public class StickersFragment extends BaseToolbarFragment {
|
|||||||
requireContext().registerReceiver(mReceiver, new IntentFilter(ACTION_RELOAD));
|
requireContext().registerReceiver(mReceiver, new IntentFilter(ACTION_RELOAD));
|
||||||
|
|
||||||
mGrabber = new TelegramStickersGrabber(TGPref.getTGBotKey());
|
mGrabber = new TelegramStickersGrabber(TGPref.getTGBotKey());
|
||||||
mService = TelegramStickersService.getInstance(requireContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContent(@NonNull LayoutInflater inflater, @Nullable Bundle bundle) {
|
public View onCreateContent(@NonNull LayoutInflater inflater, @Nullable Bundle bundle) {
|
||||||
setTitle(R.string.vtltgs);
|
setTitle(R.string.vtltgs);
|
||||||
|
|
||||||
FrameLayout layout = new FrameLayout(requireContext());
|
var container = new FrameLayout(requireContext());
|
||||||
|
var recycler = new RecyclerView(requireContext());
|
||||||
|
|
||||||
mAdapter = new StickerPackAdapter();
|
mAdapter = new StickerPackAdapter();
|
||||||
RecyclerView mRecycler = new RecyclerView(requireContext());
|
recycler.setAdapter(mAdapter);
|
||||||
|
|
||||||
StickerTouchHelperCallback callback = new StickerTouchHelperCallback(mAdapter);
|
var callback = new StickerTouchHelperCallback(mAdapter);
|
||||||
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
|
var touchHelper = new ItemTouchHelper(callback);
|
||||||
touchHelper.attachToRecyclerView(mRecycler);
|
touchHelper.attachToRecyclerView(recycler);
|
||||||
|
|
||||||
mRecycler.setAdapter(mAdapter);
|
recycler.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
|
||||||
mRecycler.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
|
container.addView(recycler, new ViewGroup.LayoutParams(-1, -1));
|
||||||
layout.addView(mRecycler, new ViewGroup.LayoutParams(-1, -1));
|
|
||||||
|
|
||||||
FloatingActionButton mAddStickerPack = new FloatingActionButton(requireContext());
|
return container;
|
||||||
mAddStickerPack.setImageResource(R.drawable.ic_add_24);
|
|
||||||
mAddStickerPack.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
|
|
||||||
mAddStickerPack.setOnClickListener(v2 -> fabClick());
|
|
||||||
|
|
||||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(-2, -2);
|
|
||||||
params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
|
|
||||||
params.setMargins(0, 0, 0, AndroidUtils.dp2px(12f));
|
|
||||||
layout.addView(mAddStickerPack, params);
|
|
||||||
|
|
||||||
return layout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View arg0, Bundle arg1) {
|
public void onViewCreated(View arg0, Bundle arg1) {
|
||||||
super.onViewCreated(arg0, arg1);
|
super.onViewCreated(arg0, arg1);
|
||||||
@@ -139,113 +128,81 @@ public class StickersFragment extends BaseToolbarFragment {
|
|||||||
requireContext().unregisterReceiver(mReceiver);
|
requireContext().unregisterReceiver(mReceiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fabClick() {
|
private void addTgsPack() {
|
||||||
int method = TGPref.getTGConnectMethod();
|
|
||||||
if (method == -1) {
|
|
||||||
openMenu(requireContext().getString(R.string.stickersconnection));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
initGrabber();
|
initGrabber();
|
||||||
if (method == TYPE_SOCKS) {
|
Runnable lambda = () -> {
|
||||||
if (TGPref.getTGProxyIP() == null) {
|
var container = new LinearLayout(requireContext());
|
||||||
openMenu(requireContext().getString(R.string.stickersproxy1));
|
|
||||||
}
|
|
||||||
} else if (method == TYPE_DIRECT) {
|
|
||||||
final Runnable callback = () -> {
|
|
||||||
LinearLayout linearLayout = new LinearLayout(requireContext());
|
|
||||||
|
|
||||||
final EditText editText = new EditText(requireContext());
|
var input = new EditText(requireContext());
|
||||||
editText.setHintTextColor(ThemesUtils.getSTextAttr());
|
var inputLP = new ViewGroup.MarginLayoutParams(-1, -2);
|
||||||
editText.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
|
inputLP.setMargins(
|
||||||
|
AndroidUtils.dp2px(24.f),
|
||||||
|
0,
|
||||||
|
AndroidUtils.dp2px(24.f),
|
||||||
|
0);
|
||||||
|
container.addView(input, inputLP);
|
||||||
|
|
||||||
linearLayout.addView(editText);
|
new VkAlertDialog.Builder(requireContext())
|
||||||
editText.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
|
.setTitle(R.string.stickershelp1)
|
||||||
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) editText.getLayoutParams());
|
// .setMessage(R.string.stickershelp2)
|
||||||
margin.setMargins(AndroidUtils.dp2px(24f), 0, AndroidUtils.dp2px(24f), 0);
|
.setView(container)
|
||||||
editText.setLayoutParams(margin);
|
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||||
|
{
|
||||||
|
var packId = input.getText().toString();
|
||||||
|
if (packId.startsWith("https://"))
|
||||||
|
packId = packId.substring(packId.lastIndexOf('/') + 1);
|
||||||
|
TelegramStickersService.getInstance(requireContext())
|
||||||
|
.requestPackDownload(
|
||||||
|
packId,
|
||||||
|
new File(requireContext().getFilesDir(), "VT-Stickers/" + packId));
|
||||||
|
})
|
||||||
|
.setNeutralButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
|
||||||
|
.show();
|
||||||
|
};
|
||||||
|
|
||||||
new VkAlertDialog.Builder(requireContext())
|
if (TGPref.getTGBotKey() == null) enterBotKey(() -> checkApiKey(lambda));
|
||||||
.setTitle(requireContext().getString(R.string.stickershelp1))
|
else checkApiKey(lambda);
|
||||||
.setMessage(requireContext().getString(R.string.stickershelp2))
|
|
||||||
.setView(linearLayout)
|
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
||||||
String pack = editText.getText().toString();
|
|
||||||
if (!pack.startsWith("https://")) {
|
|
||||||
pack = "https://" + pack;
|
|
||||||
} else if (!pack.contains("addstickers")) {
|
|
||||||
AndroidUtils.sendToast(AndroidUtils.getString("invalid_pack_link"));
|
|
||||||
}
|
|
||||||
pack = parsePack(pack);
|
|
||||||
mService.requestPackDownload(pack, new File(requireContext().getFilesDir(), new File("VT-Stickers", pack).getAbsolutePath()));
|
|
||||||
})
|
|
||||||
.setNeutralButton(android.R.string.cancel,
|
|
||||||
(dialog, which) -> dialog.dismiss()
|
|
||||||
)
|
|
||||||
.show();
|
|
||||||
};
|
|
||||||
if (TGPref.getTGBotKey() == null) enterBotKey(() -> checkApiKey(callback));
|
|
||||||
else checkApiKey(callback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initGrabber() {
|
private void initGrabber() {
|
||||||
int method = TGPref.getTGConnectMethod();
|
|
||||||
if (method == TYPE_DIRECT) {
|
|
||||||
TelegramStickersGrabber.USE_PROXY = false;
|
|
||||||
mGrabber.resetProxy();
|
|
||||||
} else if (method == TYPE_SOCKS) {
|
|
||||||
if (TGPref.getTGProxyIP() != null) {
|
|
||||||
TelegramStickersGrabber.USE_PROXY = true;
|
|
||||||
TelegramStickersGrabber.PROXY_IP = TGPref.getTGProxyIP();
|
|
||||||
TelegramStickersGrabber.PROXY_PORT = TGPref.getTGProxyPort();
|
|
||||||
if (TGPref.isTGProxyPassEnabled()) {
|
|
||||||
TelegramStickersGrabber.HAS_PASS = true;
|
|
||||||
TelegramStickersGrabber.PROXY_USER = TGPref.getTGProxyUser();
|
|
||||||
TelegramStickersGrabber.PROXY_PASS = TGPref.getTGProxyPass();
|
|
||||||
}
|
|
||||||
mGrabber.enableProxy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TelegramStickersGrabber.updateURLs();
|
TelegramStickersGrabber.updateURLs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enterBotKey(Runnable r) {
|
private void enterBotKey(Runnable callback) {
|
||||||
LinearLayout linearLayout = new LinearLayout(requireContext());
|
var container = new LinearLayout(requireContext());
|
||||||
|
|
||||||
final EditText editText = new EditText(requireContext());
|
var input = new EditText(requireContext());
|
||||||
editText.setHintTextColor(ThemesUtils.getSTextAttr());
|
var inputLP = new ViewGroup.MarginLayoutParams(-1, -2);
|
||||||
editText.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
|
inputLP.setMargins(
|
||||||
|
AndroidUtils.dp2px(24.f),
|
||||||
linearLayout.addView(editText);
|
0,
|
||||||
editText.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
|
AndroidUtils.dp2px(24.f),
|
||||||
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) editText.getLayoutParams());
|
0);
|
||||||
margin.setMargins(AndroidUtils.dp2px(24f), 0, AndroidUtils.dp2px(24f), 0);
|
container.addView(input, inputLP);
|
||||||
editText.setLayoutParams(margin);
|
|
||||||
|
|
||||||
new VkAlertDialog.Builder(requireContext())
|
new VkAlertDialog.Builder(requireContext())
|
||||||
.setTitle(requireContext().getString(R.string.stickersapi5))
|
.setTitle(requireContext().getString(R.string.stickersapi5))
|
||||||
.setMessage(requireContext().getString(R.string.stickersapi6))
|
.setMessage(requireContext().getString(R.string.stickersapi6))
|
||||||
.setView(linearLayout)
|
.setView(container)
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||||
TGPref.setTGBotKey(editText.getText().toString());
|
{
|
||||||
if (r != null) r.run();
|
TGPref.setTGBotKey(input.getText().toString());
|
||||||
|
if (callback != null) callback.run();
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.cancel,
|
.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
|
||||||
(dialog, which) -> dialog.dismiss()
|
|
||||||
)
|
|
||||||
.setNeutralButton(requireContext().getString(R.string.stickersapi7),
|
.setNeutralButton(requireContext().getString(R.string.stickersapi7),
|
||||||
(dialog, which) -> new VkAlertDialog.Builder(requireContext())
|
(dialog, which) ->
|
||||||
.setTitle(requireContext().getString(R.string.stickersapi8))
|
new VkAlertDialog.Builder(requireContext())
|
||||||
.setMessage(requireContext().getString(R.string.stickersapi9) +
|
.setTitle(requireContext().getString(R.string.stickersapi8))
|
||||||
requireContext().getString(R.string.stickersapi10))
|
.setMessage(requireContext().getString(R.string.stickersapi9) +
|
||||||
.setNeutralButton(AndroidUtils.getString("open_bot"), (dl, i) -> {
|
requireContext().getString(R.string.stickersapi10))
|
||||||
var intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/botfather"));
|
.setNeutralButton(AndroidUtils.getString("open_bot"), (dl, i) -> {
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
var intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/botfather"))
|
||||||
requireActivity().startActivity(intent);
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
})
|
requireActivity().startActivity(intent);
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
})
|
||||||
.show()
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show()
|
||||||
)
|
)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
@@ -253,45 +210,35 @@ public class StickersFragment extends BaseToolbarFragment {
|
|||||||
private void checkApiKey(Runnable callback) {
|
private void checkApiKey(Runnable callback) {
|
||||||
mGrabber.setBotApiKey(TGPref.getTGBotKey());
|
mGrabber.setBotApiKey(TGPref.getTGBotKey());
|
||||||
|
|
||||||
var context = requireContext();
|
var dialog = new ProgressDialog(requireContext());
|
||||||
|
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
final ProgressDialog progressDialog = new ProgressDialog(context);
|
dialog.setMessage(requireContext().getString(R.string.stickersapi1));
|
||||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
dialog.show();
|
||||||
progressDialog.setMessage(requireContext().getString(R.string.stickersapi1));
|
|
||||||
progressDialog.show();
|
|
||||||
|
|
||||||
mGrabber.checkKey(new TelegramStickersGrabber.KeyCheckListener() {
|
mGrabber.checkKey(new TelegramStickersGrabber.KeyCheckListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onKeyChecked(boolean ok) {
|
public void onKeyChecked(boolean ok) {
|
||||||
progressDialog.dismiss();
|
dialog.dismiss();
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
Toast.makeText(context, requireContext().getString(R.string.stickersapi2), Toast.LENGTH_SHORT).show();
|
Toast.makeText(requireContext(), requireContext().getString(R.string.stickersapi2), Toast.LENGTH_SHORT).show();
|
||||||
TGPref.setTGBotKey(null);
|
TGPref.setTGBotKey(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mService.setBotKey(TGPref.getTGBotKey());
|
TelegramStickersService.getInstance(requireContext())
|
||||||
|
.setBotKey(TGPref.getTGBotKey());
|
||||||
|
|
||||||
if (callback != null) callback.run();
|
if (callback != null) callback.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNetError() {
|
public void onNetError() {
|
||||||
progressDialog.dismiss();
|
dialog.dismiss();
|
||||||
new VkAlertDialog.Builder(context)
|
new VkAlertDialog.Builder(requireContext())
|
||||||
.setMessage(requireContext().getString(R.string.stickersapi3))
|
.setMessage(requireContext().getString(R.string.stickersapi3))
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.setPositiveButton(requireContext().getString(R.string.stickersapi4), (arg0, arg1) -> checkApiKey(callback))
|
.setPositiveButton(requireContext().getString(R.string.stickersapi4), (di, which) -> checkApiKey(callback))
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private String parsePack(String pack) {
|
|
||||||
if (pack.startsWith("https://t.me/addstickers/")) {
|
|
||||||
pack = pack.substring(25);
|
|
||||||
} else if (pack.startsWith("https://telegram.me/addstickers/")) {
|
|
||||||
pack = pack.substring(32);
|
|
||||||
}
|
|
||||||
return pack;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
-87
@@ -1,87 +0,0 @@
|
|||||||
package ru.vtosters.lite.ui.fragments.tgstickers;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber;
|
|
||||||
import com.vk.core.util.ToastUtils;
|
|
||||||
import com.vtosters.lite.R;
|
|
||||||
import ru.vtosters.lite.tgs.TGPref;
|
|
||||||
import ru.vtosters.lite.tgs.TGRoot;
|
|
||||||
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
|
||||||
import ru.vtosters.lite.ui.fragments.TrackedMaterialPreferenceToolbarFragment;
|
|
||||||
|
|
||||||
public class StickersPreferencesFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle bundle) {
|
|
||||||
super.onCreate(bundle);
|
|
||||||
|
|
||||||
int vtosterXml = R.xml.empty;
|
|
||||||
addPreferencesFromResource(vtosterXml);
|
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), requireContext().getString(R.string.sprefsstickers));
|
|
||||||
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", R.string.sprefsdelkey, 0, 0, preference -> {
|
|
||||||
TGPref.setTGBotKey(null);
|
|
||||||
ToastUtils.a(requireContext().getString(R.string.sprefsdelkey2));
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), requireContext().getString(R.string.sprefsnetwork));
|
|
||||||
PreferenceFragmentUtils.addListPreference(getPreferenceScreen(), "VTGS:CM", "0", requireContext().getString(R.string.sprefsctype), new CharSequence[]{
|
|
||||||
requireContext().getString(R.string.ctypedirect), requireContext().getString(R.string.ctypesocks)
|
|
||||||
}, new String[]{
|
|
||||||
"0", "2"
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("VTGS:CM").setTitle(TGRoot.getSummary());
|
|
||||||
|
|
||||||
findPreference("VTGS:CM").setOnPreferenceChangeListener((preference, o) -> {
|
|
||||||
String newData = (String) o;
|
|
||||||
TGPref.setTGConnectMethod(Integer.parseInt(newData));
|
|
||||||
TelegramStickersGrabber.updateURLs();
|
|
||||||
preference.setTitle(TGRoot.getSummary());
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), R.string.ssocks);
|
|
||||||
|
|
||||||
PreferenceFragmentUtils.addEditTextPreference(getPreferenceScreen(), "tg_proxy_host", R.string.ssockshost, (preference, o) -> {
|
|
||||||
TGPref.setTGProxyIP(o);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
PreferenceFragmentUtils.addEditTextPreference(getPreferenceScreen(), "tg_proxy_port", R.string.ssocksport, (preference, o) -> {
|
|
||||||
try {
|
|
||||||
TGPref.setTGProxyPort(Integer.parseInt(o));
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
ToastUtils.a(requireContext().getString(R.string.ssockswport));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
PreferenceFragmentUtils.addMaterialSwitchPreference(getPreferenceScreen(), "tg_proxy_auth", requireContext().getString(R.string.ssocksauth), "", 0, false, (preference, o) -> {
|
|
||||||
boolean value = (boolean) o;
|
|
||||||
|
|
||||||
findPreference("tg_proxy_login").setEnabled(value);
|
|
||||||
findPreference("tg_proxy_pass").setEnabled(value);
|
|
||||||
|
|
||||||
TGPref.setTGProxyPassEnabled(value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
PreferenceFragmentUtils.addEditTextPreference(getPreferenceScreen(), "tg_proxy_login", R.string.ssockslogin, (preference, o) -> {
|
|
||||||
TGPref.setTGProxyUserPass(o, TGPref.getTGProxyPass());
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
PreferenceFragmentUtils.addEditTextPreference(getPreferenceScreen(), "tg_proxy_pass", R.string.ssockspass, (preference, o) -> {
|
|
||||||
TGPref.setTGProxyUserPass(TGPref.getTGProxyUser(), o);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("tg_proxy_login").setEnabled(TGPref.isTGProxyPassEnabled());
|
|
||||||
findPreference("tg_proxy_pass").setEnabled(TGPref.isTGProxyPassEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int T4() {
|
|
||||||
return R.string.vtltgs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -31,4 +31,9 @@ public class DockBarTab {
|
|||||||
Class<?> fragmentClass) {
|
Class<?> fragmentClass) {
|
||||||
return new DockBarTab(tag, iconID, titleID, resID, fragmentClass);
|
return new DockBarTab(tag, iconID, titleID, resID, fragmentClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object arg) {
|
||||||
|
return this.id == ((DockBarTab) arg).id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,10 @@ public class SuperAppItem {
|
|||||||
public static SuperAppItem valuesOf(String type, String title) {
|
public static SuperAppItem valuesOf(String type, String title) {
|
||||||
return new SuperAppItem(type, title);
|
return new SuperAppItem(type, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object arg) {
|
||||||
|
var item = (SuperAppItem) arg;
|
||||||
|
return this.type.equals(item.type) && this.title.equals(item.title);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,16 +37,16 @@ public class AlphaView extends SliderViewBase implements ColorObserver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Bitmap makeBitmap(int w, int h) {
|
protected Bitmap makeBitmap(int w, int h) {
|
||||||
final boolean isWide = w > h;
|
boolean isWide = w > h;
|
||||||
final int n = Math.max(w, h);
|
int n = Math.max(w, h);
|
||||||
int color = observableColor.getColor();
|
int color = observableColor.getColor();
|
||||||
int[] colors = new int[n];
|
int[] colors = new int[n];
|
||||||
for (int i = 0; i < n; ++i) {
|
for (int i = 0; i < n; ++i) {
|
||||||
float alpha = isWide ? (float) i / n : 1 - (float) i / n;
|
float alpha = isWide ? (float) i / n : 1 - (float) i / n;
|
||||||
colors[i] = color & 0xffffff | (int) (alpha * 0xff) << 24;
|
colors[i] = color & 0xffffff | (int) (alpha * 0xff) << 24;
|
||||||
}
|
}
|
||||||
final int bmpWidth = isWide ? w : 1;
|
int bmpWidth = isWide ? w : 1;
|
||||||
final int bmpHeight = isWide ? 1 : h;
|
int bmpHeight = isWide ? 1 : h;
|
||||||
return Bitmap.createBitmap(colors, bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
|
return Bitmap.createBitmap(colors, bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ class HexEdit {
|
|||||||
new HexFilter()
|
new HexFilter()
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void setUpListeners(final EditText hexEdit, final ObservableColor observableColor) {
|
public static void setUpListeners(EditText hexEdit, ObservableColor observableColor) {
|
||||||
|
|
||||||
class MultiObserver implements ColorObserver, TextWatcher {
|
class MultiObserver implements ColorObserver, TextWatcher {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateColor(ObservableColor observableColor) {
|
public void updateColor(ObservableColor observableColor) {
|
||||||
final String colorString = formatColor(observableColor.getColor());
|
String colorString = formatColor(observableColor.getColor());
|
||||||
// Prevent onTextChanged getting called when we update text programmatically
|
// Prevent onTextChanged getting called when we update text programmatically
|
||||||
hexEdit.removeTextChangedListener(this);
|
hexEdit.removeTextChangedListener(this);
|
||||||
hexEdit.setText(colorString);
|
hexEdit.setText(colorString);
|
||||||
@@ -57,7 +56,7 @@ class HexEdit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final MultiObserver multiObserver = new MultiObserver();
|
MultiObserver multiObserver = new MultiObserver();
|
||||||
hexEdit.addTextChangedListener(multiObserver);
|
hexEdit.addTextChangedListener(multiObserver);
|
||||||
observableColor.addObserver(multiObserver);
|
observableColor.addObserver(multiObserver);
|
||||||
setShowAlphaDigits(hexEdit, true);
|
setShowAlphaDigits(hexEdit, true);
|
||||||
@@ -71,14 +70,13 @@ class HexEdit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setShowAlphaDigits(final EditText hexEdit, boolean showAlphaDigits) {
|
public static void setShowAlphaDigits(EditText hexEdit, boolean showAlphaDigits) {
|
||||||
hexEdit.setFilters(showAlphaDigits ? withAlphaDigits : withoutAlphaDigits);
|
hexEdit.setFilters(showAlphaDigits ? withAlphaDigits : withoutAlphaDigits);
|
||||||
hexEdit.setText(hexEdit.getText()); // trigger a reformat of text
|
hexEdit.setText(hexEdit.getText()); // trigger a reformat of text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class ColorPasteLengthFilter implements InputFilter {
|
private static class ColorPasteLengthFilter implements InputFilter {
|
||||||
|
|
||||||
private static final int MAX_LENGTH = 6;
|
private static final int MAX_LENGTH = 6;
|
||||||
private static final int PASTED_LEN = 8;
|
private static final int PASTED_LEN = 8;
|
||||||
private final InputFilter sixDigitFilter = new InputFilter.LengthFilter(MAX_LENGTH);
|
private final InputFilter sixDigitFilter = new InputFilter.LengthFilter(MAX_LENGTH);
|
||||||
@@ -86,8 +84,8 @@ class HexEdit {
|
|||||||
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
||||||
// If 8 digits have been pasted, replacing all source, trim alpha digits.
|
// If 8 digits have been pasted, replacing all source, trim alpha digits.
|
||||||
// Otherwise standard LengthFilter behavior.
|
// Otherwise standard LengthFilter behavior.
|
||||||
final int srcLength = end - start;
|
int srcLength = end - start;
|
||||||
final int dstSelLength = dend - dstart;
|
int dstSelLength = dend - dstart;
|
||||||
if (srcLength == PASTED_LEN && dstSelLength == dest.length()) {
|
if (srcLength == PASTED_LEN && dstSelLength == dest.length()) {
|
||||||
// Discard alpha digits:
|
// Discard alpha digits:
|
||||||
return source.subSequence(PASTED_LEN - MAX_LENGTH, PASTED_LEN);
|
return source.subSequence(PASTED_LEN - MAX_LENGTH, PASTED_LEN);
|
||||||
@@ -98,7 +96,6 @@ class HexEdit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class HexFilter implements InputFilter {
|
private static class HexFilter implements InputFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
||||||
boolean state = true;
|
boolean state = true;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user