Compare commits
60
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93ff69e46f | ||
|
|
eb4f0be29a | ||
|
|
3ef4dc3b91 | ||
|
|
08dc8b1f21 | ||
|
|
e39dd13730 | ||
|
|
c2e902d227 | ||
|
|
9a4e2a65d7 | ||
|
|
812c3047a8 | ||
|
|
53081115cc | ||
|
|
af4f069eb4 | ||
|
|
904814e5cb | ||
|
|
c240db520d | ||
|
|
8f9235e6db | ||
|
|
10b27566c0 | ||
|
|
16b67cf6c6 | ||
|
|
23436f1026 | ||
|
|
6998bdba1c | ||
|
|
5aa0fe9959 | ||
|
|
88f0939f04 | ||
|
|
73653fa2e0 | ||
|
|
c812b13446 | ||
|
|
ea9291a5eb | ||
|
|
8d93e298b4 | ||
|
|
49d448ad99 | ||
|
|
6bcc278dee | ||
|
|
cff0a98227 | ||
|
|
3529eb7ba1 | ||
|
|
2ee4bf1107 | ||
|
|
ac43a7794b | ||
|
|
47b2519ca5 | ||
|
|
701674ce45 | ||
|
|
e19a005362 | ||
|
|
e11e97cd29 | ||
|
|
ab2715456f | ||
|
|
68526f0ba2 | ||
|
|
b3243f1d41 | ||
|
|
175866ddc5 | ||
|
|
f691f8cc9e | ||
|
|
305614dae6 | ||
|
|
4bbcde750a | ||
|
|
e159b4f594 | ||
|
|
204b0c6e2c | ||
|
|
522f6cd42b | ||
|
|
7dd9e5ff3a | ||
|
|
07f8542f56 | ||
|
|
1a7a0e7f4f | ||
|
|
953aa790e3 | ||
|
|
c1de68893c | ||
|
|
e0d3f22f16 | ||
|
|
60e32df046 | ||
|
|
bbc4aa98f7 | ||
|
|
287af14589 | ||
|
|
404594df87 | ||
|
|
3b8af6e477 | ||
|
|
cb38660a3a | ||
|
|
f6f0fb3d08 | ||
|
|
dab50535ef | ||
|
|
d9300fc621 | ||
|
|
48eba3ccbb | ||
|
|
2336da53be |
@@ -97,6 +97,8 @@ dependencies {
|
||||
implementation 'net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4'
|
||||
implementation 'com.google.guava:guava:32.0.0-android'
|
||||
|
||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
||||
compileOnly fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,124 +0,0 @@
|
||||
package com.arthenica.ffmpegkit;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import com.arthenica.smartexception.java.Exceptions;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class NativeLoader {
|
||||
static final String[] FFMPEG_LIBRARIES = {"avutil", "swresample", "avcodec", "avformat", "avfilter", "avdevice"};
|
||||
|
||||
private static void loadLibrary(String libraryName) {
|
||||
System.loadLibrary(libraryName);
|
||||
}
|
||||
|
||||
private static List<String> loadExternalLibraries() {
|
||||
//return Packages.getExternalLibraries();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static String loadNativeAbi() {
|
||||
return AbiDetect.getNativeAbi();
|
||||
}
|
||||
|
||||
static String loadAbi() {
|
||||
return AbiDetect.getAbi();
|
||||
}
|
||||
|
||||
static String loadPackageName() {
|
||||
return Packages.getPackageName();
|
||||
}
|
||||
|
||||
static String loadVersion() {
|
||||
return FFmpegKitConfig.getVersion();
|
||||
}
|
||||
|
||||
static boolean loadIsLTSBuild() {
|
||||
return AbiDetect.isNativeLTSBuild();
|
||||
}
|
||||
|
||||
static int loadLogLevel() {
|
||||
return FFmpegKitConfig.getNativeLogLevel();
|
||||
}
|
||||
|
||||
static String loadBuildDate() {
|
||||
return FFmpegKitConfig.getBuildDate();
|
||||
}
|
||||
|
||||
static void enableRedirection() {
|
||||
FFmpegKitConfig.enableRedirection();
|
||||
}
|
||||
|
||||
static void loadFFmpegKitAbiDetect() {
|
||||
loadLibrary("ffmpegkit_abidetect");
|
||||
}
|
||||
|
||||
static boolean loadFFmpeg() {
|
||||
boolean nativeFFmpegLoaded = false;
|
||||
boolean nativeFFmpegTriedAndFailed = false;
|
||||
|
||||
if (AbiDetect.ARM_V7A.equals(loadNativeAbi())) {
|
||||
try {
|
||||
for (String ffmpegLibrary : FFMPEG_LIBRARIES) {
|
||||
loadLibrary(ffmpegLibrary + "_neon");
|
||||
}
|
||||
nativeFFmpegLoaded = true;
|
||||
} catch (final Error e) {
|
||||
android.util.Log.i(FFmpegKitConfig.TAG, String.format("NEON supported armeabi-v7a ffmpeg library not found. Loading default armeabi-v7a library.%s", Exceptions.getStackTraceString(e)));
|
||||
nativeFFmpegTriedAndFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativeFFmpegLoaded) {
|
||||
for (String ffmpegLibrary : FFMPEG_LIBRARIES) {
|
||||
loadLibrary(ffmpegLibrary);
|
||||
}
|
||||
}
|
||||
return nativeFFmpegTriedAndFailed;
|
||||
}
|
||||
|
||||
static void loadFFmpegKit(boolean nativeFFmpegTriedAndFailed) {
|
||||
boolean nativeFFmpegKitLoaded = false;
|
||||
if (!nativeFFmpegTriedAndFailed && "arm-v7a".equals(loadNativeAbi())) {
|
||||
try {
|
||||
loadLibrary("ffmpegkit_armv7a_neon");
|
||||
nativeFFmpegKitLoaded = true;
|
||||
AbiDetect.setArmV7aNeonLoaded();
|
||||
} catch (Error e) {
|
||||
Log.i("ffmpeg-kit", String.format("NEON supported armeabi-v7a ffmpegkit library not found. Loading default armeabi-v7a library.%s", Exceptions.getStackTraceString(e)));
|
||||
}
|
||||
}
|
||||
if (!nativeFFmpegKitLoaded) {
|
||||
loadLibrary("ffmpegkit");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static String getDeviceDebugInformation() {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("brand: ");
|
||||
stringBuilder.append(Build.BRAND);
|
||||
stringBuilder.append(", model: ");
|
||||
stringBuilder.append(Build.MODEL);
|
||||
stringBuilder.append(", device: ");
|
||||
stringBuilder.append(Build.DEVICE);
|
||||
stringBuilder.append(", api level: ");
|
||||
stringBuilder.append(Build.VERSION.SDK_INT);
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
stringBuilder.append(", abis: ");
|
||||
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_ABIS));
|
||||
stringBuilder.append(", 32bit abis: ");
|
||||
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_32_BIT_ABIS));
|
||||
stringBuilder.append(", 64bit abis: ");
|
||||
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_64_BIT_ABIS));
|
||||
} else {
|
||||
stringBuilder.append(", cpu abis: ");
|
||||
stringBuilder.append(Build.CPU_ABI);
|
||||
stringBuilder.append(", cpu abi2s: ");
|
||||
stringBuilder.append(Build.CPU_ABI2);
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,732 +0,0 @@
|
||||
/*
|
||||
* BSD 3-Clause License
|
||||
*
|
||||
* Copyright (c) 2020, Taner Sener
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.arthenica.smartexception;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>Abstract class that includes common variables and methods for different <code>Exceptions</code> implementations.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public abstract class AbstractExceptions {
|
||||
|
||||
/**
|
||||
* <p>Default max depth used in methods which do not have a maxDepth argument.
|
||||
*/
|
||||
public static final int DEFAULT_MAX_DEPTH = 10;
|
||||
|
||||
/**
|
||||
* <p>Default value for ignoring all causes when stack trace elements are printed or converted to string.
|
||||
*/
|
||||
public static final boolean DEFAULT_IGNORE_ALL_CAUSES = false;
|
||||
|
||||
/**
|
||||
* <p>Stores global root package names.
|
||||
*/
|
||||
public static final Set<String> rootPackageSet = Collections.synchronizedSet(new HashSet<String>());
|
||||
|
||||
/**
|
||||
* <p>Stores global group package names.
|
||||
*/
|
||||
public static final Set<String> groupPackageSet = Collections.synchronizedSet(new HashSet<String>());
|
||||
|
||||
/**
|
||||
* <p>Stores global ignore package names.
|
||||
*/
|
||||
public static final Set<String> ignorePackageSet = Collections.synchronizedSet(new HashSet<String>());
|
||||
|
||||
/**
|
||||
* <p>Stores global ignore cause package names.
|
||||
*/
|
||||
public static final Set<String> ignoreCausePackageSet = Collections.synchronizedSet(new HashSet<String>());
|
||||
|
||||
/**
|
||||
* <p>Stores the value of global ignore all causes option.
|
||||
*/
|
||||
public static boolean ignoreAllCauses = DEFAULT_IGNORE_ALL_CAUSES;
|
||||
|
||||
/**
|
||||
* <p>Stores the global stack trace serializer implementation.
|
||||
*/
|
||||
public static StackTraceElementSerializer stackTraceElementSerializer;
|
||||
|
||||
/**
|
||||
* <p>Registers a new root package.
|
||||
*
|
||||
* @param packageString root package name to register
|
||||
*/
|
||||
public static void registerRootPackage(final String packageString) {
|
||||
rootPackageSet.add(packageString);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clears previously registered root packages.
|
||||
*/
|
||||
public static void clearRootPackages() {
|
||||
rootPackageSet.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Registers a new group package.
|
||||
*
|
||||
* @param packageString group package name to register
|
||||
*/
|
||||
public static void registerGroupPackage(final String packageString) {
|
||||
groupPackageSet.add(packageString);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clears previously registered group packages.
|
||||
*/
|
||||
public static void clearGroupPackages() {
|
||||
groupPackageSet.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
|
||||
* objects in <code>getStackTraceString</code> methods.
|
||||
*
|
||||
* @return current stack trace serializer implementation
|
||||
*/
|
||||
public static StackTraceElementSerializer getStackTraceElementSerializer() {
|
||||
return stackTraceElementSerializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
|
||||
* objects in <code>getStackTraceString</code> methods.
|
||||
*
|
||||
* @param stackTraceElementSerializer new stack trace serializer implementation
|
||||
*/
|
||||
public static void setStackTraceElementSerializer(final StackTraceElementSerializer stackTraceElementSerializer) {
|
||||
AbstractExceptions.stackTraceElementSerializer = stackTraceElementSerializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Registers a new ignore package.
|
||||
*
|
||||
* @param packageString ignore package name to register
|
||||
* @param ignoreCauseClasses ignore cause classes from this package too
|
||||
*/
|
||||
public static void registerIgnorePackage(final String packageString, final boolean ignoreCauseClasses) {
|
||||
ignorePackageSet.add(packageString);
|
||||
if (ignoreCauseClasses) {
|
||||
ignoreCausePackageSet.add(packageString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clears previously registered ignore packages.
|
||||
*/
|
||||
public static void clearIgnorePackages() {
|
||||
ignorePackageSet.clear();
|
||||
ignoreCausePackageSet.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the value of ignore all causes option.
|
||||
*
|
||||
* @return the value of global ignore all causes option. If value is true then stack trace elements printed or
|
||||
* converted to string will not include causes. If value is false causes will be appended to the stack trace of
|
||||
* the main throwable.
|
||||
*/
|
||||
public static boolean getIgnoreAllCauses() {
|
||||
return ignoreAllCauses;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the value of ignore all causes option.
|
||||
*
|
||||
* @param ignoreAllCauses new global ignore all causes option. If value is true then stack trace elements printed
|
||||
* or converted to string will not include causes. If value is false causes will be appended to the stack trace of
|
||||
* the main throwable.
|
||||
*/
|
||||
public static void setIgnoreAllCauses(final boolean ignoreAllCauses) {
|
||||
AbstractExceptions.ignoreAllCauses = ignoreAllCauses;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code>.
|
||||
*
|
||||
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
|
||||
* by {@link #registerGroupPackage(String)} and ignore packages registered by
|
||||
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable) {
|
||||
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code>.
|
||||
*
|
||||
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
|
||||
* by {@link #registerGroupPackage(String)} and ignore packages registered by
|
||||
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param ignoreAllCauses ignore all causes in the exception chain
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final boolean ignoreAllCauses) {
|
||||
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param rootPackageSet root packages to use for building the stack trace
|
||||
* @param groupPackageSet group packages to use for building the stack trace
|
||||
* @param ignorePackageSet ignore packages to use for building the stack trace
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet) {
|
||||
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param rootPackageSet root packages to use for building the stack trace
|
||||
* @param groupPackageSet group packages to use for building the stack trace
|
||||
* @param ignorePackageSet ignore packages to use for building the stack trace
|
||||
* @param ignoreAllCauses ignore all causes in the exception chain
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final boolean ignoreAllCauses) {
|
||||
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param rootPackage root package to use for building the stack trace
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final String rootPackage) {
|
||||
return getStackTraceString(throwable, false, Collections.singleton(rootPackage), new HashSet<String>(), new HashSet<String>(), 0, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param rootPackage root package to use for building the stack trace
|
||||
* @param groupPackage group package to use for building the stack trace
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final String rootPackage, final String groupPackage) {
|
||||
return getStackTraceString(throwable, false, Collections.singleton(rootPackage), Collections.singleton(groupPackage), new HashSet<String>(), 0, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param maxDepth max depth in exception chain that will be used
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final int maxDepth) {
|
||||
return getStackTraceString(throwable, false, new HashSet<String>(), new HashSet<String>(), new HashSet<String>(), maxDepth, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param maxDepth max depth in exception chain that will be used
|
||||
* @param ignoreAllCauses ignore all causes in the exception chain
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final int maxDepth, final boolean ignoreAllCauses) {
|
||||
return getStackTraceString(throwable, false, new HashSet<String>(), new HashSet<String>(), new HashSet<String>(), maxDepth, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using parameters provided.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param isCause throwable is a cause or not
|
||||
* @param rootPackageSet root packages to use for building the stack trace
|
||||
* @param groupPackageSet group packages to use for building the stack trace
|
||||
* @param ignorePackageSet ignore packages to use for building the stack trace
|
||||
* @param maxDepth max depth in exception chain that will be used
|
||||
* @param ignoreAllCauses ignore all causes in the exception chain
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final boolean isCause, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final int maxDepth, final boolean ignoreAllCauses) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (throwable == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final String className = throwable.getClass().getName();
|
||||
|
||||
final StackTraceElement[] stackTraceElements;
|
||||
if (maxDepth > 0) {
|
||||
stackTraceElements = getStackTrace(throwable, maxDepth);
|
||||
} else {
|
||||
stackTraceElements = getStackTrace(throwable, rootPackageSet, ignorePackageSet);
|
||||
}
|
||||
String message = throwable.getLocalizedMessage();
|
||||
if (isEmpty(message)) {
|
||||
message = throwable.getMessage();
|
||||
}
|
||||
|
||||
/* PRINT MESSAGE */
|
||||
if (isCause) {
|
||||
builder.append(System.lineSeparator());
|
||||
builder.append("Caused by: ");
|
||||
builder.append(className);
|
||||
if (!isEmpty(message)) {
|
||||
builder.append(": ");
|
||||
builder.append(message);
|
||||
}
|
||||
} else {
|
||||
builder.append(System.lineSeparator());
|
||||
builder.append(className);
|
||||
if (!isEmpty(message)) {
|
||||
builder.append(": ");
|
||||
builder.append(message);
|
||||
}
|
||||
}
|
||||
|
||||
/* PRINT SHORT STACK TRACE */
|
||||
String currentGroupPackage = null;
|
||||
StackTraceElement firstStackTraceElementInTheGroup = null;
|
||||
int currentGroupCount = 0;
|
||||
for (StackTraceElement traceElement : stackTraceElements) {
|
||||
String traceElementClassName = traceElement.getClassName();
|
||||
String groupPackageMatch = getContainingPackage(traceElementClassName, groupPackageSet);
|
||||
|
||||
if (groupPackageMatch != null) {
|
||||
if (!groupPackageMatch.equals(currentGroupPackage)) {
|
||||
appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
|
||||
|
||||
builder.append(System.lineSeparator());
|
||||
builder.append("\tat ");
|
||||
|
||||
currentGroupPackage = groupPackageMatch;
|
||||
firstStackTraceElementInTheGroup = traceElement;
|
||||
currentGroupCount = 1;
|
||||
} else {
|
||||
currentGroupCount++;
|
||||
}
|
||||
} else {
|
||||
currentGroupCount = appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
|
||||
|
||||
builder.append(System.lineSeparator());
|
||||
builder.append("\tat ");
|
||||
if (stackTraceElementSerializer == null) {
|
||||
throw new IllegalArgumentException("Stack trace element serializer not initialized.");
|
||||
} else {
|
||||
builder.append(stackTraceElementSerializer.toString(traceElement));
|
||||
}
|
||||
currentGroupPackage = null;
|
||||
}
|
||||
}
|
||||
|
||||
appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
|
||||
|
||||
final Throwable cause = throwable.getCause();
|
||||
if (cause != null && !containsPackage(className, ignoreCausePackageSet) && !ignoreAllCauses) {
|
||||
builder.append(getStackTraceString(cause, true, rootPackageSet, groupPackageSet, ignorePackageSet, maxDepth, ignoreAllCauses));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Appends stack trace group information for the given parameters.
|
||||
*
|
||||
* @param stringBuilder string builder that group stack trace information will be appended
|
||||
* @param currentGroupPackage package name of the current group
|
||||
* @param numberOfElementsInTheCurrentGroup number of elements in the current group
|
||||
* @param firstStackTraceElementInTheGroup first stack trace element of this group
|
||||
* @return new value for the group element count
|
||||
*/
|
||||
public static int appendStackTraceGroupElement(final StringBuilder stringBuilder, final String currentGroupPackage, final int numberOfElementsInTheCurrentGroup, final StackTraceElement firstStackTraceElementInTheGroup) {
|
||||
if (numberOfElementsInTheCurrentGroup > 0) {
|
||||
if (stackTraceElementSerializer == null) {
|
||||
throw new IllegalArgumentException("Stack trace element serializer not initialized.");
|
||||
} else {
|
||||
stringBuilder.append((numberOfElementsInTheCurrentGroup == 1) ? stackTraceElementSerializer.toString(firstStackTraceElementInTheGroup) : String.format("%s%s ... %d more", stackTraceElementSerializer.getModuleName(firstStackTraceElementInTheGroup), currentGroupPackage, (numberOfElementsInTheCurrentGroup - 1)));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns true if package of the given class name matches one of package names provided in
|
||||
* <code>packageSet</code>.
|
||||
*
|
||||
* @param fullClassName fully qualified class name
|
||||
* @param packageSet set containing package names
|
||||
* @return true if package of the given class name matches one of package names provided in <code>packageSet</code>,
|
||||
* false otherwise
|
||||
*/
|
||||
public static boolean containsPackage(final String fullClassName, final Set<String> packageSet) {
|
||||
return getContainingPackage(fullClassName, packageSet) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks if one of the package names in the <code>packageSet</code> matches the package of the given class name
|
||||
* and returns the matching package name.
|
||||
*
|
||||
* @param fullClassName fully qualified class name
|
||||
* @param packageSet set containing package names
|
||||
* @return one of the package names in the <code>packageSet</code> that matches the package of the given class name
|
||||
* or null if no matching package name is found in the <code>packageSet</code>
|
||||
*/
|
||||
public static String getContainingPackage(final String fullClassName, final Set<String> packageSet) {
|
||||
for (final String parentExceptionPackage : packageSet) {
|
||||
if (fullClassName.startsWith(parentExceptionPackage)) {
|
||||
return parentExceptionPackage;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns all messages found in the exception chain of the <code>throwable</code> as a single string.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @return a string containing all messages found in the exception chain
|
||||
*/
|
||||
public static String getAllMessages(final Throwable throwable) {
|
||||
final StringBuilder messageBuilder = new StringBuilder();
|
||||
getAllMessages(throwable, messageBuilder);
|
||||
return messageBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds all messages found in the exception chain of the <code>throwable</code> into the given message builder.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param messageBuilder message builder
|
||||
*/
|
||||
public static void getAllMessages(final Throwable throwable, final StringBuilder messageBuilder) {
|
||||
if (throwable != null) {
|
||||
final String message = throwable.getMessage();
|
||||
if (!isEmpty(message)) {
|
||||
if (messageBuilder.length() != 0) {
|
||||
messageBuilder.append(System.lineSeparator());
|
||||
messageBuilder.append(" - Caused by: ");
|
||||
}
|
||||
messageBuilder.append(message);
|
||||
}
|
||||
getAllMessages(throwable.getCause(), messageBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Builds a smart stack trace for the given <code>throwable</code> using elements found until the maxDepth and
|
||||
* elements of it.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param maxDepth max depth in exception chain that will be returned
|
||||
* @return an array containing stack trace elements
|
||||
*/
|
||||
public static StackTraceElement[] getStackTrace(final Throwable throwable, final int maxDepth) {
|
||||
final ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>();
|
||||
|
||||
if (throwable != null) {
|
||||
StackTraceElement[] stackTrace = throwable.getStackTrace();
|
||||
for (int i = 0; (i < stackTrace.length) && (i < maxDepth); i++) {
|
||||
list.add(stackTrace[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return list.toArray(new StackTraceElement[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Builds a smart stack trace for the given <code>throwable</code> using packages provided and returns elements
|
||||
* of it.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param rootPackageSet root packages to use for building the stack trace
|
||||
* @param ignorePackageSet ignore packages to use for building the stack trace
|
||||
* @return an array containing stack trace elements
|
||||
*/
|
||||
public static StackTraceElement[] getStackTrace(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> ignorePackageSet) {
|
||||
final ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>();
|
||||
final ArrayList<StackTraceElement> partialList = new ArrayList<StackTraceElement>();
|
||||
|
||||
if (throwable != null) {
|
||||
for (StackTraceElement stackTraceElement : throwable.getStackTrace()) {
|
||||
final String className = stackTraceElement.getClassName();
|
||||
if (!isEmpty(className)) {
|
||||
if (containsPackage(className, rootPackageSet)) {
|
||||
list.addAll(partialList);
|
||||
list.add(stackTraceElement);
|
||||
} else if (!containsPackage(className, ignorePackageSet)) {
|
||||
partialList.add(stackTraceElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (list.isEmpty()) {
|
||||
list.addAll(partialList);
|
||||
}
|
||||
|
||||
return list.toArray(new StackTraceElement[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns true if the given cause class is found in the exception chain of the <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
|
||||
* {@link #DEFAULT_MAX_DEPTH}.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @return true if the given cause class is found in the exception chain of the <code>throwable</code>, false
|
||||
* otherwise
|
||||
*/
|
||||
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass) {
|
||||
return containsCause(throwable, causeClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns true if the given cause class and cause message is found in the exception chain of the
|
||||
* <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
|
||||
* {@link #DEFAULT_MAX_DEPTH}.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @param causeMessage message to search for
|
||||
* @return true if the given cause class and cause message is found in the exception chain of the
|
||||
* <code>throwable</code>, false otherwise
|
||||
*/
|
||||
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
|
||||
return (searchCause(throwable, causeClass, causeMessage, DEFAULT_MAX_DEPTH) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @return the cause of the <code>throwable</code> found or null if <code>throwable</code> does not have a cause
|
||||
*/
|
||||
public static Throwable getCause(final Throwable throwable) {
|
||||
return getCause(throwable, DEFAULT_MAX_DEPTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Note that this method walks through the exception chain up to given <code>maxDepth</code>. If exception chain
|
||||
* includes more items than <code>maxDepth</code>, cause found at <code>maxDepth</code> level is returned.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param maxDepth max depth in exception chain that will be searched
|
||||
* @return the cause of the <code>throwable</code> found until <code>maxDepth</code> is reached in the exception
|
||||
* chain or null if <code>throwable</code> does not have a cause
|
||||
*/
|
||||
public static Throwable getCause(final Throwable throwable, final int maxDepth) {
|
||||
if (throwable == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (maxDepth <= 0) {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
final Throwable cause = throwable.getCause();
|
||||
if (cause == null) {
|
||||
return throwable;
|
||||
} else {
|
||||
return getCause(cause, maxDepth - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
|
||||
* {@link #DEFAULT_MAX_DEPTH}.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
|
||||
*/
|
||||
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass) {
|
||||
return searchCause(throwable, causeClass, null, DEFAULT_MAX_DEPTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
|
||||
* {@link #DEFAULT_MAX_DEPTH}.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @param causeMessage message to search for
|
||||
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
|
||||
* and cause message
|
||||
*/
|
||||
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
|
||||
return searchCause(throwable, causeClass, causeMessage, DEFAULT_MAX_DEPTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
|
||||
* <code>maxDepth</code>.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @param causeMessage message to search for
|
||||
* @param maxDepth max depth in exception chain that will be searched
|
||||
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
|
||||
* and cause message
|
||||
*/
|
||||
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage, final int maxDepth) {
|
||||
if (throwable == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isEmpty(causeMessage)) {
|
||||
if (throwable.getClass().equals(causeClass)) {
|
||||
return throwable;
|
||||
}
|
||||
} else {
|
||||
if (throwable.getClass().equals(causeClass) && getAllMessages(throwable).toLowerCase().contains(causeMessage.toLowerCase())) {
|
||||
return throwable;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxDepth <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Throwable cause = throwable.getCause();
|
||||
if (cause == null) {
|
||||
return null;
|
||||
} else {
|
||||
return searchCause(cause, causeClass, causeMessage, maxDepth - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
|
||||
* <code>maxDepth</code>.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @param maxDepth max depth in exception chain that will be searched
|
||||
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
|
||||
*/
|
||||
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final int maxDepth) {
|
||||
if (throwable == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (throwable.getClass().equals(causeClass)) {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
if (maxDepth <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Throwable cause = throwable.getCause();
|
||||
if (cause == null) {
|
||||
return null;
|
||||
} else {
|
||||
return searchCause(cause, causeClass, maxDepth - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks if given string is empty or not.
|
||||
*
|
||||
* @param value string to check
|
||||
* @return true if given string is null or has zero length after trim(), false otherwise
|
||||
*/
|
||||
public static boolean isEmpty(final String value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return value.trim().length() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the package name of this class.
|
||||
*
|
||||
* @param className fully qualified class name
|
||||
* @return name of the package
|
||||
*/
|
||||
public static String packageName(final String className) {
|
||||
if (className == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int index = className.lastIndexOf(".");
|
||||
if (index >= 0) {
|
||||
return className.substring(0, index);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* BSD 3-Clause License
|
||||
*
|
||||
* Copyright (c) 2020, Taner Sener
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.arthenica.smartexception;
|
||||
|
||||
/**
|
||||
* <p>Base interface for classes which can serialize {@link StackTraceElement} objects.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public interface StackTraceElementSerializer {
|
||||
|
||||
/**
|
||||
* <p>Converts the given <code>stackTraceElement</code> into a string.
|
||||
*
|
||||
* @param stackTraceElement stack trace element to convert
|
||||
* @return string representing the given <code>stackTraceElement</code>
|
||||
*/
|
||||
String toString(final StackTraceElement stackTraceElement);
|
||||
|
||||
/**
|
||||
* <p>Returns module name for the given <code>stackTraceElement</code>.
|
||||
*
|
||||
* @param stackTraceElement stack trace element to use
|
||||
* @return module name of the given <code>stackTraceElement</code> or an empty string if implementation does not
|
||||
* support printing module names
|
||||
*/
|
||||
String getModuleName(final StackTraceElement stackTraceElement);
|
||||
|
||||
/**
|
||||
* <p>Returns string definition used to print native methods.
|
||||
*
|
||||
* @return string definition used to print native methods
|
||||
*/
|
||||
String getNativeMethodDefinition();
|
||||
|
||||
/**
|
||||
* <p>Returns string definition used to print methods with unknown source.
|
||||
*
|
||||
* @return string definition used to print methods with unknown source
|
||||
*/
|
||||
String getUnknownSourceDefinition();
|
||||
|
||||
}
|
||||
@@ -1,423 +0,0 @@
|
||||
/*
|
||||
* BSD 3-Clause License
|
||||
*
|
||||
* Copyright (c) 2020, Taner Sener
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.arthenica.smartexception.java;
|
||||
|
||||
import com.arthenica.smartexception.AbstractExceptions;
|
||||
import com.arthenica.smartexception.StackTraceElementSerializer;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>Utility class to handle Throwable objects and stack traces. This implementation is compatible with all Java and
|
||||
* Android versions.
|
||||
*
|
||||
* @author Taner Sener
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public class Exceptions {
|
||||
|
||||
static {
|
||||
AbstractExceptions.setStackTraceElementSerializer(new StackTraceElementSerializer() {
|
||||
|
||||
@Override
|
||||
public String toString(StackTraceElement stackTraceElement) {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.append(stackTraceElement.getClassName());
|
||||
stringBuilder.append(".");
|
||||
stringBuilder.append(stackTraceElement.getMethodName());
|
||||
|
||||
if (stackTraceElement.isNativeMethod()) {
|
||||
stringBuilder.append("(Native Method)");
|
||||
} else if ((stackTraceElement.getFileName() != null) && (stackTraceElement.getFileName().length() > 0)) {
|
||||
stringBuilder.append("(");
|
||||
stringBuilder.append(stackTraceElement.getFileName());
|
||||
if (stackTraceElement.getLineNumber() >= 0) {
|
||||
stringBuilder.append(":");
|
||||
stringBuilder.append(stackTraceElement.getLineNumber());
|
||||
}
|
||||
stringBuilder.append(")");
|
||||
} else {
|
||||
stringBuilder.append("(Unknown Source)");
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModuleName(StackTraceElement stackTraceElement) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNativeMethodDefinition() {
|
||||
return "(Native Method)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUnknownSourceDefinition() {
|
||||
return "(Unknown Source)";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Registers a new root package.
|
||||
*
|
||||
* @param packageString root package name to register
|
||||
*/
|
||||
public static void registerRootPackage(final String packageString) {
|
||||
AbstractExceptions.registerRootPackage(packageString);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clears previously registered root packages.
|
||||
*/
|
||||
public static void clearRootPackages() {
|
||||
AbstractExceptions.clearRootPackages();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Registers a new group package.
|
||||
*
|
||||
* @param packageString group package name to register
|
||||
*/
|
||||
public static void registerGroupPackage(final String packageString) {
|
||||
AbstractExceptions.registerGroupPackage(packageString);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clears previously registered group packages.
|
||||
*/
|
||||
public static void clearGroupPackages() {
|
||||
AbstractExceptions.clearGroupPackages();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
|
||||
* objects in <code>getStackTraceString</code> methods.
|
||||
*
|
||||
* @return current stack trace serializer implementation
|
||||
*/
|
||||
public static StackTraceElementSerializer getStackTraceElementSerializer() {
|
||||
return AbstractExceptions.getStackTraceElementSerializer();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
|
||||
* objects in <code>getStackTraceString</code> methods.
|
||||
*
|
||||
* @param stackTraceElementSerializer new stack trace serializer implementation
|
||||
*/
|
||||
public static void setStackTraceElementSerializer(final StackTraceElementSerializer stackTraceElementSerializer) {
|
||||
AbstractExceptions.setStackTraceElementSerializer(stackTraceElementSerializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Registers a new ignore package.
|
||||
*
|
||||
* @param packageString ignore package name to register
|
||||
* @param ignoreCauseClasses ignore cause classes from this package too
|
||||
*/
|
||||
public static void registerIgnorePackage(final String packageString, final boolean ignoreCauseClasses) {
|
||||
AbstractExceptions.registerIgnorePackage(packageString, ignoreCauseClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clears previously registered ignore packages.
|
||||
*/
|
||||
public static void clearIgnorePackages() {
|
||||
AbstractExceptions.clearIgnorePackages();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the value of ignore all causes option.
|
||||
*
|
||||
* @return the value of global ignore all causes option. If value is true then stack trace elements printed or
|
||||
* converted to string will not include causes. If value is false causes will be appended to the stack trace of
|
||||
* the main throwable.
|
||||
*/
|
||||
public static boolean getIgnoreAllCauses() {
|
||||
return AbstractExceptions.getIgnoreAllCauses();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the value of ignore all causes option.
|
||||
*
|
||||
* @param ignoreAllCauses new global ignore all causes option. If value is true then stack trace elements printed
|
||||
* or converted to string will not include causes. If value is false causes will be appended to the stack trace of
|
||||
* the main throwable.
|
||||
*/
|
||||
public static void setIgnoreAllCauses(final boolean ignoreAllCauses) {
|
||||
AbstractExceptions.setIgnoreAllCauses(ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code>.
|
||||
*
|
||||
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
|
||||
* by {@link #registerGroupPackage(String)} and ignore packages registered by
|
||||
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable) {
|
||||
return AbstractExceptions.getStackTraceString(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code>.
|
||||
*
|
||||
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
|
||||
* by {@link #registerGroupPackage(String)} and ignore packages registered by
|
||||
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param ignoreAllCauses ignore all causes in the exception chain
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final boolean ignoreAllCauses) {
|
||||
return AbstractExceptions.getStackTraceString(throwable, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param rootPackageSet root packages to use for building the stack trace
|
||||
* @param groupPackageSet group packages to use for building the stack trace
|
||||
* @param ignorePackageSet ignore packages to use for building the stack trace
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet) {
|
||||
return AbstractExceptions.getStackTraceString(throwable, rootPackageSet, groupPackageSet, ignorePackageSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param rootPackageSet root packages to use for building the stack trace
|
||||
* @param groupPackageSet group packages to use for building the stack trace
|
||||
* @param ignorePackageSet ignore packages to use for building the stack trace
|
||||
* @param ignoreAllCauses ignore all causes in the exception chain
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final boolean ignoreAllCauses) {
|
||||
return AbstractExceptions.getStackTraceString(throwable, rootPackageSet, groupPackageSet, ignorePackageSet, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param rootPackage root package to use for building the stack trace
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final String rootPackage) {
|
||||
return AbstractExceptions.getStackTraceString(throwable, rootPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param rootPackage root package to use for building the stack trace
|
||||
* @param groupPackage group package to use for building the stack trace
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final String rootPackage, final String groupPackage) {
|
||||
return AbstractExceptions.getStackTraceString(throwable, rootPackage, groupPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param maxDepth max depth in exception chain that will be used
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final int maxDepth) {
|
||||
return AbstractExceptions.getStackTraceString(throwable, maxDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param maxDepth max depth in exception chain that will be used
|
||||
* @param ignoreAllCauses ignore all causes in the exception chain
|
||||
* @return a string containing the smart stack trace for the given <code>throwable</code>
|
||||
*/
|
||||
public static String getStackTraceString(final Throwable throwable, final int maxDepth, final boolean ignoreAllCauses) {
|
||||
return AbstractExceptions.getStackTraceString(throwable, maxDepth, ignoreAllCauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns all messages found in the exception chain of the <code>throwable</code> as a single string.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @return a string containing all messages found in the exception chain
|
||||
*/
|
||||
public static String getAllMessages(final Throwable throwable) {
|
||||
return AbstractExceptions.getAllMessages(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns true if the given cause class is found in the exception chain of the <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
|
||||
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @return true if the given cause class is found in the exception chain of the <code>throwable</code>, false
|
||||
* otherwise
|
||||
*/
|
||||
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass) {
|
||||
return AbstractExceptions.containsCause(throwable, causeClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns true if the given cause class and cause message is found in the exception chain of the
|
||||
* <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
|
||||
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @param causeMessage message to search for
|
||||
* @return true if the given cause class and cause message is found in the exception chain of the
|
||||
* <code>throwable</code>, false otherwise
|
||||
*/
|
||||
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
|
||||
return AbstractExceptions.containsCause(throwable, causeClass, causeMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @return the cause of the <code>throwable</code> found or null if <code>throwable</code> does not have a cause
|
||||
*/
|
||||
public static Throwable getCause(final Throwable throwable) {
|
||||
return AbstractExceptions.getCause(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Note that this method walks through the exception chain up to given <code>maxDepth</code>. If exception chain
|
||||
* includes more items than <code>maxDepth</code>, cause found at <code>maxDepth</code> level is returned.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param maxDepth max depth in exception chain that will be searched
|
||||
* @return the cause of the <code>throwable</code> found until <code>maxDepth</code> is reached in the exception
|
||||
* chain or null if <code>throwable</code> does not have a cause
|
||||
*/
|
||||
public static Throwable getCause(final Throwable throwable, final int maxDepth) {
|
||||
return AbstractExceptions.getCause(throwable, maxDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
|
||||
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
|
||||
*/
|
||||
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass) {
|
||||
return AbstractExceptions.searchCause(throwable, causeClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
|
||||
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @param causeMessage message to search for
|
||||
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
|
||||
* and cause message
|
||||
*/
|
||||
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
|
||||
return AbstractExceptions.searchCause(throwable, causeClass, causeMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
|
||||
* <code>maxDepth</code>.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @param causeMessage message to search for
|
||||
* @param maxDepth max depth in exception chain that will be searched
|
||||
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
|
||||
* and cause message
|
||||
*/
|
||||
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage, final int maxDepth) {
|
||||
return AbstractExceptions.searchCause(throwable, causeClass, causeMessage, maxDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
|
||||
* <br>
|
||||
* <br>
|
||||
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
|
||||
* <code>maxDepth</code>.
|
||||
*
|
||||
* @param throwable parent throwable
|
||||
* @param causeClass class to search for
|
||||
* @param maxDepth max depth in exception chain that will be searched
|
||||
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
|
||||
*/
|
||||
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final int maxDepth) {
|
||||
return AbstractExceptions.searchCause(throwable, causeClass, maxDepth);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,8 +24,8 @@ public class MediaImageEncoder {
|
||||
return compressBitmap(bitmap, file, CompressFormat.JPEG, quality);
|
||||
}
|
||||
|
||||
private static boolean compressBitmapLossless(Bitmap bitmap, File file) {
|
||||
return compressBitmap(bitmap, file, CompressFormat.PNG, 100);
|
||||
public static boolean needToCompress() {
|
||||
return Preferences.getPreferences().getBoolean("compressPhotos", true);
|
||||
}
|
||||
|
||||
private static boolean compressBitmapNative(Bitmap bitmap, File file, int quality) {
|
||||
@@ -37,7 +37,7 @@ public class MediaImageEncoder {
|
||||
byte[] jpegData = MediaNative.compressBitmapJpeg(bitmap, quality);
|
||||
|
||||
if (jpegData == null) {
|
||||
return false;
|
||||
return compressBitmapJpeg(bitmap, file, quality);
|
||||
}
|
||||
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
|
||||
@@ -51,43 +51,21 @@ public class MediaImageEncoder {
|
||||
|
||||
public static boolean encodePicture(Bitmap bitmap, File file, int quality) {
|
||||
if (bitmap != null && !bitmap.isRecycled() && bitmap.getWidth() * bitmap.getHeight() != 0) {
|
||||
switch (Preferences.getPreferences().getString("compression", "jpeg")) {
|
||||
case "skip" -> {
|
||||
return compressBitmapLossless(bitmap, file);
|
||||
}
|
||||
case "jpeg" -> {
|
||||
return compressBitmapJpeg(bitmap, file, quality);
|
||||
}
|
||||
case "native" -> {
|
||||
return compressBitmapNative(bitmap, file, quality);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean needToSkipCompression() {
|
||||
return Preferences.getPreferences().getString("compression", "jpeg").equals("skip");
|
||||
}
|
||||
|
||||
public static boolean needToCompress() {
|
||||
return !needToSkipCompression();
|
||||
}
|
||||
|
||||
public static boolean encodeJpeg(Bitmap bitmap, File file) {
|
||||
return encodePicture(bitmap, file, 90);
|
||||
}
|
||||
|
||||
public static boolean encodeJpegWithoutCompression(Bitmap bitmap, File file) {
|
||||
return encodePicture(bitmap, file, 95);
|
||||
}
|
||||
|
||||
public static boolean encodeJpeg(Bitmap bitmap, File file, int quality) {
|
||||
if (bitmap != null && !bitmap.isRecycled() && bitmap.getWidth() * bitmap.getHeight() != 0) {
|
||||
return needToCompress() ? compressBitmapNative(bitmap, file, quality) : compressBitmapJpeg(bitmap, file, quality);
|
||||
return compressBitmapNative(bitmap, file, quality);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean encodeJpeg(Bitmap bitmap, File file) {
|
||||
return encodePicture(bitmap, file, Preferences.compress(85));
|
||||
}
|
||||
|
||||
public static boolean encodeJpegWithoutCompression(Bitmap bitmap, File file) {
|
||||
return encodePicture(bitmap, file, 100);
|
||||
}
|
||||
|
||||
public static boolean encodeJpeg(Bitmap bitmap, File file, int quality) {
|
||||
return encodePicture(bitmap, file, Preferences.compress(85));
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import android.util.Log;
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import ru.vtosters.sponsorpost.internal.Native;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
@@ -249,7 +248,6 @@ public final class MediaNative {
|
||||
if (isMediaLoaded) {
|
||||
return;
|
||||
}
|
||||
NativeLibLoader.loadLibrary("ffmpeg");
|
||||
isMediaLoaded = NativeLibLoader.loadLibrary("vkmedia");
|
||||
isMediaLoaded = NativeLibLoader.loadLibrary("vkmediaencoder");
|
||||
}
|
||||
@@ -275,7 +273,6 @@ public final class MediaNative {
|
||||
SoLoader.init(context2, false);
|
||||
NativeLibLoader.loadLibrary("gnustl_shared");
|
||||
NativeLibLoader.loadLibrary("vkchronicle");
|
||||
Native.canVote = NativeLibLoader.loadLibrary("sponsorpost");
|
||||
loadVKMediaLibs();
|
||||
f17443d.notifyAll();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
package com.vk.music.stats;
|
||||
|
||||
import android.content.Intent;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import com.vk.core.extensions.StringExt;
|
||||
import com.vk.music.common.MusicPlaybackLaunchContext;
|
||||
import com.vk.music.logger.MusicLogger;
|
||||
import com.vk.music.stats.i.MusicPlaybackParams;
|
||||
import com.vk.navigation.NavigatorKeys;
|
||||
import com.vtosters.lite.data.Analytics;
|
||||
import kotlin.jvm.b.Functions2;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.music.Metrics;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class VKMusicStatsTracker implements MusicStatsTracker {
|
||||
private String a;
|
||||
private final Functions2<String, Analytics.l> b;
|
||||
private static final a c = new a();
|
||||
|
||||
public VKMusicStatsTracker(Functions2<String, Analytics.l> functions2) {
|
||||
this.b = functions2;
|
||||
}
|
||||
|
||||
private String e(MusicPlaybackParams musicPlaybackParams) {
|
||||
String g = musicPlaybackParams.g();
|
||||
if (g == null) {
|
||||
g = this.a;
|
||||
}
|
||||
if (g == null) {
|
||||
g = "auto";
|
||||
}
|
||||
this.a = g;
|
||||
return g;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(boolean z) {
|
||||
MusicStatsTracker.a.a(this, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void b(MusicPlaybackParams musicPlaybackParams) {
|
||||
c.a(a(musicPlaybackParams, "music_start_playback"));
|
||||
|
||||
if (Preferences.sendMusicMetrics()) {
|
||||
try {
|
||||
makeMetricsRequest(musicPlaybackParams, "music_start_playback");
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void b(boolean z) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void c(MusicPlaybackParams musicPlaybackParams) {
|
||||
MusicStatsTracker.a.b(this, musicPlaybackParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void d() {
|
||||
MusicStatsTracker.a.a(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(Intent intent, String str) {
|
||||
MusicStatsTracker.a.a(this, intent, str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void b(String str, @NonNull String str2) {
|
||||
Analytics.l invoke = this.b.invoke("music_subscription_action");
|
||||
invoke.a("popup", str);
|
||||
invoke.a("action", str2);
|
||||
c.a(invoke);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void c(boolean z) {
|
||||
MusicStatsTracker.a.b(this, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void d(MusicPlaybackParams musicPlaybackParams) {
|
||||
if (Objects.equals("pause", this.a)) {
|
||||
a(musicPlaybackParams);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(long j) {
|
||||
MusicStatsTracker.a.a(this, j);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void c() {
|
||||
Analytics.l invoke = this.b.invoke("music_subscription_push");
|
||||
invoke.a("action", "open");
|
||||
c.a(invoke);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(String str, MusicStatsRefer musicStatsRefer) {
|
||||
Analytics.l invoke = this.b.invoke("music_subscription_show");
|
||||
invoke.a("popup", str);
|
||||
c.a(invoke);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void b() {
|
||||
Analytics.l invoke = this.b.invoke("music_subscription_push");
|
||||
invoke.a("action", "send");
|
||||
c.a(invoke);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(MusicPlaybackParams musicPlaybackParams) {
|
||||
c.a(a(musicPlaybackParams, "music_stop_playback"));
|
||||
|
||||
if (Preferences.sendMusicMetrics()) {
|
||||
try {
|
||||
makeMetricsRequest(musicPlaybackParams, "music_stop_playback");
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(PlayerStateChangedParams playerStateChangedParams) {
|
||||
Analytics.l invoke = this.b.invoke("audio_player");
|
||||
invoke.a(NavigatorKeys.t0, playerStateChangedParams.a());
|
||||
invoke.a("prev_state", playerStateChangedParams.c());
|
||||
invoke.a("duration", playerStateChangedParams.b());
|
||||
c.a(invoke);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void b(String str) {
|
||||
Analytics.l invoke = this.b.invoke("music_subscription_free_displayed");
|
||||
invoke.a("popup", str);
|
||||
c.a(invoke);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(String str) {
|
||||
Analytics.l invoke = this.b.invoke("playlist_start");
|
||||
invoke.a(NavigatorKeys.e, str);
|
||||
c.a(invoke);
|
||||
}
|
||||
|
||||
private void makeMetricsRequest(MusicPlaybackParams musicPlaybackParams, String str) throws JSONException {
|
||||
JSONArray events = new JSONArray();
|
||||
JSONObject event = new JSONObject();
|
||||
|
||||
if (str.equals("music_stop_playback")) {
|
||||
event.put("e", str);
|
||||
event.put("audio_id", musicPlaybackParams.c());
|
||||
event.put("start_time", "0");
|
||||
event.put("shuffle", "false");
|
||||
event.put("reason", "new");
|
||||
event.put("state", "app");
|
||||
event.put("duration", musicPlaybackParams.a());
|
||||
events.put(event);
|
||||
} else {
|
||||
event.put("e", str);
|
||||
event.put("audio_id", musicPlaybackParams.c());
|
||||
event.put("start_time", "0");
|
||||
event.put("shuffle", "false");
|
||||
event.put("reason", "auto");
|
||||
event.put("state", "app");
|
||||
|
||||
if (StringExt.a((CharSequence) musicPlaybackParams.e())) {
|
||||
event.put("prev_audio_id", musicPlaybackParams.e());
|
||||
}
|
||||
|
||||
events.put(event);
|
||||
}
|
||||
|
||||
Metrics.trackEvents(events.toString());
|
||||
}
|
||||
|
||||
private Analytics.l a(MusicPlaybackParams musicPlaybackParams, String str) {
|
||||
Analytics.l invoke = this.b.invoke(str);
|
||||
invoke.a("audio_id", musicPlaybackParams.c());
|
||||
invoke.a("uuid", UUID.randomUUID().hashCode());
|
||||
invoke.a("shuffle", musicPlaybackParams.l());
|
||||
invoke.a("reason", e(musicPlaybackParams));
|
||||
invoke.a("start_time", musicPlaybackParams.i());
|
||||
invoke.a("playback_started_at", musicPlaybackParams.d());
|
||||
invoke.a(NavigatorKeys.l0, musicPlaybackParams.j());
|
||||
if (!Objects.equals("music_start_playback", str)) {
|
||||
invoke.a("duration", musicPlaybackParams.a());
|
||||
}
|
||||
int i = h.$EnumSwitchMapping$0[musicPlaybackParams.b().ordinal()];
|
||||
if (i == 1) {
|
||||
invoke.a("repeat", "one");
|
||||
} else if (i == 2) {
|
||||
invoke.a("repeat", "all");
|
||||
}
|
||||
invoke.a(NavigatorKeys.t0, musicPlaybackParams.m());
|
||||
MusicPlaybackLaunchContext h = musicPlaybackParams.h();
|
||||
invoke.a(NavigatorKeys.V, h.v0());
|
||||
if (h.v1()) {
|
||||
invoke.a("playlist_id", h.u1());
|
||||
}
|
||||
if (h.i(4) || h.i(8)) {
|
||||
invoke.a("expanded", h.i(4));
|
||||
}
|
||||
if (StringExt.a((CharSequence) musicPlaybackParams.e())) {
|
||||
invoke.a("prev_audio_id", musicPlaybackParams.e());
|
||||
}
|
||||
if (StringExt.a((CharSequence) musicPlaybackParams.f())) {
|
||||
invoke.a("prev_playlist_id", musicPlaybackParams.f());
|
||||
}
|
||||
return invoke;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(String str, String str2) {
|
||||
boolean a2 = Objects.equals("success", str2);
|
||||
Analytics.l builder = this.b.invoke("music_subscription_purchase_result");
|
||||
builder.a("popup", str);
|
||||
builder.a(NotificationCompat.CATEGORY_STATUS, a2 ? "success" : "fail");
|
||||
if (!a2) {
|
||||
builder.a("reason", str2);
|
||||
}
|
||||
c.a(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a() {
|
||||
Analytics.l invoke = this.b.invoke("music_subscription");
|
||||
invoke.a("popup", "background_v2");
|
||||
invoke.a("action", "link");
|
||||
c.a(invoke);
|
||||
}
|
||||
|
||||
public static final class a {
|
||||
private a() {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void a(Analytics.l lVar) {
|
||||
MusicLogger.a("MusicStats", "[VK_TRACKER]", lVar);
|
||||
lVar.b();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,12 @@ import com.vk.im.engine.models.messages.NestedMsg;
|
||||
import com.vk.libsqliteext.CustomSqliteExtensionsKt;
|
||||
import io.requery.android.database.sqlite.SQLiteDatabase;
|
||||
import io.requery.android.database.sqlite.SQLiteOpenHelper;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
||||
import static ru.vtosters.hooks.other.Preferences.getString;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
||||
|
||||
public class DeletedMessagesHook {
|
||||
@@ -69,7 +69,7 @@ public class DeletedMessagesHook {
|
||||
}
|
||||
|
||||
private static String getPrefixUndelete() {
|
||||
var prefs = getString("undeletemsg_prefix_value");
|
||||
var prefs = Preferences.getPreferences().getString("undeletemsg_prefix_value", "🗑");
|
||||
|
||||
if (prefs.isEmpty()) {
|
||||
return "\uD83D\uDDD1 ";
|
||||
@@ -78,7 +78,7 @@ public class DeletedMessagesHook {
|
||||
return prefs + " ";
|
||||
}
|
||||
|
||||
public static void updateDialog(MsgDeleteLpTask msgDeleteLpTask) throws NoSuchFieldException, IllegalAccessException {
|
||||
public static void updateDialog(MsgDeleteLpTask msgDeleteLpTask) {
|
||||
var cursor = getMessageFromDatabaseById(msgDeleteLpTask.d);
|
||||
if (cursor == null) return;
|
||||
|
||||
@@ -157,4 +157,4 @@ public class DeletedMessagesHook {
|
||||
return msgIdsList;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,8 +72,9 @@ public class EncryptionMessagesHook {
|
||||
public static String decryptMessage(String msgBody, int peer) {
|
||||
try {
|
||||
for (IMProcessor processor : EncryptProvider.processors) {
|
||||
if ((processor.isUsed() || !processor.isPublic()) && processor.isEncrypted(msgBody) && (processor.isPublic() || EncryptProvider.getKeyForProcessor(processor, peer) != null))
|
||||
return "\uD83D\uDD12 " + injectOwnTextAll(processor.decode(msgBody, EncryptProvider.getKeyForProcessor(processor, peer)));
|
||||
if ((processor.isUsed() || !processor.isPublic()) && processor.isEncrypted(msgBody) && (processor.isPublic() || EncryptProvider.getKeyForProcessor(processor, peer) != null)) {
|
||||
return "\uD83D\uDD12 " + processor.getUIName() + ": " + injectOwnTextAll(processor.decode(msgBody, EncryptProvider.getKeyForProcessor(processor, peer)));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package ru.vtosters.hooks;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import com.vk.dto.gift.GiftCategory;
|
||||
import com.vtosters.lite.TabletDialogActivity;
|
||||
import com.vtosters.lite.fragments.gifts.GiftCategoryFragment;
|
||||
import ru.vtosters.lite.ui.fragments.SuperAppEditorFragment;
|
||||
import ru.vtosters.lite.utils.NavigatorUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GiftsFix {
|
||||
public static Bundle staticBundle;
|
||||
|
||||
public static void openGiftCategoryFragment(Activity activity, ArrayList<Integer> userIds, GiftCategory giftCategory, int balance, String reference) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putIntegerArrayList("uids", userIds);
|
||||
bundle.putParcelable("data", giftCategory);
|
||||
bundle.putInt("balance", balance);
|
||||
bundle.putString("ref", reference);
|
||||
|
||||
staticBundle = bundle;
|
||||
|
||||
NavigatorUtils.switchFragment(activity, GiftCategoryFragment.class);
|
||||
}
|
||||
|
||||
public static Bundle getBundle() {
|
||||
return staticBundle;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.os.Build;
|
||||
import b.h.g.k.VKProgressDialog;
|
||||
import com.aefyr.tsg.g2.TelegramStickersService;
|
||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||
import com.vk.medianative.NativeLibLoader;
|
||||
import com.vtosters.lite.R;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.hooks.other.ThemesUtils;
|
||||
@@ -17,10 +18,8 @@ import ru.vtosters.lite.themes.ThemesManager;
|
||||
import ru.vtosters.lite.ui.dialogs.InstallGMS;
|
||||
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
||||
import ru.vtosters.lite.ui.dialogs.Start;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||
import ru.vtosters.lite.utils.NavigatorUtils;
|
||||
import ru.vtosters.lite.utils.VTVerifications;
|
||||
import ru.vtosters.lite.utils.*;
|
||||
import ru.vtosters.sponsorpost.internal.Native;
|
||||
import ru.vtosters.sponsorpost.utils.Updates;
|
||||
|
||||
import static ru.vtosters.hooks.other.Preferences.checkupdates;
|
||||
@@ -67,6 +66,10 @@ public class MainActivityInjector {
|
||||
// VKIDProtection.alert(activity);
|
||||
//needs to show selected tgs pack count in settings after cold launch
|
||||
TelegramStickersService.getInstance(activity);
|
||||
|
||||
if (AccountManagerUtils.isLogin() && !Preferences.serverFeaturesDisable() && Preferences.isValidSignature()) {
|
||||
Native.canVote = NativeLibLoader.loadLibrary("sponsorpost");
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateBinsAndTmpArchive(Activity activity) {
|
||||
@@ -80,7 +83,7 @@ public class MainActivityInjector {
|
||||
ThemesManager.generateModApk(ThemesUtils.getReservedAccent());
|
||||
activity.runOnUiThread(LifecycleUtils::restartApplication);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
e.fillInStackTrace();
|
||||
activity.runOnUiThread(() -> {
|
||||
dialog.cancel();
|
||||
new VkAlertDialog.Builder(activity)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package ru.vtosters.hooks;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import com.vk.core.network.Network;
|
||||
import com.vk.im.engine.internal.api_parsers.UserApiParser;
|
||||
import com.vk.im.engine.models.users.User;
|
||||
import com.vtosters.lite.R;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
@@ -10,8 +13,6 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.proxy.ProxyUtils;
|
||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -146,16 +147,36 @@ public class OnlineFormatterHook {
|
||||
return json;
|
||||
}
|
||||
|
||||
public static JSONArray onlineHookList(JSONArray jsonArr) {
|
||||
if (!getBoolValue("onlinefix", false) || Preferences.serverFeaturesDisable()) return jsonArr;
|
||||
|
||||
public static SparseArray<User> onlineHookList(String str) {
|
||||
JSONArray jSONArray;
|
||||
try {
|
||||
JsonInjectors.setOnlineInfoUsers(jsonArr);
|
||||
} catch (Exception e) {
|
||||
Log.e("onlineHookProfiles", e.getMessage());
|
||||
jSONArray = new JSONObject(str).getJSONArray("response");
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
SparseArray<User> sparseArray = new SparseArray<>(jSONArray.length());
|
||||
int length = jSONArray.length();
|
||||
|
||||
if (getBoolValue("onlinefix", false) || !Preferences.serverFeaturesDisable()) {
|
||||
try {
|
||||
jSONArray = JsonInjectors.setOnlineInfoUsers(jSONArray);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return jsonArr;
|
||||
for (int i = 0; i < length; i++) {
|
||||
JSONObject jSONObject;
|
||||
try {
|
||||
jSONObject = jSONArray.getJSONObject(i);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
User b = UserApiParser.b(jSONObject);
|
||||
sparseArray.put(b.getId(), b);
|
||||
}
|
||||
|
||||
return sparseArray;
|
||||
}
|
||||
|
||||
public static JSONObject onlineHookProfiles(JSONObject json) {
|
||||
@@ -188,19 +209,19 @@ public class OnlineFormatterHook {
|
||||
try {
|
||||
JsonInjectors.setOnlineInfoUsers(json.optJSONObject("read_requests").optJSONArray("items"));
|
||||
} catch (Exception e) {
|
||||
Log.e("onlineHookItems", e.getMessage());
|
||||
Log.e("onlineHookItemsRec", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
JsonInjectors.setOnlineInfoUsers(json.optJSONObject("recommendations").optJSONArray("items"));
|
||||
} catch (Exception e) {
|
||||
Log.e("onlineHookItems", e.getMessage());
|
||||
Log.e("onlineHookItemsRec", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
JsonInjectors.setOnlineInfoUsers(json.optJSONArray("profiles"));
|
||||
} catch (Exception e) {
|
||||
Log.e("onlineHookItems", e.getMessage());
|
||||
Log.e("onlineHookItemsRec", e.getMessage());
|
||||
}
|
||||
|
||||
return json;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package ru.vtosters.hooks;
|
||||
|
||||
import android.os.Build;
|
||||
import me.leolin.shortcutbadger.Badger;
|
||||
import me.leolin.shortcutbadger.impl.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ShortcutBadgerHook {
|
||||
public static void init(List<Class<? extends Badger>> list) {
|
||||
list.add(AdwHomeBadger.class);
|
||||
list.add(ApexHomeBadger.class);
|
||||
list.add(DefaultBadger.class);
|
||||
list.add(NewHtcHomeBadger.class);
|
||||
list.add(NovaHomeBadger.class);
|
||||
list.add(AsusHomeBadger.class);
|
||||
list.add(HuaweiHomeBadger.class);
|
||||
list.add(OPPOHomeBader.class);
|
||||
list.add(SamsungHomeBadger.class);
|
||||
list.add(ZukHomeBadger.class);
|
||||
list.add(VivoHomeBadger.class);
|
||||
list.add(ZTEHomeBadger.class);
|
||||
list.add(EverythingMeHomeBadger.class);
|
||||
|
||||
if (Build.VERSION.SDK_INT < 34) {
|
||||
list.add(SonyHomeBadger.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,17 @@ package ru.vtosters.hooks;
|
||||
import com.vk.medianative.MediaImageEncoder;
|
||||
import com.vk.toggle.FeatureManager;
|
||||
import com.vk.toggle.Features;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
|
||||
import static ru.vtosters.hooks.other.Preferences.*;
|
||||
|
||||
public class TogglesHook {
|
||||
public static boolean shouldPatch(Features.Type feature) {
|
||||
return switch (feature) {
|
||||
case AB_MULTI_GIFTS, AB_NEWS_VIDEO_LAYOUT_TEXT, AB_STICKERS_DISCOVER, AB_GIFTS_FROM_KEYBOARD, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_DISCOVER_CATEGORIES, FEATURE_FAVE_PERF_IMPROVE, FEATURE_GROUP_ADMIN_MESSAGES, FEATURE_GROUP_MSG_PUSH_PARAM, FEATURE_IMAGE_QUALITY_UPGRADE, FEATURE_IM_AUDIO_MSG_TRANSCRIPT, FEATURE_IM_CASPER_MSGS, FEATURE_IM_DISABLE_FORCE_OPEN_VK_ME, FEATURE_IM_GIF_AUTOPLAY, FEATURE_IM_MR_IN_VKAPP, FEATURE_MEMORIES_ENABLED, FEATURE_MENU_GAMES_CAROUSEL, FEATURE_MILKSHAKE, FEATURE_MILKSHAKE_NEWS_SCROLL_ON_BACK, FEATURE_MILKSHAKE_SWITCH_THEME_ON_TAP, FEATURE_ML_BRANDS, FEATURE_ML_FEATURES, FEATURE_ML_MODELS_LOADING, FEATURE_MONEY_TRANSFERS_VKPAY, FEATURE_MUSIC_ARTIST_CATALOG, FEATURE_MUSIC_NEW_CATALOG, FEATURE_NARRATIVE_SNIPPET_TYPE, FEATURE_NEWS_GAMES_IN_DISCOVER, FEATURE_NEWS_HEADER_SCROLL, FEATURE_NEW_FORMAT_SHOW_WPB, FEATURE_PODCASTS_PAGE, FEATURE_QR_CREATE_QR, FEATURE_QR_DYNAMIC_FRAME, FEATURE_QR_SCAN_FROM_PHOTO, FEATURE_QR_VISION_DECODER, FEATURE_QUEUE_COUNTERS, FEATURE_REEF, FEATURE_ROAMING_AUTOPLAY, FEATURE_SHARE_MSGS_ON_INVITE, FEATURE_STICKERS_BOT_LINK, FEATURE_STICKERS_NEW_CATALOG, FEATURE_STORIES_AVATAR, FEATURE_STORIES_FEED_TYPE, FEATURE_STORIES_SHOW_ALWAYS, FEATURE_STORY_ANSWER_PUBLIC, FEATURE_STORY_APP_STICKER, FEATURE_STORY_ARCHIVE, FEATURE_STORY_ARCHIVE_POST, FEATURE_STORY_BAKGROUND_ALL, FEATURE_STORY_BOX, FEATURE_STORY_CADRE, FEATURE_STORY_CAMERA_TOOLTIP, FEATURE_STORY_EDITOR_GALLERY, FEATURE_STORY_EDITOR_TYPE, FEATURE_STORY_ENDLESS_VIDEO, FEATURE_STORY_FAST_REACTIONS, FEATURE_STORY_GIF, FEATURE_STORY_GROUPED, FEATURE_STORY_HIGH_QUALITY, FEATURE_STORY_MASKS_FRONTAL, FEATURE_STORY_MENTION_ICON, FEATURE_STORY_MODERN_PUBLISH, FEATURE_STORY_MUSIC_EDITOR, FEATURE_STORY_MUSIC_REPLIES, FEATURE_STORY_MUSIC_REPLIES_2, FEATURE_STORY_NEW_FRAME, FEATURE_STORY_ONE_TIME, FEATURE_STORY_PHOTO_SHARING, FEATURE_STORY_PHOTO_STICKER, FEATURE_STORY_POLLS, FEATURE_STORY_POST_REPOST, FEATURE_STORY_PRELOADING, FEATURE_STORY_QUESTION, FEATURE_STORY_QUESTION_STYLE, FEATURE_STORY_RLOTTIE, FEATURE_STORY_RLOTTIE_CACHE, FEATURE_STORY_SAVE_DEFAULT_ON, FEATURE_STORY_SEND_DIALOG_LIST, FEATURE_STORY_STICKER_PACK, FEATURE_STORY_TIME, FEATURE_STORY_VIEWER_CAMERA, FEATURE_STORY_VOICE_ANSWER, FEATURE_STORY_WITH_CONTEXT, FEATURE_SUPERAPP_MENU, FEATURE_TRAFFIC_SAVER, FEATURE_VKPAY_WIDGET, FEATURE_VKUI_INTERNAL_TO_MINI_APPS, FEATURE_VKUI_TOKEN_CACHE, FEATURE_VK_APPS_SEARCH, FEATURE_VOTES_BALANCE, FEATURE_WEBVIEW_TOKEN_ACTIVE, FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED, EXPERIMENT_NEWS_DISABLE_CACHE ->
|
||||
case AB_IM_LONGPOLL_MSG_BATCHING, AB_IM_VIEW_POOL, AB_MULTI_GIFTS, AB_NEWS_VIDEO_LAYOUT_TEXT, AB_STICKERS_DISCOVER, AB_GIFTS_FROM_KEYBOARD, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_DISCOVER_CATEGORIES, FEATURE_FAVE_PERF_IMPROVE, FEATURE_GROUP_ADMIN_MESSAGES, FEATURE_GROUP_MSG_PUSH_PARAM, FEATURE_IMAGE_QUALITY_UPGRADE, FEATURE_IM_AUDIO_MSG_TRANSCRIPT, FEATURE_IM_CASPER_MSGS, FEATURE_IM_DISABLE_FORCE_OPEN_VK_ME, FEATURE_IM_GIF_AUTOPLAY, FEATURE_IM_MR_IN_VKAPP, FEATURE_MEMORIES_ENABLED, FEATURE_MENU_GAMES_CAROUSEL, FEATURE_MILKSHAKE, FEATURE_MILKSHAKE_NEWS_SCROLL_ON_BACK, FEATURE_MILKSHAKE_SWITCH_THEME_ON_TAP, FEATURE_ML_BRANDS, FEATURE_ML_FEATURES, FEATURE_ML_MODELS_LOADING, FEATURE_MONEY_TRANSFERS_VKPAY, FEATURE_MUSIC_ARTIST_CATALOG, FEATURE_MUSIC_NEW_CATALOG, FEATURE_NARRATIVE_SNIPPET_TYPE, FEATURE_NEWS_GAMES_IN_DISCOVER, FEATURE_NEWS_HEADER_SCROLL, FEATURE_NEW_FORMAT_SHOW_WPB, FEATURE_PODCASTS_PAGE, FEATURE_QR_CREATE_QR, FEATURE_QR_DYNAMIC_FRAME, FEATURE_QR_SCAN_FROM_PHOTO, FEATURE_QR_VISION_DECODER, FEATURE_QUEUE_COUNTERS, FEATURE_REEF, FEATURE_ROAMING_AUTOPLAY, FEATURE_SHARE_MSGS_ON_INVITE, FEATURE_STICKERS_BOT_LINK, FEATURE_STICKERS_NEW_CATALOG, FEATURE_STORIES_AVATAR, FEATURE_STORIES_FEED_TYPE, FEATURE_STORIES_SHOW_ALWAYS, FEATURE_STORY_ANSWER_PUBLIC, FEATURE_STORY_APP_STICKER, FEATURE_STORY_ARCHIVE, FEATURE_STORY_ARCHIVE_POST, FEATURE_STORY_BAKGROUND_ALL, FEATURE_STORY_BOX, FEATURE_STORY_CADRE, FEATURE_STORY_CAMERA_TOOLTIP, FEATURE_STORY_EDITOR_GALLERY, FEATURE_STORY_EDITOR_TYPE, FEATURE_STORY_ENDLESS_VIDEO, FEATURE_STORY_FAST_REACTIONS, FEATURE_STORY_GIF, FEATURE_STORY_GROUPED, FEATURE_STORY_HIGH_QUALITY, FEATURE_STORY_MASKS_FRONTAL, FEATURE_STORY_MENTION_ICON, FEATURE_STORY_MODERN_PUBLISH, FEATURE_STORY_MUSIC_EDITOR, FEATURE_STORY_MUSIC_REPLIES, FEATURE_STORY_MUSIC_REPLIES_2, FEATURE_STORY_NEW_FRAME, FEATURE_STORY_ONE_TIME, FEATURE_STORY_PHOTO_SHARING, FEATURE_STORY_PHOTO_STICKER, FEATURE_STORY_POLLS, FEATURE_STORY_POST_REPOST, FEATURE_STORY_PRELOADING, FEATURE_STORY_QUESTION, FEATURE_STORY_QUESTION_STYLE, FEATURE_STORY_RLOTTIE, FEATURE_STORY_RLOTTIE_CACHE, FEATURE_STORY_SAVE_DEFAULT_ON, FEATURE_STORY_SEND_DIALOG_LIST, FEATURE_STORY_STICKER_PACK, FEATURE_STORY_TIME, FEATURE_STORY_VIEWER_CAMERA, FEATURE_STORY_VOICE_ANSWER, FEATURE_STORY_WITH_CONTEXT, FEATURE_SUPERAPP_MENU, FEATURE_TRAFFIC_SAVER, FEATURE_VKPAY_WIDGET, FEATURE_VKUI_INTERNAL_TO_MINI_APPS, FEATURE_VKUI_TOKEN_CACHE, FEATURE_VK_APPS_SEARCH, FEATURE_VOTES_BALANCE, FEATURE_WEBVIEW_TOKEN_ACTIVE, FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED, EXPERIMENT_NEWS_DISABLE_CACHE ->
|
||||
true;
|
||||
default -> false;
|
||||
default ->
|
||||
false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,9 +25,10 @@ public class TogglesHook {
|
||||
case FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED -> !milkshake();
|
||||
case AB_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_COMPACT_REPOST, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT ->
|
||||
postsredesign();
|
||||
case FEATURE_IMAGE_QUALITY_UPGRADE -> MediaImageEncoder.needToSkipCompression();
|
||||
case FEATURE_IMAGE_QUALITY_UPGRADE -> !MediaImageEncoder.needToCompress();
|
||||
case FEATURE_MENU_GAMES_CAROUSEL -> miniapps();
|
||||
case FEATURE_TRAFFIC_SAVER -> !disableForceTrafficSaver();
|
||||
case AB_IM_LONGPOLL_MSG_BATCHING, AB_IM_VIEW_POOL -> Preferences.getBoolValue("batchmessages", false);
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,9 +24,7 @@ public class VKUIHook {
|
||||
|
||||
private static void applyVKUIStyles(WebView webView) {
|
||||
if (getBoolValue("VKUI_INJ", true) && webView != null && webView.getUrl() != null && !webView.getUrl().contains("static.vk.com/memories")) {
|
||||
if (!WebViewColoringUtils.isLoaded) {
|
||||
WebViewColoringUtils.load();
|
||||
}
|
||||
WebViewColoringUtils.load();
|
||||
loadAndApplyCSS(webView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.vk.music.common.MusicPlaybackLaunchContext;
|
||||
import com.vtosters.lite.R;
|
||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.music.VKXUtils;
|
||||
|
||||
@@ -19,18 +20,29 @@ import static ru.vtosters.hooks.other.Preferences.milkshake;
|
||||
import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected;
|
||||
|
||||
public class MusicBottomSheetHook {
|
||||
public static ArrayList<MusicAction> hook(final ArrayList<MusicAction> actions, final MusicTrack musicTrack) {
|
||||
public static ArrayList<MusicAction> hook(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
|
||||
if (musicTrack.F1()) return actions;
|
||||
final var trackId = asId(musicTrack);
|
||||
|
||||
String trackId = asId(musicTrack);
|
||||
|
||||
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
|
||||
|
||||
if (isIntegrationEnabled()) {
|
||||
if (VKXUtils.isVkxCached(trackId)) actions.add(getRemoveCacheTrackVkxAction());
|
||||
else if (isNetworkConnected()) actions.add(addToCacheTrackVkxAction());
|
||||
if (VKXUtils.isVkxCached(trackId)) {
|
||||
actions.add(getRemoveCacheTrackVkxAction());
|
||||
} else if (isNetworkConnected()) {
|
||||
actions.add(addToCacheTrackVkxAction());
|
||||
}
|
||||
} else {
|
||||
if (MusicCacheImpl.isCachedTrack(trackId)) actions.add(getRemoveCacheTrackAction());
|
||||
else if (isNetworkConnected()) actions.add(addToCacheTrackAction());
|
||||
if (MusicCacheImpl.isCachedTrack(trackId)) {
|
||||
actions.add(getRemoveCacheTrackAction());
|
||||
} else if (isNetworkConnected()) {
|
||||
actions.add(addToCacheTrackAction());
|
||||
}
|
||||
}
|
||||
|
||||
if (isNetworkConnected()) actions.add(downloadAsMp3Action());
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
@@ -72,8 +84,12 @@ public class MusicBottomSheetHook {
|
||||
} else if (isNetworkConnected()) {
|
||||
actions.add(addToCacheTrackVkxAction());
|
||||
}
|
||||
} else if (isNetworkConnected()) {
|
||||
actions.add(addToCacheTrackAction());
|
||||
} else {
|
||||
if (PlaylistCacheDbDelegate.isCachedPlaylist(AndroidUtils.getGlobalContext(), playlist.v1())) {
|
||||
actions.add(getRemoveCacheTrackAction());
|
||||
} else if (isNetworkConnected()) {
|
||||
actions.add(addToCacheTrackAction());
|
||||
}
|
||||
}
|
||||
|
||||
if (isNetworkConnected()) actions.add(downloadAsMp3Action());
|
||||
@@ -134,20 +150,24 @@ public class MusicBottomSheetHook {
|
||||
}
|
||||
|
||||
public static boolean injectOnClick(int actionId, Playlist playlist) { // playlist inj
|
||||
if (actionId == R.id.play_in_vkx)
|
||||
if (actionId == R.id.play_in_vkx) {
|
||||
return tryPlayInVKX(null, null, playlist);
|
||||
|
||||
if (actionId == R.id.add_to_cache) {
|
||||
}
|
||||
|
||||
if (actionId == R.id.remove_from_cache) {
|
||||
PlaylistCacheDbDelegate.deletePlaylist(AndroidUtils.getGlobalContext(), playlist.v1());
|
||||
return true;
|
||||
} else if (actionId == R.id.add_to_cache) {
|
||||
AudioDownloader.cachePlaylist(playlist);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (actionId == R.id.remove_from_cache_vkx) {
|
||||
getInstance().runOnService(service -> {
|
||||
try {
|
||||
service.deletePlaylistFromCache(playlist.a, playlist.b);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
@@ -156,7 +176,7 @@ public class MusicBottomSheetHook {
|
||||
try {
|
||||
service.addPlaylistToCache(playlist.a, playlist.b, (playlist.Q != null) ? playlist.Q : "");
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
@@ -168,7 +188,7 @@ public class MusicBottomSheetHook {
|
||||
try {
|
||||
service.downloadPlaylist(playlist.a, playlist.b, (playlist.Q != null) ? playlist.Q : "");
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -258,4 +278,4 @@ public class MusicBottomSheetHook {
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,14 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.vk.core.network.Network.ClientType.CLIENT_API;
|
||||
import static ru.vtosters.hooks.DateHook.getLocale;
|
||||
@@ -44,25 +46,23 @@ public class CatalogJsonInjector {
|
||||
|
||||
fetchCatalogId("https://vk.com/audio?section=albums", oldItems);
|
||||
|
||||
if (Preferences.sendMusicMetrics()) {
|
||||
fetchCatalogId("https://vk.com/audio?section=recent", oldItems);
|
||||
}
|
||||
|
||||
setDefaultAudioPage(oldItems, catalog);
|
||||
}
|
||||
|
||||
if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
||||
var noPlaylists = !json.has("playlists");
|
||||
|
||||
if (noPlaylists) {
|
||||
json.put("playlists", new JSONArray().put(getPlaylist()));
|
||||
Log.d("catalogInjector", "added new pl");
|
||||
} else {
|
||||
json.optJSONArray("playlists").put(getPlaylist());
|
||||
Log.d("catalogInjector", "added to exist pl");
|
||||
}
|
||||
PlaylistHelper.addCachedPlaylists(json.optJSONArray("playlists"), noPlaylists);
|
||||
|
||||
if (!useOldAppVer || noPlaylists) {
|
||||
var newBlocks = new JSONArray();
|
||||
|
||||
newBlocks
|
||||
.put(getCatalogHeader())
|
||||
.put(getCatalogHeader("Кешированные плейлисты"))
|
||||
.put(getCatalogPlaylist())
|
||||
.put(getCatalogSeparator());
|
||||
|
||||
@@ -89,6 +89,7 @@ public class CatalogJsonInjector {
|
||||
var blocks = section.getJSONArray("blocks");
|
||||
|
||||
fixDailyMix(blocks);
|
||||
removeUnsupportedLayouts(blocks);
|
||||
|
||||
if (!isUsersCatalog || MusicCacheImpl.isEmpty() || LibVKXClient.isIntegrationEnabled()) {
|
||||
return; // early return if not users catalog or no tracks or integration enabled
|
||||
@@ -96,23 +97,13 @@ public class CatalogJsonInjector {
|
||||
|
||||
var noPlaylists = !json.has("playlists");
|
||||
|
||||
if (noPlaylists) {
|
||||
json.put("playlists", new JSONArray().put(getPlaylist()));
|
||||
Log.d("catalogInjector", "added new pl");
|
||||
} else {
|
||||
try {
|
||||
json.optJSONArray("playlists").put(getPlaylist());
|
||||
Log.d("catalogInjector", "added to exist pl");
|
||||
} catch (Exception e) {
|
||||
Log.d("catalogInjector", e.toString());
|
||||
}
|
||||
}
|
||||
PlaylistHelper.addCachedPlaylists(json.optJSONArray("playlists"), noPlaylists);
|
||||
|
||||
if (!useOldAppVer && !noPlaylists) {
|
||||
var newBlocks = new JSONArray();
|
||||
|
||||
newBlocks
|
||||
.put(getCatalogHeader())
|
||||
.put(getCatalogHeader("Кешированные плейлисты"))
|
||||
.put(getCatalogPlaylist())
|
||||
.put(getCatalogSeparator());
|
||||
|
||||
@@ -130,12 +121,15 @@ public class CatalogJsonInjector {
|
||||
|
||||
if (type.equals("music_playlists") && j.has("playlists_ids")) {
|
||||
var newarr = new JSONArray();
|
||||
var playlists_ids = j.optJSONArray("playlists_ids");
|
||||
var playlists_ids = j.getJSONArray("playlists_ids");
|
||||
var savedPlaylists = PlaylistHelper.getCachedPlaylistsIds();
|
||||
|
||||
newarr.put(getUserId() + "_-1");
|
||||
for (int n = 0; n < savedPlaylists.length(); n++) {
|
||||
newarr.put(savedPlaylists.getString(n));
|
||||
}
|
||||
|
||||
for (int n = 0; n < playlists_ids.length(); n++) {
|
||||
newarr.put(playlists_ids.optString(n));
|
||||
newarr.put(playlists_ids.getString(n));
|
||||
}
|
||||
|
||||
Log.d("catalogInjector", "added to pl ids");
|
||||
@@ -218,19 +212,33 @@ public class CatalogJsonInjector {
|
||||
}
|
||||
|
||||
private static void removeUnsupportedLayouts(JSONArray blocks) throws JSONException {
|
||||
for (int i = 0; i < blocks.length(); i++) {
|
||||
var block = blocks.optJSONObject(i);
|
||||
var layout = block.optJSONObject("layout");
|
||||
if (layout != null) {
|
||||
var name = layout.optString("name");
|
||||
if (name.equals("header_extended")) {
|
||||
if (layout.has("top_title")) blocks.remove(i);
|
||||
layout.put("name", "header");
|
||||
}
|
||||
for (int i = blocks.length() - 1; i >= 0; i--) {
|
||||
JSONObject block = blocks.optJSONObject(i);
|
||||
JSONObject layout = block.optJSONObject("layout");
|
||||
String data_type = block.optString("data_type");
|
||||
|
||||
if (Objects.equals(data_type, "music_recommended_playlists") || Objects.equals(data_type, "radiostations")) {
|
||||
blocks.remove(i); // block
|
||||
if (i - 1 >= 0) blocks.remove(i - 1); // text
|
||||
if (i - 2 >= 0) blocks.remove(i - 2); // divider
|
||||
continue; // Skip to the next iteration after removing
|
||||
}
|
||||
|
||||
if (layout == null) continue; // Skip to the next iteration if layout is null
|
||||
|
||||
String name = layout.optString("name");
|
||||
if (!name.equals("header_extended")) {
|
||||
continue; // Skip to the next iteration if not header_extended
|
||||
}
|
||||
|
||||
if (layout.has("top_title")) {
|
||||
blocks.remove(i);
|
||||
}
|
||||
layout.put("name", "header");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void setDefaultAudioPage(JSONArray jsonArray, JSONObject catalog) throws JSONException {
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
var item = jsonArray.optJSONObject(i);
|
||||
@@ -263,7 +271,9 @@ public class CatalogJsonInjector {
|
||||
+ "&access_token="
|
||||
+ AccountManagerUtils.getUserToken())
|
||||
.a("Accept-Encoding", "gzip")
|
||||
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
|
||||
.a("User-Agent", Network.l.c().a())
|
||||
.a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||
.a();
|
||||
try (Response resp = Network.b(CLIENT_API).a(request).execute()) {
|
||||
var json = new JSONObject(GzipDecompressor.decompressResponse(resp)).getJSONObject("response");
|
||||
var catalogarr = json.optJSONObject("catalog").optJSONArray("sections").optJSONObject(0);
|
||||
|
||||
@@ -18,15 +18,16 @@ import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class PlaylistInjector {
|
||||
public final static String CHANNEL_NAME = "VTCH";
|
||||
private static final ExecutorService executor = Executors.newCachedThreadPool();
|
||||
public final static String CHANNEL_NAME = "VTCH";
|
||||
|
||||
public static void injectDownloadPlaylist(Playlist playlist) {
|
||||
executor.submit(() -> AudioDownloader.cachePlaylist(playlist));
|
||||
AudioDownloader.cachePlaylist(playlist);
|
||||
}
|
||||
|
||||
public static boolean eligibleForOfflineCaching() {
|
||||
@@ -35,7 +36,7 @@ public class PlaylistInjector {
|
||||
|
||||
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
|
||||
try {
|
||||
if (!eligibleForOfflineCaching())
|
||||
if (MusicCacheImpl.isEmpty())
|
||||
return null;
|
||||
var requestArgs = audioGetPlaylist.b();
|
||||
|
||||
@@ -45,8 +46,6 @@ public class PlaylistInjector {
|
||||
boolean isVirtualPlaylist = accessKey != null && (accessKey.equals("cache"));
|
||||
boolean isAlbumVirtualPlaylist = accessKey != null && (accessKey.equals("cacheAlbum"));
|
||||
|
||||
Log.d("PlaylistInjector", "id = " + id + " / owner = " + ownerId + " / access = " + accessKey + " / " + isVirtualPlaylist + " + " + isAlbumVirtualPlaylist);
|
||||
|
||||
if (TextUtils.isEmpty(id) || (!isVirtualPlaylist && !isAlbumVirtualPlaylist))
|
||||
return null;
|
||||
|
||||
@@ -73,7 +72,7 @@ public class PlaylistInjector {
|
||||
|
||||
response.c = tracks;
|
||||
} catch (RemoteException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
e.fillInStackTrace();
|
||||
response.c = new ArrayList<>();
|
||||
}
|
||||
observableEmitter.b(response);
|
||||
@@ -82,18 +81,20 @@ public class PlaylistInjector {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVirtualPlaylist) {
|
||||
response.c = (ArrayList<MusicTrack>) TracklistHelper.getMyCachedMusicTracks();
|
||||
response.c = (ArrayList<MusicTrack>) MusicCacheImpl.getPlaylistSongs(ownerId, id);
|
||||
|
||||
Log.d("Playlist", "Open playlist " + ownerId + "_" + id);
|
||||
|
||||
if (Objects.equals(id, "-1")) {
|
||||
response.b = PlaylistHelper.createCachedPlaylistMetadata();
|
||||
} else {
|
||||
response.c = (ArrayList<MusicTrack>) TracklistHelper.getTracks(requestArgs.get("owner_id"));
|
||||
response.b = PlaylistHelper.createCachedPlaylistMetadata(requestArgs.get("owner_id"));
|
||||
response.b = MusicCacheImpl.getPlaylist(id, ownerId);
|
||||
}
|
||||
|
||||
observableEmitter.b(response);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
|
||||
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
||||
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||
@@ -22,6 +23,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.*;
|
||||
|
||||
public class TracklistInjector {
|
||||
|
||||
public static boolean eligibleForOfflineCaching() {
|
||||
@@ -59,7 +62,9 @@ public class TracklistInjector {
|
||||
if (MusicCacheImpl.isEmpty()) {
|
||||
observableEmitter.b(parser.c(getEmptyCatalog()));
|
||||
} else {
|
||||
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
|
||||
observableEmitter.b(parser.c(createVirtualCatalog(
|
||||
PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(),
|
||||
AccountManagerUtils.getUserId() + "_-1"))));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -162,16 +167,12 @@ public class TracklistInjector {
|
||||
boolean noPlaylists = hasNoPlaylists(blocks);
|
||||
|
||||
if (noPlaylists) {
|
||||
catalogNode.put("playlists", new JSONArray().put(PlaylistHelper.getPlaylist()));
|
||||
PlaylistHelper.addCachedPlaylists(catalogNode.optJSONArray("playlists"), true);
|
||||
|
||||
JSONArray newBlocks = createNewBlocks(blocks);
|
||||
|
||||
Log.d("catalogInjector", "added cache catalog playlist");
|
||||
|
||||
firstSection.put("blocks", newBlocks);
|
||||
firstSection.put("blocks", createNewBlocks(blocks));
|
||||
} else {
|
||||
try {
|
||||
catalogNode.optJSONArray("playlists").put(PlaylistHelper.getPlaylist());
|
||||
PlaylistHelper.addCachedPlaylists(catalogNode.optJSONArray("playlists"), false);
|
||||
|
||||
updatePlaylistsIds(blocks);
|
||||
} catch (Exception e) {
|
||||
@@ -188,7 +189,7 @@ public class TracklistInjector {
|
||||
return firstSection.getString("url").equals("https://vk.com/audios" + AccountManagerUtils.getUserId() + "?section=all");
|
||||
}
|
||||
|
||||
private static boolean hasNoPlaylists(JSONArray blocks) throws JSONException {
|
||||
private static boolean hasNoPlaylists(JSONArray blocks) {
|
||||
return blocks.optJSONObject(0).optJSONArray("buttons").optJSONObject(0).optJSONObject("action").optString("type").equals("create_playlist");
|
||||
}
|
||||
|
||||
@@ -196,13 +197,14 @@ public class TracklistInjector {
|
||||
JSONArray newBlocks = new JSONArray();
|
||||
|
||||
newBlocks
|
||||
.put(PlaylistHelper.getCatalogHeader())
|
||||
.put(PlaylistHelper.getCatalogHeader(AndroidUtils.getString(R.string.cached_tracks_title)))
|
||||
.put(PlaylistHelper.getCatalogPlaylist())
|
||||
.put(PlaylistHelper.getCatalogSeparator());
|
||||
|
||||
for (int i = 0; i < blocks.length(); i++) {
|
||||
newBlocks.put(blocks.optJSONObject(i));
|
||||
}
|
||||
|
||||
return newBlocks;
|
||||
}
|
||||
|
||||
@@ -212,16 +214,15 @@ public class TracklistInjector {
|
||||
String type = j.optString("data_type");
|
||||
|
||||
if (type.equals("music_playlists") && j.has("playlists_ids")) {
|
||||
JSONArray newarr = new JSONArray();
|
||||
JSONArray newarr = PlaylistHelper.getCachedPlaylistsIds();
|
||||
JSONArray playlists_ids = j.optJSONArray("playlists_ids");
|
||||
|
||||
newarr.put(AccountManagerUtils.getUserId() + "_-1");
|
||||
|
||||
for (int n = 0; n < playlists_ids.length(); n++) {
|
||||
newarr.put(playlists_ids.optString(n));
|
||||
}
|
||||
|
||||
j.put("playlists_ids", newarr);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -240,7 +241,19 @@ public class TracklistInjector {
|
||||
|
||||
audiosBlock.put("layout", layout);
|
||||
|
||||
var blocks = new JSONArray().put(getShuffleButton()).put(audiosBlock);
|
||||
var blocks = new JSONArray();
|
||||
|
||||
if (MusicCacheImpl.hasPlaylist()) {
|
||||
blocks.put(getCatalogHeader("Кешированные плейлисты"))
|
||||
.put(getCatalogPlaylist());
|
||||
}
|
||||
|
||||
if (!MusicCacheImpl.getAllOwnTracks().isEmpty()) {
|
||||
if (MusicCacheImpl.hasPlaylist()) blocks.put(getCatalogSeparator());
|
||||
blocks.put(getCatalogHeader(AndroidUtils.getString(R.string.cached_tracks_title)))
|
||||
.put(getShuffleButton())
|
||||
.put(audiosBlock);
|
||||
}
|
||||
|
||||
var randomId = getRandomId();
|
||||
|
||||
@@ -259,6 +272,7 @@ public class TracklistInjector {
|
||||
|
||||
return new JSONObject()
|
||||
.put("catalog", catalog)
|
||||
.put("playlists", addCachedPlaylists(new JSONArray(), false))
|
||||
.put("audios", TracklistHelper.tracksToJsons(tracks));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.StrictMode;
|
||||
|
||||
import com.vk.core.util.AppContextHolder;
|
||||
import com.vk.medianative.MediaImageEncoder;
|
||||
import com.vk.medianative.MediaNative;
|
||||
import com.vtosters.lite.data.Users;
|
||||
@@ -22,7 +24,8 @@ import java.security.NoSuchAlgorithmException;
|
||||
public class Preferences {
|
||||
|
||||
public static void init(Application application) throws Exception {
|
||||
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
|
||||
AppContextHolder.a = application.getApplicationContext();
|
||||
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
|
||||
StrictMode.setThreadPolicy(policy); // fix profiles hide hook
|
||||
|
||||
MediaNative.init(application);
|
||||
@@ -289,6 +292,10 @@ public class Preferences {
|
||||
return getBoolValue("vkme_notifs", false);
|
||||
}
|
||||
|
||||
public static boolean sendMusicMetrics() {
|
||||
return getBoolValue("sendMusicMetrics", true);
|
||||
}
|
||||
|
||||
public static boolean screenshotdetect() {
|
||||
return getBoolValue("screenshotdetect", true);
|
||||
}
|
||||
@@ -356,7 +363,14 @@ public class Preferences {
|
||||
}
|
||||
|
||||
public static int compress(int origquality) {
|
||||
if (MediaImageEncoder.needToSkipCompression()) return 100;
|
||||
return origquality;
|
||||
return MediaImageEncoder.needToCompress() ? origquality : 100;
|
||||
}
|
||||
|
||||
public static String metadataSeparator() {
|
||||
return getPreferences().getString("metadata_separator", "; ");
|
||||
}
|
||||
|
||||
public static void setMetadataSeparator(String separator) {
|
||||
getPreferences().edit().putString("metadata_separator", separator).apply();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import com.vk.core.drawable.RecoloredDrawable;
|
||||
import com.vk.core.ui.themes.MilkshakeHelper;
|
||||
import com.vk.core.ui.themes.VKTheme;
|
||||
import com.vk.core.ui.themes.VKThemeHelper;
|
||||
import com.vk.im.engine.models.dialogs.DialogTheme;
|
||||
import com.vk.im.ui.themes.DefaultThemeProvider;
|
||||
import com.vtosters.lite.R;
|
||||
import com.vtosters.lite.data.ThemeTracker;
|
||||
import ru.vtosters.lite.deviceinfo.OEMDetector;
|
||||
@@ -32,7 +34,6 @@ import ru.vtosters.lite.themes.ThemesHacks;
|
||||
import ru.vtosters.lite.themes.ThemesManager;
|
||||
import ru.vtosters.lite.ui.wallpapers.WallpapersHooks;
|
||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||
import ru.vtosters.lite.utils.WebViewColoringUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@@ -44,7 +45,7 @@ public class ThemesUtils {
|
||||
try {
|
||||
setTheme(theme, LifecycleUtils.getCurrentActivity(), restartActivity);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
} // Apply VKTheme and ImTheme (hard applying without dynamic theme changing)
|
||||
|
||||
@@ -61,14 +62,27 @@ public class ThemesUtils {
|
||||
activity = LifecycleUtils.getCurrentActivity();
|
||||
}
|
||||
VKThemeHelper.theme(theme, activity, fl);
|
||||
if (isMonetTheme() || ThemesManager.canApplyCustomAccent()) ThemesCore.clear();
|
||||
if (restartActivity) activity.recreate();
|
||||
if (isMonetTheme() || ThemesManager.canApplyCustomAccent()) {
|
||||
ThemesCore.clear();
|
||||
ThemesCore.init();
|
||||
DialogTheme.d.a(new DefaultThemeProvider(VKThemeHelper.k).b()); // reset dialog attrs cache
|
||||
}
|
||||
ThemeTracker.a();
|
||||
WebViewColoringUtils.isLoaded = false;
|
||||
if (restartActivity) {
|
||||
activity.recreate();
|
||||
}
|
||||
new WebView(activity).clearCache(true);
|
||||
WebCachePreloader.e();
|
||||
}
|
||||
|
||||
public static DialogTheme getDialogTheme() {
|
||||
if (isMonetTheme()) {
|
||||
return new DefaultThemeProvider(VKThemeHelper.k).b(); // get directly from theme cuz monet issue
|
||||
} else {
|
||||
return DialogTheme.d.a(); // get cached theme
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDarkTheme() {
|
||||
return VKThemeHelper.r();
|
||||
}
|
||||
@@ -341,7 +355,7 @@ public class ThemesUtils {
|
||||
}
|
||||
|
||||
public static int getColor(int i) {
|
||||
return ThemesHacks.getHackedColor(LifecycleUtils.getCurrentActivity(), i);
|
||||
return ThemesHacks.getHackedColor(getGlobalContext(), i);
|
||||
} // Android Support color injector + accent color checker
|
||||
|
||||
public static int getAlertStyle() {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package ru.vtosters.lite.concurrent;
|
||||
|
||||
import com.vk.core.concurrent.VkExecutors;
|
||||
import io.reactivex.Scheduler;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
|
||||
import io.reactivex.Scheduler;
|
||||
|
||||
public class VTExecutors {
|
||||
public static ExecutorService getMusicDownloadExecutor() {
|
||||
@@ -32,7 +33,21 @@ public class VTExecutors {
|
||||
}
|
||||
|
||||
private static class LazyHolder {
|
||||
public static final ExecutorService INSTANCE = Executors.newSingleThreadExecutor();
|
||||
private static final int MIN_PARALLELISM = 1;
|
||||
private static final int MAX_PARALLELISM = 7;
|
||||
private static final int PARALLELISM = clamp(
|
||||
(Runtime.getRuntime().availableProcessors() >> 1) - 1
|
||||
);
|
||||
|
||||
private static final ExecutorService INSTANCE =
|
||||
new ForkJoinPool(PARALLELISM,
|
||||
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
|
||||
(t, e) -> {},
|
||||
/* FIFO */ true);
|
||||
|
||||
private static int clamp(int value) {
|
||||
return Math.min(LazyHolder.MAX_PARALLELISM, Math.max(value, LazyHolder.MIN_PARALLELISM));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
package ru.vtosters.lite.downloaders;
|
||||
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||
import com.vk.core.util.ToastUtils;
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import com.vk.dto.music.Playlist;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.hooks.music.MusicCacheFilesHook;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
|
||||
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
||||
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
|
||||
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
|
||||
import ru.vtosters.lite.music.downloader.AudioGet;
|
||||
import ru.vtosters.lite.music.downloader.PlaylistDownloader;
|
||||
import ru.vtosters.lite.music.downloader.ThumbnailPlaylistDownloader;
|
||||
import ru.vtosters.lite.music.downloader.TrackDownloader;
|
||||
import ru.vtosters.lite.music.interfaces.Callback;
|
||||
import ru.vtosters.lite.music.notification.MusicNotificationBuilder;
|
||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
import ru.vtosters.lite.utils.music.PlaylistUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getString;
|
||||
|
||||
@@ -32,19 +41,18 @@ import static ru.vtosters.lite.utils.AndroidUtils.getString;
|
||||
*/
|
||||
|
||||
public class AudioDownloader {
|
||||
public static final ExecutorService executor = Executors.newCachedThreadPool();
|
||||
public static final String dlpath = Preferences.getBoolValue("dldir", false) ? Environment.DIRECTORY_DOWNLOADS : Environment.DIRECTORY_MUSIC;
|
||||
|
||||
public static void downloadPlaylist(Playlist playlist) {
|
||||
var tracks = PlaylistConverter.getPlaylist(playlist);
|
||||
List<MusicTrack> tracks = PlaylistConverter.getPlaylist(playlist);
|
||||
|
||||
var playlistName = IOUtils.getValidFileName(playlist.g);
|
||||
String playlistName = IOUtils.getValidFileName(playlist.g);
|
||||
|
||||
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
|
||||
var downloadPath = musicPath + File.separator + playlistName;
|
||||
String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
|
||||
String downloadPath = musicPath + File.separator + playlistName;
|
||||
|
||||
var notificationId = playlistName.hashCode();
|
||||
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId);
|
||||
int notificationId = playlistName.hashCode();
|
||||
NotificationCompat.Builder notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId);
|
||||
|
||||
PlaylistDownloader.downloadPlaylist(
|
||||
tracks,
|
||||
@@ -60,54 +68,96 @@ public class AudioDownloader {
|
||||
}
|
||||
|
||||
public static void cacheTrack(MusicTrack track) {
|
||||
var trackId = LibVKXClient.asId(track);
|
||||
String trackId = LibVKXClient.asId(track);
|
||||
|
||||
if (MusicCacheImpl.isCachedTrack(trackId)) {
|
||||
MusicCacheImpl.removeTrack(trackId);
|
||||
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
||||
return;
|
||||
} else {
|
||||
File trackFile = MusicCacheFilesHook.getTrackFile(trackId);
|
||||
if (!trackFile.exists()) {
|
||||
trackFile.getParentFile().mkdirs();
|
||||
}
|
||||
downloadM3U8(track, true);
|
||||
}
|
||||
|
||||
var trackFile = MusicCacheFilesHook.getTrackFile(trackId);
|
||||
if (!trackFile.exists())
|
||||
trackFile.getParentFile().mkdirs();
|
||||
executor.submit(() -> downloadM3U8(track, true));
|
||||
}
|
||||
|
||||
public static void cachePlaylist(Playlist playlist) {
|
||||
var tracks = PlaylistConverter.getPlaylist(playlist);
|
||||
List<MusicTrack> tracks = PlaylistConverter.getPlaylist(playlist);
|
||||
|
||||
var notificationId = playlist.g.hashCode();
|
||||
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId);
|
||||
int notificationId = playlist.g.hashCode();
|
||||
NotificationCompat.Builder notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId);
|
||||
|
||||
JSONObject thumbs = PlaylistUtils.getThumb(playlist);
|
||||
|
||||
if (thumbs != null) {
|
||||
new ThumbnailPlaylistDownloader().download(null, new Callback() {
|
||||
@Override
|
||||
public void onProgress(int progress) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
PlaylistCacheDbDelegate.addPlaylist(AndroidUtils.getGlobalContext(), playlist);
|
||||
|
||||
Log.d("Playlist", "adding to cache with thumbs " + playlist.v1());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSizeReceived(long size, long header) {
|
||||
|
||||
}
|
||||
}, playlist);
|
||||
} else {
|
||||
PlaylistCacheDbDelegate.addPlaylist(AndroidUtils.getGlobalContext(), playlist);
|
||||
|
||||
Log.d("Playlist", "adding to cache without thumbs " + playlist.a);
|
||||
}
|
||||
|
||||
PlaylistDownloader.cachePlaylist(
|
||||
tracks,
|
||||
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
|
||||
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId),
|
||||
playlist
|
||||
);
|
||||
}
|
||||
|
||||
public static void cacheAllAudios() {
|
||||
var tracks = AudioGet.getAudios();
|
||||
List<MusicTrack> tracks = AudioGet.getAudios();
|
||||
|
||||
var notificationId = AccountManagerUtils.getUserId();
|
||||
var notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
|
||||
int notificationId = AccountManagerUtils.getUserId();
|
||||
NotificationCompat.Builder notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
|
||||
|
||||
if (tracks == null || tracks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlaylistDownloader.cachePlaylist(
|
||||
tracks,
|
||||
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
|
||||
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId),
|
||||
PlaylistHelper.createCachedPlaylistMetadata()
|
||||
);
|
||||
}
|
||||
|
||||
public static void downloadAllAudios() {
|
||||
var tracks = AudioGet.getAudios();
|
||||
List<MusicTrack> tracks = AudioGet.getAudios();
|
||||
|
||||
var notificationId = AccountManagerUtils.getUserId();
|
||||
var notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
|
||||
int notificationId = AccountManagerUtils.getUserId();
|
||||
NotificationCompat.Builder notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
|
||||
|
||||
var playlistName = "Audios of " + AccountManagerUtils.getUserId();
|
||||
String playlistName = "Audios of " + AccountManagerUtils.getUserId();
|
||||
|
||||
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
|
||||
var downloadPath = musicPath + File.separator + playlistName;
|
||||
String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
|
||||
String downloadPath = musicPath + File.separator + playlistName;
|
||||
|
||||
if (tracks == null || tracks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlaylistDownloader.downloadPlaylist(
|
||||
tracks,
|
||||
@@ -118,20 +168,20 @@ public class AudioDownloader {
|
||||
}
|
||||
|
||||
private static void downloadM3U8(MusicTrack track, boolean cache) {
|
||||
if (track.D == null) {
|
||||
Objects.requireNonNull(track.D, () -> {
|
||||
ToastUtils.a(getString(R.string.link_audio_error));
|
||||
return;
|
||||
}
|
||||
|
||||
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
|
||||
var tempId = track.d;
|
||||
var downloadPath = musicPath + File.separator;
|
||||
var notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId);
|
||||
return "link must not be null";
|
||||
});
|
||||
String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
|
||||
int tempId = track.d;
|
||||
String downloadPath = musicPath + File.separator;
|
||||
NotificationCompat.Builder notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId);
|
||||
|
||||
Callback cb = MusicCallbackBuilder.buildOneTrackCallback(tempId, notification);
|
||||
if (cache) {
|
||||
TrackDownloader.cacheTrack(track, MusicCallbackBuilder.buildOneTrackCallback(tempId, notification));
|
||||
TrackDownloader.cacheTrack(track, cb, PlaylistHelper.createCachedPlaylistMetadata());
|
||||
} else {
|
||||
TrackDownloader.downloadTrack(track, downloadPath, MusicCallbackBuilder.buildOneTrackCallback(tempId, notification));
|
||||
TrackDownloader.downloadTrack(track, downloadPath, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,7 @@ import android.os.Environment;
|
||||
import com.vk.core.network.Network;
|
||||
import com.vk.dto.common.ImageSize;
|
||||
import com.vk.dto.stories.model.StoryEntry;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okhttp3.*;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -41,45 +38,43 @@ public class StoryDownloader {
|
||||
.b(url)
|
||||
.a();
|
||||
|
||||
try (Response response = client.a(request).execute()) {
|
||||
if (!response.h()) {
|
||||
throw new IOException("Unexpected code " + response);
|
||||
}
|
||||
|
||||
ResponseBody responseBody = response.a();
|
||||
|
||||
if (responseBody == null) {
|
||||
throw new IOException("No response body");
|
||||
}
|
||||
|
||||
String fileName = "photo" + story.E.a + ".jpg";
|
||||
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "/Stories/");
|
||||
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs();
|
||||
}
|
||||
|
||||
File outputFile = new File(outputDir, fileName);
|
||||
|
||||
try (InputStream inputStream = responseBody.a();
|
||||
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
outputStream.flush();
|
||||
|
||||
MediaScannerConnection.scanFile(AndroidUtils.getGlobalContext(), new String[]{outputFile.toString()}, null, null);
|
||||
|
||||
AndroidUtils.sendToast("Файл скачан в Pictures/Stories");
|
||||
} catch (IOException e) {
|
||||
client.a(request).a(new Callback() {
|
||||
@Override
|
||||
public void a(Call call, IOException e) {
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(Call call, Response response) {
|
||||
ResponseBody responseBody = response.a();
|
||||
|
||||
String fileName = "photo" + story.E.a + ".jpg";
|
||||
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "/Stories/");
|
||||
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs();
|
||||
}
|
||||
|
||||
File outputFile = new File(outputDir, fileName);
|
||||
|
||||
try (InputStream inputStream = responseBody.a();
|
||||
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
outputStream.flush();
|
||||
|
||||
MediaScannerConnection.scanFile(AndroidUtils.getGlobalContext(), new String[]{outputFile.toString()}, null, null);
|
||||
|
||||
AndroidUtils.sendToast("Файл скачан в Pictures/Stories");
|
||||
} catch (IOException e) {
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package ru.vtosters.lite.downloaders;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
@@ -10,6 +13,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||
import com.vk.core.dialogs.bottomsheet.MenuBottomSheetAction;
|
||||
import com.vk.core.network.Network;
|
||||
@@ -23,6 +27,7 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -39,6 +44,9 @@ import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
|
||||
public class VideoDownloader {
|
||||
public static int DOWNLOAD_ID = 0;
|
||||
public static int OPEN_EXTERNAL_LINK_ID = 1;
|
||||
public static String channelId = "video_download_channel";
|
||||
public static String channelName = "Video Download Channel";
|
||||
public static int notificationId = 1;
|
||||
|
||||
public static void addAction(List<MenuBottomSheetAction> actions, int... ints) {
|
||||
actions.add(new MenuBottomSheetAction(ints[0], ints[1], ints[2], ints[3]));
|
||||
@@ -73,46 +81,86 @@ public class VideoDownloader {
|
||||
.setAdapter(adapter, (dialog, which) -> {
|
||||
String url = urls.get(which);
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) AndroidUtils.getGlobalContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
|
||||
notificationManager.createNotificationChannel(notificationChannel);
|
||||
}
|
||||
|
||||
String notificationTitle = "Загрузка видео";
|
||||
String notificationText = "Загрузка началась";
|
||||
|
||||
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(AndroidUtils.getGlobalContext(), channelId)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setContentTitle(notificationTitle)
|
||||
.setContentText(notificationText)
|
||||
.setProgress(0, 0, true)
|
||||
.setOngoing(true);
|
||||
|
||||
notificationManager.notify(notificationId, notificationBuilder.build());
|
||||
|
||||
OkHttpClient client = Network.b(Network.ClientType.CLIENT_IMAGE_LOADER);;
|
||||
Request request = new Request.a()
|
||||
.b(url)
|
||||
.a();
|
||||
|
||||
try (Response response = client.a(request).execute()) {
|
||||
if (!response.h()) {
|
||||
throw new IOException("Unexpected code " + response);
|
||||
}
|
||||
|
||||
ResponseBody responseBody = response.a();
|
||||
|
||||
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), "/VKVideo/");
|
||||
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs();
|
||||
}
|
||||
|
||||
File outputFile = new File(outputDir, videoFile + ".mp4");
|
||||
|
||||
try (InputStream inputStream = responseBody.a();
|
||||
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
outputStream.flush();
|
||||
|
||||
MediaScannerConnection.scanFile(finalContext, new String[]{outputFile.toString()}, null, null);
|
||||
|
||||
AndroidUtils.sendToast("Файл скачан в Movies/VKVideo");
|
||||
} catch (IOException e) {
|
||||
client.a(request).a(new Callback() {
|
||||
@Override
|
||||
public void a(Call call, IOException e) {
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(Call call, Response response) {
|
||||
ResponseBody responseBody = response.a();
|
||||
|
||||
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), "/VKVideo/");
|
||||
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs();
|
||||
}
|
||||
|
||||
File outputFile = new File(outputDir, videoFile + ".mp4");
|
||||
|
||||
try (InputStream inputStream = responseBody.a();
|
||||
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
long totalBytesRead = 0;
|
||||
long fileSize = responseBody.d();
|
||||
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
totalBytesRead += bytesRead;
|
||||
int progress = (int) ((totalBytesRead * 100) / fileSize);
|
||||
|
||||
notificationBuilder.setProgress(100, progress, false)
|
||||
.setContentText("Загружено " + progress + "%");
|
||||
|
||||
notificationManager.notify(notificationId, notificationBuilder.build());
|
||||
}
|
||||
|
||||
outputStream.flush();
|
||||
|
||||
MediaScannerConnection.scanFile(finalContext, new String[]{outputFile.toString()}, null, null);
|
||||
|
||||
notificationManager.cancel(notificationId);
|
||||
|
||||
String successMessage = "Файл успешно скачан";
|
||||
|
||||
notificationBuilder.setContentText(successMessage)
|
||||
.setProgress(0, 0, false)
|
||||
.setContentTitle(notificationTitle)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setOngoing(false);
|
||||
|
||||
notificationManager.notify(notificationId, notificationBuilder.build());
|
||||
} catch (IOException e) {
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.show();
|
||||
}
|
||||
@@ -168,6 +216,7 @@ public class VideoDownloader {
|
||||
|
||||
var req = new Request.a()
|
||||
.b("https://" + getApi() + "/method/stories.getById?stories=" + storyId + "&v=5.99&access_token=" + getUserToken())
|
||||
.a("Accept-Encoding", "gzip")
|
||||
.a();
|
||||
|
||||
Network.b(CLIENT_API).a(req).a(new Callback() {
|
||||
@@ -183,7 +232,7 @@ public class VideoDownloader {
|
||||
progressDialog.cancel();
|
||||
|
||||
try {
|
||||
JSONObject mainJson = new JSONObject(response.a().g());
|
||||
JSONObject mainJson = new JSONObject(GzipDecompressor.decompressResponse(response));
|
||||
JSONObject responseJson = mainJson.getJSONObject("response").optJSONArray("items").optJSONObject(0);
|
||||
StoryEntry story = new StoryEntry(responseJson);
|
||||
StoryDownloader.downloadStory(story);
|
||||
@@ -211,6 +260,7 @@ public class VideoDownloader {
|
||||
|
||||
var req = new Request.a()
|
||||
.b("https://" + getApi() + "/method/video.get?owner_id=" + ownerId + "&videos=" + videoId + "&v=5.99&access_token=" + getUserToken())
|
||||
.a("Accept-Encoding", "gzip")
|
||||
.a();
|
||||
|
||||
Network.b(CLIENT_API).a(req).a(new Callback() {
|
||||
@@ -225,7 +275,7 @@ public class VideoDownloader {
|
||||
progressDialog.cancel();
|
||||
|
||||
try {
|
||||
JSONObject mainJson = new JSONObject(response.a().g());
|
||||
JSONObject mainJson = new JSONObject(GzipDecompressor.decompressResponse(response));
|
||||
JSONObject responseJson = mainJson.getJSONObject("response");
|
||||
JSONArray itemsJson = responseJson.getJSONArray("items");
|
||||
|
||||
@@ -238,4 +288,4 @@ public class VideoDownloader {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package ru.vtosters.lite.foaf;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||
import com.vk.core.network.Network;
|
||||
import com.vtosters.lite.R;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
@@ -27,6 +29,8 @@ import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.vk.core.network.Network.ClientType.CLIENT_API;
|
||||
|
||||
public class FoafBase {
|
||||
private static final Pattern FOAF_REGEX = Pattern.compile("<ya:created dc:date=\"(.+?)\"");
|
||||
private static final String DELETED = "<ya:profileState>deleted</ya:profileState>";
|
||||
@@ -68,9 +72,9 @@ public class FoafBase {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
private static String getLink(int i) {
|
||||
String prefix = (ProxyUtils.isAnyProxyEnabled() || ProxyUtils.isVKProxyEnabled()) ? "https://" + (ProxyUtils.isVKProxyEnabled() ? VikaMobile.getApiHost() : ProxyUtils.getApi()) + "/_/vk.com/foaf.php?id=" : "https://vk.com/foaf.php?id=";
|
||||
return prefix + i;
|
||||
return ProxyUtils.isAnyProxyEnabled() ? String.format("https://%s/_/vk.com/foaf.php?id=%d", ProxyUtils.getApi(), i) : String.format("https://vk.com/foaf.php?id=%d", i);
|
||||
}
|
||||
|
||||
public static void loadAndShow(Context context, int i) {
|
||||
@@ -81,12 +85,13 @@ public class FoafBase {
|
||||
|
||||
Request request = new Request.a()
|
||||
.b(getLink(i))
|
||||
.a("Accept-Encoding", "gzip")
|
||||
.a();
|
||||
|
||||
try (Response response = client.a(request).execute()) {
|
||||
try (Response response = Network.b(CLIENT_API).a(request).execute()) {
|
||||
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> {
|
||||
try {
|
||||
showFoafInfo(progressDialog, context, i, response.a().g());
|
||||
showFoafInfo(progressDialog, context, i, GzipDecompressor.decompressResponse(response));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -122,6 +127,7 @@ public class FoafBase {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
private static void showFoafDialog(Context context, int i, String normalHumanDate, long daysPassed) {
|
||||
String message = String.format("%s %d%s %s%s %d", context.getString(R.string.foafid), i, context.getString(R.string.foafregdate), normalHumanDate, context.getString(R.string.foafdate), daysPassed);
|
||||
new VkAlertDialog.Builder(context)
|
||||
|
||||
@@ -2,22 +2,25 @@ package ru.vtosters.lite.music;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import com.vtosters.lite.R;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||
import ru.vtosters.lite.music.downloader.M3UDownloader;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
|
||||
|
||||
public class Genius {
|
||||
private static final String KEY = "Bearer ZTejoT_ojOEasIkT9WrMBhBQOz6eYKK5QULCMECmOhvwqjRZ6WbpamFe3geHnvp3";
|
||||
private static final String URL = "https://api.genius.com";
|
||||
@@ -25,7 +28,7 @@ public class Genius {
|
||||
|
||||
public static String getTextMusic(MusicTrack musictrack) {
|
||||
String uid = musictrack.y1();
|
||||
String artist = removeExtras(M3UDownloader.getArtists(musictrack));
|
||||
String artist = removeExtras(MusicTrackUtils.getArtists(musictrack));
|
||||
String title = removeExtras(musictrack.f);
|
||||
int duration = musictrack.h;
|
||||
|
||||
|
||||
@@ -3,19 +3,14 @@ package ru.vtosters.lite.music;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import com.vtosters.lite.R;
|
||||
import okhttp3.*;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.downloader.M3UDownloader;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -24,6 +19,20 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||
|
||||
public class LastFMScrobbler {
|
||||
private static final String KEY = "5965d63402414776c54c266db0211746";
|
||||
private static final String KEY_SECRET = "d982180eed13275bb948e41cf225f88f";
|
||||
@@ -33,7 +42,7 @@ public class LastFMScrobbler {
|
||||
|
||||
public static void grabMusicTrack(MusicTrack musictrack) {
|
||||
var uid = musictrack.y1();
|
||||
var artist = M3UDownloader.getArtists(musictrack);
|
||||
var artist = MusicTrackUtils.getArtists(musictrack);
|
||||
var title = musictrack.f;
|
||||
var duration = musictrack.h;
|
||||
var track_id = musictrack.d;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package ru.vtosters.lite.music;
|
||||
|
||||
import android.util.Log;
|
||||
import com.vk.core.network.Network;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.vk.core.network.Network.ClientType.CLIENT_API;
|
||||
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
|
||||
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
|
||||
|
||||
public class Metrics {
|
||||
public static void trackEvents(String object) {
|
||||
trackEventsRequest(object);
|
||||
}
|
||||
|
||||
private static void trackEventsRequest(String object) {
|
||||
Request req = new Request.a()
|
||||
.b("https://" + getApi() + "/method/stats.trackEvents?events=" + object + "&v=5.119&access_token=" + getUserToken())
|
||||
.a();
|
||||
|
||||
Network.b(CLIENT_API).a(req).a(new Callback() {
|
||||
@Override
|
||||
public void a(Call call, IOException e) {
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void a(Call call, Response response) throws IOException {
|
||||
Log.d("MusicMetrics", response.a().g());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,21 @@ package ru.vtosters.lite.music.cache;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.music.downloader.M3UDownloader;
|
||||
import com.vk.dto.music.Playlist;
|
||||
import ru.vtosters.lite.music.cache.delegate.MusicCacheDbDelegate;
|
||||
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
|
||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
|
||||
import java.util.List;
|
||||
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||
|
||||
public class MusicCacheImpl {
|
||||
public static void addTrack(MusicTrack track) {
|
||||
@@ -20,7 +26,7 @@ public class MusicCacheImpl {
|
||||
track.I != null ? track.I.getId() + "" : "-1",
|
||||
track.f,
|
||||
!TextUtils.isEmpty(track.g) ? track.g : "",
|
||||
M3UDownloader.getArtists(track),
|
||||
MusicTrackUtils.getArtists(track),
|
||||
track.I != null ? track.I.getTitle() : "",
|
||||
track.K,
|
||||
track.h,
|
||||
@@ -32,20 +38,28 @@ public class MusicCacheImpl {
|
||||
MusicCacheStorageUtils.removeTrackDirById(trackId);
|
||||
}
|
||||
|
||||
public static List<MusicTrack> getAllTracks() {
|
||||
return MusicCacheDbDelegate.getAllTracks(AndroidUtils.getGlobalContext());
|
||||
public static List<MusicTrack> getAllOwnTracks() {
|
||||
return PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1");
|
||||
}
|
||||
|
||||
public static List<MusicTrack> getAlbumById(String albumId) {
|
||||
return MusicCacheDbDelegate.getAlbumById(AndroidUtils.getGlobalContext(), albumId);
|
||||
public static List<MusicTrack> getPlaylistSongs(String owner_id, String playlist_id) {
|
||||
return PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(), owner_id + "_" + playlist_id); // get songs from playlist
|
||||
}
|
||||
|
||||
public static List<MusicTrack> getFirstAlbumTrack(String albumId) {
|
||||
return MusicCacheDbDelegate.getFirstAlbumTrack(AndroidUtils.getGlobalContext(), albumId);
|
||||
public static Playlist getPlaylist(String playlist_id, String owner_id) {
|
||||
return getPlaylistById(owner_id + "_" + playlist_id); // get playlist from db
|
||||
}
|
||||
|
||||
public static List<MusicTrack> getPlaylist() {
|
||||
return MusicCacheDbDelegate.getPlaylist(AndroidUtils.getGlobalContext());
|
||||
public static List<Playlist> getPlaylists() {
|
||||
return PlaylistCacheDbDelegate.getAllPlaylists(AndroidUtils.getGlobalContext()); // get all cached playlists
|
||||
}
|
||||
|
||||
public static Playlist getPlaylistById(String query) {
|
||||
return PlaylistCacheDbDelegate.getPlaylistById(AndroidUtils.getGlobalContext(), query); // get playlist from db
|
||||
}
|
||||
|
||||
public static boolean hasPlaylist() {
|
||||
return PlaylistCacheDbDelegate.getPlaylistsCount(AndroidUtils.getGlobalContext()) != 0; // has playlists or not
|
||||
}
|
||||
|
||||
public static long getTracksCount() {
|
||||
@@ -58,7 +72,7 @@ public class MusicCacheImpl {
|
||||
try {
|
||||
return (long) service.getCache().size();
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
e.fillInStackTrace();
|
||||
return defaultValue();
|
||||
}
|
||||
}
|
||||
@@ -80,6 +94,7 @@ public class MusicCacheImpl {
|
||||
|
||||
public static void clear() {
|
||||
MusicCacheDbDelegate.drop(AndroidUtils.getGlobalContext());
|
||||
PlaylistCacheDbDelegate.drop(AndroidUtils.getGlobalContext());
|
||||
MusicCacheStorageUtils.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,14 @@ 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.music.downloader.M3UDownloader;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -24,6 +24,12 @@ import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
|
||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||
|
||||
/**
|
||||
* <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>
|
||||
@@ -117,12 +123,14 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //
|
||||
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, M3UDownloader.getArtists(track));
|
||||
vals.put(Constants.COLUMN_ARTIST, MusicTrackUtils.getArtists(track));
|
||||
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);
|
||||
|
||||
PlaylistCacheDbDelegate.addTrackToPlaylist(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1", track.y1());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,18 +174,21 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //
|
||||
return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME);
|
||||
}
|
||||
|
||||
public MusicTrack getTrackById(String trackId) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(Constants.TABLE_NAME, null, Constants.COLUMN_TRACK_ID + " = ?", new String[]{trackId}, null, null, null);
|
||||
MusicTrack track = null;
|
||||
if (cursor.moveToFirst()) {
|
||||
track = fromCursor(cursor);
|
||||
}
|
||||
cursor.close();
|
||||
return track;
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -216,7 +227,7 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Constants {
|
||||
int DV_VERSION = 0x2;
|
||||
int DV_VERSION = 0x3;
|
||||
String DB_NAME = "vt_lite_cache.db";
|
||||
String TABLE_NAME = "tracks";
|
||||
//columns
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
package ru.vtosters.lite.music.cache.db;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
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.util.Log;
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import com.vk.dto.music.Playlist;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
||||
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static ru.vtosters.lite.utils.music.MusicCacheStorageUtils.*;
|
||||
|
||||
public class PlaylistCacheDb extends SQLiteOpenHelper implements AutoCloseable {
|
||||
public PlaylistCacheDb(Context context) {
|
||||
super(context, Constants.DB_NAME, null, Constants.DV_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(Constants.CREATE_QUERY);
|
||||
db.execSQL(Constants.CREATE_PLAYLIST_TRACKS_QUERY);
|
||||
}
|
||||
|
||||
private static Playlist fromCursor(Cursor cur) throws JSONException {
|
||||
@SuppressLint("Range")
|
||||
String photoString = cur.getString(cur.getColumnIndex(Constants.COLUMN_PHOTO));
|
||||
JSONObject photo = null;
|
||||
|
||||
try {
|
||||
photo = new JSONObject(photoString);
|
||||
} catch (JSONException e) {
|
||||
// ok and
|
||||
}
|
||||
|
||||
@SuppressLint("Range")
|
||||
JSONObject playlist = PlaylistHelper.generatePlaylist(
|
||||
cur.getInt(cur.getColumnIndex(Constants.COLUMN_ID)),
|
||||
cur.getInt(cur.getColumnIndex(Constants.COLUMN_OWNER_ID)),
|
||||
Boolean.parseBoolean(cur.getString(cur.getColumnIndex(Constants.COLUMN_IS_EXPLICIT))),
|
||||
cur.getString(cur.getColumnIndex(Constants.COLUMN_TITLE)),
|
||||
cur.getString(cur.getColumnIndex(Constants.COLUMN_DESCRIPTION)),
|
||||
photo,
|
||||
0);
|
||||
|
||||
Log.d("Playlist", "generated " + Playlist.U.a(playlist).v1());
|
||||
|
||||
return Playlist.U.a(playlist);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
db.execSQL(Constants.DROP_QUERY);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
public void addPlaylist(int id, int ownerId, boolean isExplicit, String title, String description, JSONObject photo) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Constants.COLUMN_ID, id);
|
||||
values.put(Constants.COLUMN_OWNER_ID, ownerId);
|
||||
values.put(Constants.COLUMN_IS_EXPLICIT, isExplicit);
|
||||
values.put(Constants.COLUMN_TITLE, title);
|
||||
values.put(Constants.COLUMN_DESCRIPTION, description);
|
||||
values.put(Constants.COLUMN_PHOTO, String.valueOf(photo));
|
||||
|
||||
getWritableDatabase().insert(Constants.TABLE_NAME, null, values);
|
||||
}
|
||||
|
||||
private void deletePlaylist(int id, int ownerId) {
|
||||
getWritableDatabase().delete(Constants.TABLE_NAME, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)});
|
||||
}
|
||||
|
||||
public boolean isCachedPlaylist(int id, int ownerId) {
|
||||
Cursor cursor = getReadableDatabase().query(Constants.TABLE_NAME, new String[]{Constants.COLUMN_ID}, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)}, null, null, null);
|
||||
boolean exists = cursor.getCount() > 0;
|
||||
cursor.close();
|
||||
return exists;
|
||||
}
|
||||
|
||||
public long getPlaylistsCount() {
|
||||
return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME);
|
||||
}
|
||||
|
||||
public void deletePlaylistWithTracks(String playlistId) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
String[] parts = playlistId.split("_");
|
||||
int id = Integer.parseInt(parts[1]);
|
||||
int ownerId = Integer.parseInt(parts[0]);
|
||||
|
||||
// Get all tracks in the playlist
|
||||
List<MusicTrack> tracks = getTracksInPlaylist(playlistId);
|
||||
|
||||
// Delete the playlist
|
||||
deletePlaylist(id, ownerId);
|
||||
|
||||
// Delete each track from the playlist_tracks table
|
||||
for (MusicTrack track : tracks) {
|
||||
String trackId = track.y1();
|
||||
removeTrackFromPlaylist(playlistId, trackId);
|
||||
removePlaylistThumb(playlistId);
|
||||
|
||||
if (!isTrackInAnyOtherPlaylist(trackId, playlistId)) {
|
||||
MusicCacheImpl.removeTrack(trackId);
|
||||
Log.d("Playlist", "removed " + trackId);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all entries from the playlist_tracks table for this playlist
|
||||
db.delete(Constants.TABLE_PLAYLIST_TRACKS, Constants.COLUMN_PLAYLIST_ID + " = ?", new String[]{playlistId});
|
||||
}
|
||||
|
||||
private boolean isTrackInAnyOtherPlaylist(String trackId, String playlistId) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(
|
||||
Constants.TABLE_PLAYLIST_TRACKS,
|
||||
null,
|
||||
Constants.COLUMN_TRACK_ID + " = ? AND " + Constants.COLUMN_PLAYLIST_ID + " != ?",
|
||||
new String[]{trackId, playlistId},
|
||||
null, null, null
|
||||
);
|
||||
boolean exists = cursor.getCount() > 0;
|
||||
cursor.close();
|
||||
return exists;
|
||||
}
|
||||
|
||||
public List<Playlist> getAllPlaylists() {
|
||||
Cursor cursor = getReadableDatabase().query(Constants.TABLE_NAME, null, null, null, null, null, null);
|
||||
List<Playlist> playlists = getPlaylistsWithCursor(cursor);
|
||||
cursor.close();
|
||||
return playlists;
|
||||
}
|
||||
|
||||
public Playlist getPlaylistById(int id, int ownerId) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(Constants.TABLE_NAME, null, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)}, null, null, null);
|
||||
Playlist playlist = null;
|
||||
if (cursor.moveToFirst()) {
|
||||
try {
|
||||
playlist = fromCursor(cursor);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
return playlist;
|
||||
}
|
||||
|
||||
private List<Playlist> getPlaylistsWithCursor(Cursor cur) {
|
||||
List<Playlist> playlists = new ArrayList<>();
|
||||
while (cur.moveToNext()) {
|
||||
try {
|
||||
playlists.add(fromCursor(cur));
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return playlists;
|
||||
}
|
||||
|
||||
public void addTrackToPlaylist(String playlistId, String trackId) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Constants.COLUMN_PLAYLIST_ID, playlistId);
|
||||
values.put(Constants.COLUMN_TRACK_ID, trackId);
|
||||
getWritableDatabase().insert(Constants.TABLE_PLAYLIST_TRACKS, null, values);
|
||||
}
|
||||
|
||||
public void removeTrackFromPlaylist(String playlistId, String trackId) {
|
||||
getWritableDatabase().delete(Constants.TABLE_PLAYLIST_TRACKS, Constants.COLUMN_PLAYLIST_ID + " = ? AND " + Constants.COLUMN_TRACK_ID + " = ?", new String[]{playlistId, trackId});
|
||||
}
|
||||
|
||||
public List<MusicTrack> getTracksInPlaylist(String playlistId) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(
|
||||
Constants.TABLE_PLAYLIST_TRACKS,
|
||||
new String[]{Constants.COLUMN_TRACK_ID},
|
||||
Constants.COLUMN_PLAYLIST_ID + " = ?",
|
||||
new String[]{playlistId},
|
||||
null, null, null
|
||||
);
|
||||
|
||||
List<MusicTrack> tracks = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
@SuppressLint("Range")
|
||||
String trackId = cursor.getString(cursor.getColumnIndex(Constants.COLUMN_TRACK_ID));
|
||||
MusicTrack track = TracklistHelper.getTrack(trackId);
|
||||
|
||||
Log.d("Playlist", "add " + trackId + " to playlist " + playlistId);
|
||||
|
||||
if (track != null) {
|
||||
tracks.add(track);
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
return tracks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigure(SQLiteDatabase db) {
|
||||
super.onConfigure(db);
|
||||
db.setForeignKeyConstraintsEnabled(true);
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Constants {
|
||||
int DV_VERSION = 0x2;
|
||||
String DB_NAME = "vt_lite_cache_playlists.db";
|
||||
String TABLE_NAME = "playlists";
|
||||
|
||||
//columns
|
||||
String COLUMN_ID = "id";
|
||||
String COLUMN_OWNER_ID = "owner_id";
|
||||
String COLUMN_IS_EXPLICIT = "is_explicit";
|
||||
String COLUMN_TITLE = "title";
|
||||
String COLUMN_DESCRIPTION = "description";
|
||||
String COLUMN_PHOTO = "photo";
|
||||
|
||||
// tracks
|
||||
String COLUMN_TRACK_ID = "track_id";
|
||||
String TABLE_PLAYLIST_TRACKS = "playlist_tracks";
|
||||
String COLUMN_PLAYLIST_ID = "playlist_id";
|
||||
|
||||
//queries
|
||||
String CREATE_QUERY = "create table " + TABLE_NAME
|
||||
+ "("
|
||||
+ COLUMN_ID + " INTEGER PRIMARY KEY autoincrement,"
|
||||
+ COLUMN_OWNER_ID + " INTEGER not null,"
|
||||
+ COLUMN_IS_EXPLICIT + " TEXT not null,"
|
||||
+ COLUMN_TITLE + " TEXT not null,"
|
||||
+ COLUMN_DESCRIPTION + " TEXT not null,"
|
||||
+ COLUMN_PHOTO + " TEXT not null"
|
||||
+ ")";
|
||||
|
||||
String CREATE_PLAYLIST_TRACKS_QUERY = "create table " + TABLE_PLAYLIST_TRACKS + "("
|
||||
+ COLUMN_PLAYLIST_ID + " TEXT not null,"
|
||||
+ COLUMN_TRACK_ID + " TEXT not null"
|
||||
+ ")";
|
||||
|
||||
String DROP_QUERY = "drop table if exists " + TABLE_NAME;
|
||||
}
|
||||
}
|
||||
+8
-26
@@ -1,16 +1,16 @@
|
||||
package ru.vtosters.lite.music.cache;
|
||||
package ru.vtosters.lite.music.cache.delegate;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
|
||||
|
||||
public class MusicCacheDbDelegate {
|
||||
static synchronized private MusicCacheDb connectToDb(Context context) {
|
||||
synchronized (MusicCacheDb.class) {
|
||||
return new MusicCacheDb(context);
|
||||
}
|
||||
private static MusicCacheDb connectToDb(Context context) {
|
||||
return new MusicCacheDb(context);
|
||||
}
|
||||
|
||||
public static void addTrack(Context context,
|
||||
@@ -43,27 +43,9 @@ public class MusicCacheDbDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<MusicTrack> getAllTracks(Context context) {
|
||||
public static MusicTrack getTrackById(Context context, String trackId) {
|
||||
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();
|
||||
return db.getTrackById(trackId);
|
||||
}
|
||||
}
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package ru.vtosters.lite.music.cache.delegate;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import com.vk.dto.music.Playlist;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.lite.music.cache.db.PlaylistCacheDb;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
import ru.vtosters.lite.utils.music.PlaylistUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PlaylistCacheDbDelegate {
|
||||
static synchronized private PlaylistCacheDb connectToDb(Context context) {
|
||||
synchronized (PlaylistCacheDb.class) {
|
||||
return new PlaylistCacheDb(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addPlaylist(Context context, Playlist playlist) {
|
||||
try (var db = connectToDb(context)) {
|
||||
db.addPlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, generatePhotoJSON(playlist));
|
||||
}
|
||||
}
|
||||
|
||||
public static JSONObject generatePhotoJSON(Playlist playlist) {
|
||||
if (PlaylistUtils.getThumb(playlist) != null) {
|
||||
JSONObject photo = new JSONObject();
|
||||
String playlistId = playlist.v1();
|
||||
try {
|
||||
photo.put("height", 300);
|
||||
photo.put("width", 300);
|
||||
|
||||
addPhotoSizeToJSON(photo, playlistId, 600, "photo_600");
|
||||
addPhotoSizeToJSON(photo, playlistId, 34, "photo_34");
|
||||
addPhotoSizeToJSON(photo, playlistId, 1200, "photo_1200");
|
||||
addPhotoSizeToJSON(photo, playlistId, 68, "photo_68");
|
||||
addPhotoSizeToJSON(photo, playlistId, 135, "photo_135");
|
||||
addPhotoSizeToJSON(photo, playlistId, 300, "photo_300");
|
||||
addPhotoSizeToJSON(photo, playlistId, 270, "photo_270");
|
||||
|
||||
return photo;
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addPhotoSizeToJSON(JSONObject photo, String playlistId, int size, String key) throws JSONException {
|
||||
photo.put(key, Uri.fromFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, size)).toString());
|
||||
Log.d("Playlist", "thumb link " + Uri.fromFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, size)).toString());
|
||||
}
|
||||
|
||||
public static void deletePlaylist(Context context, String playlistId) {
|
||||
try (var db = connectToDb(context)) {
|
||||
db.deletePlaylistWithTracks(playlistId);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isCachedPlaylist(Context context, String id) {
|
||||
try (var db = connectToDb(context)) {
|
||||
String[] parts = id.split("_");
|
||||
int playlistId = Integer.parseInt(parts[1]);
|
||||
int ownerId = Integer.parseInt(parts[0]);
|
||||
|
||||
return db.isCachedPlaylist(playlistId, ownerId);
|
||||
}
|
||||
}
|
||||
|
||||
public static long getPlaylistsCount(Context context) {
|
||||
try (var db = connectToDb(context)) {
|
||||
return db.getPlaylistsCount();
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Playlist> getAllPlaylists(Context context) {
|
||||
try (var db = connectToDb(context)) {
|
||||
return db.getAllPlaylists();
|
||||
}
|
||||
}
|
||||
|
||||
public static Playlist getPlaylistById(Context context, String playlistId) {
|
||||
try (var db = connectToDb(context)) {
|
||||
String[] parts = playlistId.split("_");
|
||||
int id = Integer.parseInt(parts[1]);
|
||||
int ownerId = Integer.parseInt(parts[0]);
|
||||
|
||||
return db.getPlaylistById(id, ownerId);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addTrackToPlaylist(Context context, String playlistId, String trackId) {
|
||||
try (var db = connectToDb(context)) {
|
||||
db.addTrackToPlaylist(playlistId, trackId);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeTrackFromPlaylist(Context context, String playlistId, String trackId) {
|
||||
try (var db = connectToDb(context)) {
|
||||
db.removeTrackFromPlaylist(playlistId, trackId);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<MusicTrack> getTracksInPlaylist(Context context, String playlistId) {
|
||||
try (var db = connectToDb(context)) {
|
||||
return db.getTracksInPlaylist(playlistId);
|
||||
}
|
||||
}
|
||||
|
||||
public static void drop(Context context) {
|
||||
context.deleteDatabase(PlaylistCacheDb.Constants.DB_NAME);
|
||||
}
|
||||
}
|
||||
+70
-68
@@ -1,93 +1,82 @@
|
||||
package ru.vtosters.lite.music.cache.helpers;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import com.vk.dto.music.Playlist;
|
||||
import com.vtosters.lite.R;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.downloader.M3UDownloader;
|
||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
|
||||
import ru.vtosters.lite.utils.NetworkUtils;
|
||||
import ru.vtosters.lite.utils.music.PlaylistUtils;
|
||||
|
||||
public class PlaylistHelper {
|
||||
|
||||
public static Playlist createCachedPlaylistMetadata() {
|
||||
try {
|
||||
return new Playlist(getPlaylist());
|
||||
return new Playlist(getCachedSongsPlaylist());
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Playlist createCachedPlaylistMetadata(String id) {
|
||||
var track = TracklistHelper.getTracksOne(id).get(0);
|
||||
var albumLink = track.I;
|
||||
var thumb = albumLink.u1();
|
||||
return createAlbum(
|
||||
thumb != null ? thumb.h(600) : "",
|
||||
albumLink.getTitle(),
|
||||
M3UDownloader.getArtists(track),
|
||||
track.y1(),
|
||||
albumLink.getId()
|
||||
);
|
||||
}
|
||||
|
||||
public static Playlist createAlbum(String photo, String title, String artist, String trackId, int albumId) {
|
||||
public static JSONArray addCachedPlaylists(JSONArray jsonArray, Boolean noPlaylists) {
|
||||
try {
|
||||
var jPhoto = new JSONObject();
|
||||
if (!photo.isEmpty())
|
||||
jPhoto.put("height", 600)
|
||||
.put("width", 600)
|
||||
.putOpt("photo_300", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 300)).toString())
|
||||
.putOpt("photo_600", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 600)).toString());
|
||||
var obj = new JSONObject()
|
||||
.put("id", -2)
|
||||
.put("owner_id", albumId)
|
||||
.put("type", 1)
|
||||
.put("album_type", "main_only")
|
||||
.put("title", title)
|
||||
.put("description", albumId + "/ " + AndroidUtils.getString(R.string.cached_album_warning))
|
||||
.put("main_artists", new JSONArray()
|
||||
.put(new JSONObject().put("name", artist)
|
||||
.put("id", "-1")
|
||||
.put("domain", "-1")))
|
||||
.putOpt("photo", jPhoto)
|
||||
.put("count", 0);
|
||||
return new Playlist(obj);
|
||||
if (noPlaylists) {
|
||||
jsonArray.put(new JSONArray().put(getCachedSongsPlaylist()));
|
||||
} else {
|
||||
jsonArray.put(getCachedSongsPlaylist());
|
||||
}
|
||||
|
||||
for (Playlist playlist : MusicCacheImpl.getPlaylists()) {
|
||||
if (noPlaylists) {
|
||||
jsonArray.put(new JSONArray().put(generatePlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, PlaylistUtils.getThumb(playlist), playlist.O)));
|
||||
} else {
|
||||
jsonArray.put(generatePlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, PlaylistUtils.getThumb(playlist), playlist.O));
|
||||
}
|
||||
|
||||
Log.d("PlaylistHelper", "Playlist cache added: " + playlist.a + " " + playlist.b + " " + playlist.C + " " + playlist.g + " " + playlist.B);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
e.fillInStackTrace();
|
||||
}
|
||||
return jsonArray;
|
||||
}
|
||||
|
||||
public static List<Playlist> getAlbumPlaylists() {
|
||||
List<Playlist> list = new ArrayList<>();
|
||||
for (var track : MusicCacheImpl.getPlaylist()) {
|
||||
var albumLink = track.I;
|
||||
var thumb = albumLink.u1();
|
||||
list.add(createAlbum(
|
||||
thumb != null ? thumb.h(600) : "",
|
||||
albumLink.getTitle(),
|
||||
M3UDownloader.getArtists(track),
|
||||
track.y1(),
|
||||
albumLink.getId()
|
||||
));
|
||||
public static JSONObject generatePlaylist(int id, int owner_id, boolean is_explicit, String title, String description, JSONObject photo, int count) throws JSONException {
|
||||
JSONObject playlist = new JSONObject()
|
||||
.put("id", id)
|
||||
.put("owner_id", owner_id)
|
||||
.put("type", 0)
|
||||
.put("album_type", "playlist")
|
||||
.put("is_explicit", is_explicit)
|
||||
.put("title", title)
|
||||
.put("description", description)
|
||||
.put("access_key", "cache")
|
||||
.put("genres", new JSONArray())
|
||||
.put("is_following", false)
|
||||
.put("followers", 0)
|
||||
.put("plays", 0)
|
||||
.put("create_time", 0)
|
||||
.put("update_time", 0)
|
||||
.put("subtitle", "")
|
||||
.put("meta", new JSONObject()
|
||||
.put("view", "compact"))
|
||||
.put("count", count);
|
||||
|
||||
if (photo != null) {
|
||||
playlist.put("photo", photo);
|
||||
}
|
||||
return list;
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
public static JSONObject getPlaylist() throws JSONException {
|
||||
public static JSONObject getCachedSongsPlaylist() throws JSONException {
|
||||
return new JSONObject()
|
||||
.put("id", -1)
|
||||
.put("owner_id", getUserId())
|
||||
.put("owner_id", AccountManagerUtils.getUserId())
|
||||
.put("type", 0)
|
||||
.put("album_type", "playlist")
|
||||
.put("title", AndroidUtils.getString(R.string.cached_tracks_title))
|
||||
@@ -112,7 +101,21 @@ public class PlaylistHelper {
|
||||
.put("height", 600))
|
||||
.put("meta", new JSONObject()
|
||||
.put("view", "compact"))
|
||||
.put("count", 0);
|
||||
.put("count", MusicCacheImpl.getAllOwnTracks().size());
|
||||
}
|
||||
|
||||
public static JSONArray getCachedPlaylistsIds() {
|
||||
JSONArray arr = new JSONArray();
|
||||
|
||||
if (NetworkUtils.isNetworkConnected() && !MusicCacheImpl.getAllOwnTracks().isEmpty()) {
|
||||
arr.put(AccountManagerUtils.getUserId() + "_-1");
|
||||
}
|
||||
|
||||
for (var playlist : MusicCacheImpl.getPlaylists()) {
|
||||
arr.put(playlist.v1());
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static JSONObject getCatalogPlaylist() throws JSONException {
|
||||
@@ -122,18 +125,17 @@ public class PlaylistHelper {
|
||||
.put("layout", new JSONObject()
|
||||
.put("name", "large_slider")
|
||||
.put("is_editable", 0)
|
||||
.put("owner_id", getUserId()))
|
||||
.put("playlists_ids", new JSONArray()
|
||||
.put(getUserId() + "_-1"));
|
||||
.put("owner_id", AccountManagerUtils.getUserId()))
|
||||
.put("playlists_ids", getCachedPlaylistsIds());
|
||||
}
|
||||
|
||||
public static JSONObject getCatalogHeader() throws JSONException {
|
||||
public static JSONObject getCatalogHeader(String text) throws JSONException {
|
||||
return new JSONObject()
|
||||
.put("id", "cache")
|
||||
.put("data_type", "none")
|
||||
.put("layout", new JSONObject()
|
||||
.put("name", "header")
|
||||
.put("title", AndroidUtils.getString(R.string.cached_tracks_title)));
|
||||
.put("title", text));
|
||||
}
|
||||
|
||||
public static JSONObject getCatalogSeparator() throws JSONException {
|
||||
@@ -143,4 +145,4 @@ public class PlaylistHelper {
|
||||
.put("layout", new JSONObject()
|
||||
.put("name", "separator"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-8
@@ -8,6 +8,8 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.cache.delegate.MusicCacheDbDelegate;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -22,7 +24,7 @@ public class TracklistHelper {
|
||||
}
|
||||
|
||||
public static List<MusicTrack> getTracks() {
|
||||
List<MusicTrack> tracks = MusicCacheImpl.getAllTracks();
|
||||
List<MusicTrack> tracks = MusicCacheImpl.getAllOwnTracks();
|
||||
|
||||
boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false);
|
||||
|
||||
@@ -33,13 +35,8 @@ public class TracklistHelper {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
|
||||
public static List<MusicTrack> getTracks(String id) {
|
||||
return MusicCacheImpl.getAlbumById(id);
|
||||
}
|
||||
|
||||
public static List<MusicTrack> getTracksOne(String id) {
|
||||
return MusicCacheImpl.getFirstAlbumTrack(id);
|
||||
public static MusicTrack getTrack(String id) {
|
||||
return MusicCacheDbDelegate.getTrackById(AndroidUtils.getGlobalContext(), id);
|
||||
}
|
||||
|
||||
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package ru.vtosters.lite.music.callback;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
||||
import ru.vtosters.lite.music.interfaces.Callback;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
@@ -40,12 +43,12 @@ public class MusicCallbackBuilder {
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
Log.e("AudioDownloader", "native libs");
|
||||
Log.e("AudioDownloader", e.getMessage());
|
||||
onFailure();
|
||||
onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
public void onFailure(Throwable e) {
|
||||
notification.setContentText(AndroidUtils.getString(R.string.load_audio_error)).setProgress(0, 0, false);
|
||||
notificationManager.notify(notificationId, notification.build());
|
||||
}
|
||||
@@ -74,10 +77,11 @@ public class MusicCallbackBuilder {
|
||||
public void onSuccess() {
|
||||
notification.setContentText(AndroidUtils.getString(R.string.player_download_finished));
|
||||
notification.setProgress(0, 0, false);
|
||||
notification.setSmallIcon(android.R.drawable.stat_sys_download_done);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
public void onFailure(Throwable e) {
|
||||
notification.setContentText(AndroidUtils.getString(R.string.load_audio_error)).setProgress(0, 0, false);
|
||||
notificationManager.notify(notificationId, notification.build());
|
||||
}
|
||||
|
||||
@@ -53,7 +53,9 @@ public class PlaylistConverter {
|
||||
var request = new Request.a()
|
||||
.b(requestUrl)
|
||||
.a("Accept-Encoding", "gzip")
|
||||
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
|
||||
.a("User-Agent", Network.l.c().a())
|
||||
.a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||
.a();
|
||||
|
||||
try {
|
||||
var response = CompletableFuture.supplyAsync(() -> {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package ru.vtosters.lite.music.converter.ts;
|
||||
|
||||
import com.arthenica.ffmpegkit.FFmpegKit;
|
||||
import com.vk.dto.music.Artist;
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.music.downloader.M3UDownloader;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FFMpeg {
|
||||
|
||||
/**
|
||||
* @param in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger}
|
||||
* @param out Output .mp3 file
|
||||
* @param track Track to be converted
|
||||
* @return True if success, else false
|
||||
*/
|
||||
public static void convert(File in, String out, MusicTrack track) {
|
||||
// convert .ts file to .mp3 file via ffmpeg
|
||||
var cmd = " -y -i \"" + in.getAbsolutePath() + '"'
|
||||
+ " -map 0 -dn"
|
||||
+ " -loglevel error"
|
||||
+ " -hide_banner"
|
||||
+ " -write_id3v2 1"
|
||||
+ setMetadata(track)
|
||||
+ " -c copy \"" + out + '"';
|
||||
var ses = FFmpegKit.execute(cmd);
|
||||
if (ses.getReturnCode().isValueCancel()) throw new RuntimeException(ses.getLogsAsString());
|
||||
}
|
||||
|
||||
private static String setMetadata(MusicTrack track) {
|
||||
return Preferences.getBoolValue("setMetaData", true) ? " -metadata title=\"" + IOUtils.getValidFileName(M3UDownloader.getTitle(track)) + '"'
|
||||
+ (track.L == null || track.I == null
|
||||
? " -metadata artist=\"" + normalizeMetadata(track.C) + '"'
|
||||
: " -metadata artist=\"" + normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", "))) + '"'
|
||||
+ " -metadata album=\"" + normalizeMetadata(track.I.getTitle()) + '"') : "";
|
||||
}
|
||||
|
||||
private static String normalizeMetadata(String in) {
|
||||
return in.replaceAll("[\\\\/:*?\"<>|]", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package ru.vtosters.lite.music.converter.ts;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class MpegDemuxer {
|
||||
public static void convert(byte[] inBytes, String out) throws IOException {
|
||||
try (InputStream inStream = new ByteArrayInputStream(inBytes)) {
|
||||
convert(inStream, out);
|
||||
}
|
||||
}
|
||||
|
||||
public static void convert(InputStream in, String out) throws IOException {
|
||||
int packetSize = 188;
|
||||
int desiredPid = 256;
|
||||
|
||||
var buffer = new byte[packetSize];
|
||||
var packet = ByteBuffer.wrap(buffer).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (var fis = new BufferedInputStream(in); var fos = new BufferedOutputStream(new FileOutputStream(out))) {
|
||||
while (fis.read(buffer) != -1) {
|
||||
packet.clear();
|
||||
|
||||
int firstByte = packet.get() & 0xFF;
|
||||
int secondByte = packet.get() & 0xFF;
|
||||
int thirdByte = packet.get() & 0xFF;
|
||||
int fourthByte = packet.get() & 0xFF;
|
||||
|
||||
int syncByte = firstByte;
|
||||
int transportErrorIndicator = (secondByte & 0x80) >> 7;
|
||||
int payloadUnitStartIndicator = (secondByte & 0x40) >> 6;
|
||||
|
||||
int pid = ((secondByte & 0x1F) << 8) | (thirdByte & 0xFF);
|
||||
|
||||
int adaptationFieldControl = (fourthByte & 0x30) >> 4;
|
||||
|
||||
if (syncByte != 0x47) {
|
||||
Log.d("MpegDemuxer", "Invalid sync byte: 0x" + Integer.toHexString(syncByte));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (transportErrorIndicator == 1) {
|
||||
Log.d("MpegDemuxer", "Transport error indicator is set");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (adaptationFieldControl == 2 || adaptationFieldControl == 3) {
|
||||
int adaptationFieldLength = packet.get() & 0xFF;
|
||||
packet.position(packet.position() + adaptationFieldLength);
|
||||
}
|
||||
|
||||
if (pid == desiredPid) {
|
||||
if (payloadUnitStartIndicator == 1) {
|
||||
packet.position(packet.position() + 4);
|
||||
int pesPacketLength = packet.getShort() & 0xFFFF;
|
||||
|
||||
if (pesPacketLength != 0) {
|
||||
packet.position(packet.position() + 2);
|
||||
int pesHeaderDataLength = packet.get() & 0xFF;
|
||||
packet.position(packet.position() + pesHeaderDataLength);
|
||||
} else {
|
||||
Log.v("MpegDemuxer", "PES packet length is zero, that shouldn't happen in audio stream");
|
||||
}
|
||||
}
|
||||
|
||||
fos.write(packet.array(), packet.position(), packetSize - packet.position());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package ru.vtosters.lite.music.converter.ts;
|
||||
|
||||
import android.os.Build;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class TSMerger {
|
||||
/**
|
||||
* Merge multiple .ts files into one .ts file
|
||||
*
|
||||
* @param in Directory with .ts files
|
||||
*/
|
||||
public static void merge(File in, File out) {
|
||||
File[] tsesList = in.listFiles();
|
||||
|
||||
if (tsesList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream mergedByteArrayStream = new ByteArrayOutputStream();
|
||||
|
||||
sortTsesList(tsesList);
|
||||
|
||||
for (File file : tsesList) {
|
||||
if (file.getName().endsWith(".ts")) {
|
||||
try {
|
||||
mergedByteArrayStream.write(IOUtils.readFully(file));
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeOutputFile(out, mergedByteArrayStream);
|
||||
}
|
||||
|
||||
private static void sortTsesList(File[] tsesList) {
|
||||
Arrays.sort(tsesList, Comparator.comparingInt(TSMerger::getFileIndex));
|
||||
}
|
||||
|
||||
private static int getFileIndex(File file) {
|
||||
String fileName = file.getName();
|
||||
int dotIndex = fileName.indexOf('.', 1);
|
||||
return Integer.parseInt(fileName.substring(0, dotIndex));
|
||||
}
|
||||
|
||||
private static void writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) {
|
||||
byte[] bytes = mergedByteArrayStream.toByteArray();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
Files.write(out.toPath(), bytes);
|
||||
} catch (IOException ignored) {
|
||||
// ignored
|
||||
}
|
||||
} else {
|
||||
try (FileOutputStream fos = new FileOutputStream(out)) {
|
||||
fos.write(bytes);
|
||||
} catch (IOException ignored) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,9 @@ public class AudioGet {
|
||||
var request = new Request.a()
|
||||
.b(requestUrl)
|
||||
.a("Accept-Encoding", "gzip")
|
||||
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
|
||||
|
||||
.a("User-Agent", Network.l.c().a())
|
||||
.a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||
.a();
|
||||
try {
|
||||
var response = CompletableFuture.supplyAsync(() -> {
|
||||
try (Response resp = Network.b(CLIENT_API).a(request).execute()) {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package ru.vtosters.lite.music.downloader;
|
||||
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
|
||||
import com.vk.dto.music.Playlist;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
|
||||
import ru.vtosters.lite.music.interfaces.Callback;
|
||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
|
||||
public final class CachedDownloader implements ITrackDownloader {
|
||||
|
||||
private final ITrackDownloader origin;
|
||||
|
||||
public CachedDownloader(ITrackDownloader origin) {
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void download(MusicTrack track, Callback callback, Playlist playlist) {
|
||||
origin.download(track, new Callback() {
|
||||
@Override
|
||||
public void onProgress(int progress) {
|
||||
callback.onProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
new ThumbnailTrackDownloader().download(track, new Callback() {
|
||||
@Override public void onProgress(int progress) {
|
||||
|
||||
}
|
||||
|
||||
@Override public void onSuccess() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
callback.onFailure(e);
|
||||
}
|
||||
|
||||
@Override public void onSizeReceived(long size, long header) {
|
||||
|
||||
}
|
||||
}, null);
|
||||
|
||||
PlaylistCacheDbDelegate.addTrackToPlaylist(AndroidUtils.getGlobalContext(), playlist.v1(), track.y1());
|
||||
MusicCacheImpl.addTrack(track);
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
callback.onFailure(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSizeReceived(long size, long header) {
|
||||
callback.onSizeReceived(size, header);
|
||||
}
|
||||
}, playlist);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package ru.vtosters.lite.music.downloader;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.mpatric.mp3agic.ID3v23Tag;
|
||||
import com.mpatric.mp3agic.InvalidDataException;
|
||||
import com.mpatric.mp3agic.Mp3File;
|
||||
import com.mpatric.mp3agic.NotSupportedException;
|
||||
import com.mpatric.mp3agic.UnsupportedTagException;
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import com.vk.dto.music.Artist;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
|
||||
public class ID3Tagger {
|
||||
public static void tag(File file, MusicTrack track) {
|
||||
try {
|
||||
// https://github.com/mpatric/mp3agic/issues/69#issuecomment-57897874
|
||||
// TODO: get rid of file duplication
|
||||
var oldFile = new File(file.getAbsolutePath().replace(".mp3", "_old.mp3"));
|
||||
if (!file.renameTo(oldFile)) {
|
||||
Log.e("ID3Tagger", "Failed to rename file");
|
||||
return;
|
||||
}
|
||||
|
||||
var mp3File = new Mp3File(oldFile);
|
||||
if (mp3File.hasId3v2Tag()) {
|
||||
var id3v2Tag = new ID3v23Tag();
|
||||
|
||||
if (track.L == null || track.I == null) {
|
||||
id3v2Tag.setArtist(track.C);
|
||||
} else {
|
||||
var artists = track.L.stream().map(Artist::w1).collect(Collectors.joining(Preferences.metadataSeparator()));
|
||||
id3v2Tag.setArtist(artists);
|
||||
id3v2Tag.setAlbum(track.I.getTitle());
|
||||
}
|
||||
|
||||
id3v2Tag.setTitle(Mp3Downloader.getTitle(track));
|
||||
|
||||
mp3File.setId3v2Tag(id3v2Tag);
|
||||
mp3File.save(file.getAbsolutePath());
|
||||
|
||||
if (!oldFile.delete()) {
|
||||
Log.e("ID3Tagger", "Failed to delete file");
|
||||
}
|
||||
}
|
||||
} catch (IOException | UnsupportedTagException | InvalidDataException |
|
||||
NotSupportedException e) {
|
||||
Log.e("ID3Tagger", "Failed to tag file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
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.Artist;
|
||||
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;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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(() -> {
|
||||
if (cache) {
|
||||
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 + ')' : "");
|
||||
}
|
||||
|
||||
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" : getArtists(track) + " - " + 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();
|
||||
}
|
||||
|
||||
public static String getArtists(MusicTrack track) {
|
||||
return (track.L == null || track.I == null
|
||||
? normalizeMetadata(track.C)
|
||||
: normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", "))));
|
||||
}
|
||||
|
||||
private static String normalizeMetadata(String in) {
|
||||
return in.replaceAll("[\\\\/:*?\"<>|]", "");
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
//package ru.vtosters.lite.music;
|
||||
//
|
||||
//import android.text.TextUtils;
|
||||
//import android.util.Log;
|
||||
//import android.widget.Toast;
|
||||
//
|
||||
//import com.vk.dto.music.MusicTrack;
|
||||
//
|
||||
//import org.json.JSONException;
|
||||
//import org.json.JSONObject;
|
||||
//
|
||||
//import java.io.File;
|
||||
//import java.io.IOException;
|
||||
//
|
||||
//import okhttp3.Call;
|
||||
//import okhttp3.Callback;
|
||||
//import okhttp3.OkHttpClient;
|
||||
//import okhttp3.Request;
|
||||
//import okhttp3.Response;
|
||||
//import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||
//import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||
//import ru.vtosters.lite.utils.AndroidUtils;
|
||||
//
|
||||
//public class MP3Downloader implements ITrackDownloader {
|
||||
// private static final String URL_FORMAT = "https://api.vk.com/method/audio.getById?v=5.131&access_token=%s&audios=%s©right=";
|
||||
//
|
||||
// private static final OkHttpClient client = new OkHttpClient();
|
||||
//
|
||||
// public static void downloadMP3(MusicTrack track) {
|
||||
// var token = AndroidUtils.getPreferences().getString("vk_admin_token", "");
|
||||
// if (token.isEmpty()) {
|
||||
// Toast.makeText(AndroidUtils.getGlobalContext(), "Обновите токен VK Admin в настройках", Toast.LENGTH_SHORT).show();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var request = new Request.a()
|
||||
// .b(String.format(URL_FORMAT, token, track.y1()))
|
||||
// .a();
|
||||
// client.a(request).a(new Callback() {
|
||||
// @Override
|
||||
// public void a(Call call, IOException e) {
|
||||
// Log.d("MP3Downloader", e + "");
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void a(Call call, Response response) throws IOException {
|
||||
// var payload = response.a().g();
|
||||
// Log.d("MP3Downloader", payload);
|
||||
// try {
|
||||
// JSONObject jsonObj = new JSONObject(payload);
|
||||
// if (!jsonObj.has("response"))
|
||||
// return;
|
||||
// JSONObject responseNode = new JSONObject(payload).getJSONArray("response")
|
||||
// .getJSONObject(0);
|
||||
// if (!responseNode.has("url"))
|
||||
// return;
|
||||
// var url = responseNode.optString("url");
|
||||
// Log.d("MP3Downloader", url);
|
||||
// if (TextUtils.isEmpty(url))
|
||||
// return;
|
||||
// if (VKM3UParser.isM3U8(url)) {
|
||||
// AudioDownloader.downloadM3U8(track);
|
||||
// return;
|
||||
// }
|
||||
// } catch (JSONException e) {
|
||||
// Log.d("MP3Downloader", e + "");
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.interfaces.Callback callback) {
|
||||
//
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,98 @@
|
||||
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 java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import com.vk.dto.music.Playlist;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.music.converter.ts.MpegDemuxer;
|
||||
import ru.vtosters.lite.music.interfaces.Callback;
|
||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
|
||||
public final class Mp3Downloader implements ITrackDownloader {
|
||||
private final File outputFile;
|
||||
|
||||
public Mp3Downloader(File outputFile) {
|
||||
this.outputFile = outputFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void download(MusicTrack track, Callback callback, Playlist playlist) {
|
||||
String uri = track.D;
|
||||
if (Objects.requireNonNull(uri).isEmpty()) {
|
||||
String msg = "link error: " + track.y1() + ", title: " + Mp3Downloader.getTitle(track);
|
||||
Log.d("Mp3Downloader", msg);
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
|
||||
var tsParser = new com.google.android.exoplayer2.source.hls.playlist.h(e.a(track.D));
|
||||
var baseUri = track.D.substring(0, track.D.lastIndexOf("/") + 1);
|
||||
|
||||
try {
|
||||
var segments = ((f) tsParser.a(Uri.parse(baseUri), IOUtils.openStream(track.D))).o;
|
||||
|
||||
byte[] buff = getMergedTs(baseUri, segments, callback);
|
||||
MpegDemuxer.convert(buff, outputFile.getAbsolutePath());
|
||||
|
||||
if (Preferences.getBoolValue("setMetaData", false)) {
|
||||
ID3Tagger.tag(outputFile, track);
|
||||
}
|
||||
|
||||
callback.onSuccess();
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
callback.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getMergedTs(String baseUri, List<a> segments, Callback callback) throws IOException, GeneralSecurityException {
|
||||
byte[] total = new byte[0];
|
||||
|
||||
int size = segments.size();
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
|
||||
|
||||
int progress = 0;
|
||||
for (var segment : segments) {
|
||||
var buff = IOUtils.readFully(IOUtils.openStream(baseUri + segment.a/*url*/));
|
||||
if (!TextUtils.isEmpty(segment.g)) {
|
||||
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
|
||||
if (cipherBytes.length != 16) cipherBytes = new byte[16];
|
||||
var cipherIv = new IvParameterSpec(cipherBytes);
|
||||
cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIv);
|
||||
buff = cipher.doFinal(buff);
|
||||
}
|
||||
|
||||
// copy on write
|
||||
final int len = total.length, n = buff.length;
|
||||
byte[] newBytes = Arrays.copyOf(total, len + n);
|
||||
System.arraycopy(buff, 0, newBytes, len, n);
|
||||
total = newBytes;
|
||||
|
||||
callback.onProgress(10 + Math.round(80.0F * (++progress) / size));
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static String getTitle(MusicTrack track) {
|
||||
return track.f + (!TextUtils.isEmpty(track.g) ? '(' + track.g + ')' : "");
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,67 @@
|
||||
package ru.vtosters.lite.music.downloader;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import com.vk.dto.music.Playlist;
|
||||
import ru.vtosters.lite.music.interfaces.Callback;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import java8.util.concurrent.CompletableFuture;
|
||||
|
||||
public class PlaylistDownloader {
|
||||
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
|
||||
var outDir = new File(path);
|
||||
if (!outDir.exists())
|
||||
if (outDir.mkdirs()) Log.v("PlaylistDownloader", "Directory created");
|
||||
else Log.e("PlaylistDownloader", "Directory creation failed");
|
||||
var downloadedTracks = new AtomicInteger();
|
||||
for (var musicTrack : playlist)
|
||||
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
|
||||
@Override
|
||||
public void onProgress(int progress) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
callback.onProgress(downloadedTracks.incrementAndGet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
callback.onFailure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSizeReceived(long size, long header) {
|
||||
}
|
||||
});
|
||||
Callback delegate = new ProgressCallback(callback);
|
||||
CompletableFuture.allOf(playlist
|
||||
.stream()
|
||||
.map(x -> {
|
||||
Callback.CompletableFutureCallback d = new
|
||||
Callback.CompletableFutureCallback(delegate);
|
||||
TrackDownloader.downloadTrack(x, path, d);
|
||||
return d;
|
||||
})
|
||||
.toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback) {
|
||||
var downloadedTracks = new AtomicInteger();
|
||||
for (var musicTrack : playlist)
|
||||
TrackDownloader.cacheTrack(musicTrack, new Callback() {
|
||||
@Override
|
||||
public void onProgress(int progress) {
|
||||
}
|
||||
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback, Playlist playlistId) {
|
||||
Callback delegate = new ProgressCallback(callback);
|
||||
CompletableFuture.allOf(playlist
|
||||
.stream()
|
||||
.map(x -> {
|
||||
Callback.CompletableFutureCallback d = new Callback.CompletableFutureCallback(delegate);
|
||||
TrackDownloader.cacheTrack(x, d, playlistId);
|
||||
return d;
|
||||
})
|
||||
.toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
callback.onProgress(downloadedTracks.incrementAndGet());
|
||||
}
|
||||
public static final class ProgressCallback implements Callback {
|
||||
private final AtomicInteger progress = new AtomicInteger();
|
||||
private final Callback origin;
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
callback.onFailure();
|
||||
}
|
||||
public ProgressCallback(Callback origin) {
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSizeReceived(long size, long header) {
|
||||
}
|
||||
});
|
||||
@Override public void onProgress(int progress) {}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
origin.onProgress(progress.incrementAndGet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
origin.onFailure(e);
|
||||
}
|
||||
|
||||
@Override public void onSizeReceived(long size, long header) {}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package ru.vtosters.lite.music.downloader;
|
||||
|
||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
public class ThumbnailDownloader {
|
||||
|
||||
public static void downloadThumbnails(MusicTrack track) throws IOException {
|
||||
JSONObject json = track.J();
|
||||
JSONObject album = json.optJSONObject("album");
|
||||
if (album == null) return;
|
||||
JSONObject thumb = album.optJSONObject("thumb");
|
||||
if (thumb == null) return;
|
||||
JSONArray sizes = thumb.optJSONArray("sizes");
|
||||
String trackId = LibVKXClient.asId(track);
|
||||
if (sizes == null) {
|
||||
JSONArray names = thumb.names();
|
||||
if (names == null) return;
|
||||
for (int i = 0; i < names.length(); ++i) {
|
||||
String name = names.optString(i);
|
||||
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < sizes.length(); ++i) {
|
||||
JSONObject size = sizes.optJSONObject(i);
|
||||
int width = size.optInt("width");
|
||||
String src = size.optString("src");
|
||||
downloadThumbnail(src, width, trackId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
|
||||
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package ru.vtosters.lite.music.downloader;
|
||||
|
||||
import android.util.Log;
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import com.vk.dto.music.Playlist;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import ru.vtosters.lite.music.interfaces.Callback;
|
||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
import ru.vtosters.lite.utils.music.PlaylistUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
public class ThumbnailPlaylistDownloader implements ITrackDownloader {
|
||||
|
||||
@Override
|
||||
public void download(MusicTrack track, Callback callback, Playlist playlist) {
|
||||
JSONObject json = PlaylistUtils.getThumb(playlist); // get thumbnail photo
|
||||
|
||||
if (json == null) {
|
||||
callback.onFailure(new IOException("No thumbnail found"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> sizes = List.of("600", "1200", "68", "135", "300", "270"); // define the order of sizes to download
|
||||
JSONArray sizesArray = json.getJSONArray("sizes");
|
||||
for (String size : sizes) {
|
||||
for (int i = 0; i < sizesArray.length(); i++) {
|
||||
JSONObject sizeObj = sizesArray.getJSONObject(i);
|
||||
if (sizeObj.getInt("width") == Integer.parseInt(size)) {
|
||||
String url = sizeObj.getString("src");
|
||||
|
||||
if (url.isEmpty()) continue;
|
||||
|
||||
Log.d("Playlist", "downloading thumb " + url);
|
||||
|
||||
downloadThumbnailPlaylist(url, Integer.parseInt(size), playlist.v1());
|
||||
}
|
||||
}
|
||||
}
|
||||
callback.onSuccess();
|
||||
} catch (IOException | JSONException e) {
|
||||
callback.onFailure(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadThumbnailPlaylist(String url, int res, String playlistId) throws IOException {
|
||||
IOUtils.writeToFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, res), IOUtils.readFully(new URL(url).openStream()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package ru.vtosters.lite.music.downloader;
|
||||
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
|
||||
import com.vk.dto.music.Playlist;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||
import ru.vtosters.lite.music.interfaces.Callback;
|
||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
|
||||
public final class ThumbnailTrackDownloader implements ITrackDownloader {
|
||||
|
||||
@Override
|
||||
public void download(MusicTrack track, Callback callback, Playlist playlist) {
|
||||
JSONObject json = track.J();
|
||||
JSONObject album = json.optJSONObject("album");
|
||||
if (album == null) return;
|
||||
JSONObject thumb = album.optJSONObject("thumb");
|
||||
if (thumb == null) return;
|
||||
JSONArray sizes = thumb.optJSONArray("sizes");
|
||||
String trackId = LibVKXClient.asId(track);
|
||||
try {
|
||||
if (sizes == null) {
|
||||
JSONArray names = thumb.names();
|
||||
if (names == null) return;
|
||||
for (int i = 0; i < names.length(); ++i) {
|
||||
String name = names.optString(i);
|
||||
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < sizes.length(); ++i) {
|
||||
JSONObject size = sizes.optJSONObject(i);
|
||||
int width = size.optInt("width");
|
||||
String src = size.optString("src");
|
||||
downloadThumbnail(src, width, trackId);
|
||||
}
|
||||
}
|
||||
callback.onSuccess();
|
||||
} catch (IOException e) {
|
||||
callback.onFailure(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
|
||||
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,45 @@
|
||||
package ru.vtosters.lite.music.downloader;
|
||||
|
||||
import android.util.Log;
|
||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import ru.vtosters.lite.concurrent.VTExecutors;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.interfaces.Callback;
|
||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class TrackDownloader {
|
||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||
import com.vk.dto.music.Playlist;
|
||||
import ru.vtosters.lite.concurrent.VTExecutors;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.interfaces.Callback;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||
|
||||
public final class TrackDownloader {
|
||||
|
||||
private TrackDownloader() { }
|
||||
|
||||
public static void downloadTrack(MusicTrack track, String path, Callback callback) {
|
||||
if (track.D.isEmpty()) {
|
||||
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
|
||||
callback.onFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
download(track, path, callback, M3UDownloader.getInstance(), false);
|
||||
}
|
||||
|
||||
public static void cacheTrack(MusicTrack track, Callback callback) {
|
||||
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
|
||||
callback.onSuccess();
|
||||
return;
|
||||
} else if (track.D.isEmpty()) {
|
||||
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
|
||||
callback.onFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
var path = MusicCacheStorageUtils.getTrackDirById(LibVKXClient.asId(track)).getAbsolutePath();
|
||||
download(track, path, callback, M3UDownloader.getInstance(), true);
|
||||
}
|
||||
|
||||
private static void download(MusicTrack track, String path, Callback callback, ITrackDownloader downloader, boolean cache) {
|
||||
File outDir = new File(path);
|
||||
if (!outDir.exists())
|
||||
if (outDir.mkdir())
|
||||
if (!outDir.exists()) {
|
||||
if (outDir.mkdir()) {
|
||||
Log.v("TrackDownloader", "Directory created");
|
||||
else
|
||||
} else {
|
||||
Log.e("TrackDownloader", "Directory creation failed");
|
||||
}
|
||||
}
|
||||
File outputFile = new File(outDir, IOUtils.getValidFileName(MusicTrackUtils.getArtists(track) + " - " + Mp3Downloader.getTitle(track)) + ".mp3");
|
||||
|
||||
VTExecutors.getMusicDownloadExecutor().execute(
|
||||
() -> downloader.downloadTrack(track, outDir, callback, cache)
|
||||
);
|
||||
VTExecutors.getMusicDownloadExecutor().submit(() -> new Mp3Downloader(outputFile).download(track, callback, null));
|
||||
}
|
||||
|
||||
public static void cacheTrack(MusicTrack track, Callback callback, Playlist playlist) {
|
||||
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
|
||||
return;
|
||||
}
|
||||
|
||||
File outputFile = MusicCacheStorageUtils.getTrackFile(LibVKXClient.asId(track));
|
||||
|
||||
VTExecutors.getMusicDownloadExecutor().submit(() -> new CachedDownloader(new Mp3Downloader(outputFile)).download(track, callback, playlist));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,46 @@
|
||||
package ru.vtosters.lite.music.interfaces;
|
||||
|
||||
import java8.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface Callback {
|
||||
void onProgress(int progress);
|
||||
|
||||
void onSuccess();
|
||||
|
||||
void onFailure();
|
||||
void onFailure(Throwable e);
|
||||
|
||||
void onSizeReceived(long size, long header);
|
||||
|
||||
|
||||
class CompletableFutureCallback
|
||||
extends CompletableFuture<Void>
|
||||
implements Callback {
|
||||
private final Callback origin;
|
||||
|
||||
public CompletableFutureCallback(Callback origin) {
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(int progress) {
|
||||
origin.onProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
complete(null);
|
||||
origin.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
completeExceptionally(e);
|
||||
origin.onFailure(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSizeReceived(long size, long header) {
|
||||
origin.onSizeReceived(size, header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package ru.vtosters.lite.music.interfaces;
|
||||
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
|
||||
import java.io.File;
|
||||
import com.vk.dto.music.Playlist;
|
||||
|
||||
/**
|
||||
* Interface for track downloaders
|
||||
@@ -12,8 +11,8 @@ public interface ITrackDownloader {
|
||||
* Download track
|
||||
*
|
||||
* @param track Music track
|
||||
* @param outDir Directory where to download the track
|
||||
* @param callback Callback to call when download status changes
|
||||
* @param playlist
|
||||
*/
|
||||
void downloadTrack(MusicTrack track, File outDir, Callback callback, boolean cache);
|
||||
void download(MusicTrack track, Callback callback, Playlist playlist);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.NetworkUtils;
|
||||
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -32,6 +33,7 @@ public class UsersList {
|
||||
|
||||
Request request = new Request.a()
|
||||
.b(Utils.getDomain() + "/api/getUsersWithServiceDescriptionsAndBanners")
|
||||
.a("Accept-Encoding", "gzip")
|
||||
.a();
|
||||
|
||||
VtOkHttpClient.getInstance().a(request).a(new Callback() {
|
||||
@@ -43,7 +45,7 @@ public class UsersList {
|
||||
@Override
|
||||
public void a(Call call, Response response) {
|
||||
try {
|
||||
parseJson(new JSONObject(response.a().g()).getJSONObject("response"));
|
||||
parseJson(new JSONObject(GzipDecompressor.decompressResponse(response)).getJSONObject("response"));
|
||||
} catch (JSONException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ public class ThemesCore {
|
||||
private static int darken_color;
|
||||
|
||||
static {
|
||||
setExceptions();
|
||||
setThemedColors(ThemesUtils.getAccentColor());
|
||||
init();
|
||||
}
|
||||
|
||||
public static void setExceptions() {
|
||||
@@ -201,4 +200,9 @@ public class ThemesCore {
|
||||
public static boolean isCachedAccents() {
|
||||
return cachedAccents;
|
||||
}
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
setExceptions();
|
||||
setThemedColors(ThemesUtils.getAccentColor());
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,36 @@
|
||||
package ru.vtosters.lite.ui.dialogs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||
import com.vtosters.lite.R;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.LayoutUtils;
|
||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||
|
||||
public class RoundingSeekbarDialog {
|
||||
private static final String[] roundOptions = {
|
||||
"Круглые", "Закругленные", "Квадратные"
|
||||
};
|
||||
|
||||
public static void dialog(Context context) {
|
||||
LinearLayout mContainer = new LinearLayout(context);
|
||||
SeekBar seek = new SeekBar(context);
|
||||
seek.setMax(10);
|
||||
seek.setKeyProgressIncrement(0);
|
||||
seek.setProgress(Preferences.getPreferences().getInt("pic_rounding", 0));
|
||||
seek.setPadding(AndroidUtils.dp2px(16f), AndroidUtils.dp2px(16f), AndroidUtils.dp2px(16f), AndroidUtils.dp2px(16f));
|
||||
|
||||
mContainer.addView(seek, LayoutUtils.createLinear(-1, -2));
|
||||
|
||||
new VkAlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.pic_rounding_title))
|
||||
.setMessage(context.getString(R.string.pic_rounding_info))
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(context.getString(R.string.cancel), null)
|
||||
.setView(mContainer)
|
||||
.setPositiveButton(context.getString(R.string.vtl_confirm), (dialog, which) -> {
|
||||
Preferences.getPreferences().edit().putInt("pic_rounding", seek.getProgress()).commit();
|
||||
.setItems(roundOptions, (dialog, which) -> {
|
||||
int val = getRoundingValue(which);
|
||||
|
||||
Preferences.getPreferences().edit().putInt("pic_rounding", val).apply();
|
||||
LifecycleUtils.restartApplicationWithTimer();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
private static int getRoundingValue(int selectedOption) {
|
||||
int val = 0;
|
||||
|
||||
switch (selectedOption) {
|
||||
case 1 -> val = 10;
|
||||
case 2 -> val = 1;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,33 @@
|
||||
package ru.vtosters.lite.ui.fragments;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import androidx.preference.Preference;
|
||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||
import com.vk.dto.music.Playlist;
|
||||
import com.vtosters.lite.ui.SummaryListPreference;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.hooks.other.ThemesUtils;
|
||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||
import ru.vtosters.lite.music.LastFMScrobbler;
|
||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
|
||||
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
|
||||
private static final ExecutorService executor = Executors.newCachedThreadPool();
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
@@ -50,6 +54,20 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
|
||||
}
|
||||
);
|
||||
|
||||
if (MusicCacheImpl.hasPlaylist()) {
|
||||
PreferenceFragmentUtils.addPreference(
|
||||
getPreferenceScreen(),
|
||||
"cached_playlists",
|
||||
"Кешированные плейлисты",
|
||||
String.format("Скачано плейлистов: %d", MusicCacheImpl.getPlaylists().size()),
|
||||
null,
|
||||
preference -> {
|
||||
cachedPlaylistsDialog(requireContext());
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
PreferenceFragmentUtils.addPreference(
|
||||
getPreferenceScreen(),
|
||||
"audio_download",
|
||||
@@ -116,6 +134,59 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
|
||||
}
|
||||
);
|
||||
|
||||
PreferenceFragmentUtils.addPreference(
|
||||
getPreferenceScreen(),
|
||||
"metadataSeparator",
|
||||
"Разделитель для id3v2 тегов",
|
||||
null,
|
||||
null,
|
||||
preference -> {
|
||||
LinearLayout linearLayout = new LinearLayout(requireContext());
|
||||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
EditText separator = new EditText(requireContext());
|
||||
separator.setHint("По-умолчанию: ; с пробелом");
|
||||
separator.setTextColor(ThemesUtils.getTextAttr());
|
||||
separator.setHintTextColor(ThemesUtils.getSTextAttr());
|
||||
separator.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
|
||||
separator.setText(Preferences.metadataSeparator());
|
||||
linearLayout.addView(separator);
|
||||
separator.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) separator.getLayoutParams());
|
||||
margin.setMargins(AndroidUtils.dp2px(20f), 0, AndroidUtils.dp2px(20f), 0);
|
||||
separator.setLayoutParams(margin);
|
||||
|
||||
new VkAlertDialog.Builder(requireContext())
|
||||
.setTitle("Разделитель")
|
||||
.setView(linearLayout)
|
||||
.setPositiveButton("Сохранить", (dialog, which) -> {
|
||||
if (separator.getText().toString().isEmpty()) {
|
||||
AndroidUtils.sendToast("Разделитель не может быть пустым");
|
||||
return;
|
||||
}
|
||||
Preferences.setMetadataSeparator(separator.getText().toString());
|
||||
})
|
||||
.setNegativeButton("Отмена", (dialog, which) -> dialog.cancel())
|
||||
.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Рекомендации музыки");
|
||||
PreferenceFragmentUtils.addMaterialSwitchPreference(
|
||||
getPreferenceScreen(),
|
||||
"sendMusicMetrics",
|
||||
"Отправлять статистику",
|
||||
"Статистика прослушиваний необходима для работы рекомендаций музыки\n\nЕсли вас не интересуют рекомендации - смело отключайте данную функцию",
|
||||
null,
|
||||
true,
|
||||
(preference, o) -> {
|
||||
Preferences.getPreferences().edit().putBoolean("sendMusicMetrics", (boolean) o).apply();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
if (!Preferences.serverFeaturesDisable()) {
|
||||
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Интеграция Genius");
|
||||
PreferenceFragmentUtils.addMaterialSwitchPreference(
|
||||
@@ -283,6 +354,26 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
|
||||
.show();
|
||||
}
|
||||
|
||||
private static void cachedPlaylistsDialog(Context ctx) {
|
||||
List<Playlist> playlists = PlaylistCacheDbDelegate.getAllPlaylists(ctx);
|
||||
String[] playlistNames = new String[playlists.size()];
|
||||
|
||||
for (int i = 0; i < playlists.size(); i++) {
|
||||
playlistNames[i] = playlists.get(i).g; // Assuming Playlist class has a getTitle() method
|
||||
}
|
||||
|
||||
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(ctx);
|
||||
builder.setTitle("Скачанные плейлисты");
|
||||
builder.setItems(playlistNames, (dialog, which) -> {
|
||||
Playlist selectedPlaylist = playlists.get(which);
|
||||
String playlistId = selectedPlaylist.v1();
|
||||
PlaylistCacheDbDelegate.deletePlaylist(ctx, playlistId);
|
||||
AndroidUtils.sendToast("Плейлист удален");
|
||||
});
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void delcache(Context ctx) {
|
||||
new VkAlertDialog.Builder(ctx)
|
||||
.setTitle(com.vtosters.lite.R.string.warning)
|
||||
|
||||
@@ -92,6 +92,11 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference("batchmessages").setOnPreferenceChangeListener((preference, o) -> {
|
||||
LifecycleUtils.restartApplicationWithTimer();
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference("copyownlink").setOnPreferenceClickListener(preference -> {
|
||||
copyText("https://vk.com/id" + AccountManagerUtils.getUserId());
|
||||
ToastUtils.a(requireContext().getString(R.string.link_copied));
|
||||
|
||||
@@ -116,17 +116,6 @@ public class ThemesFragment extends TrackedMaterialPreferenceToolbarFragment {
|
||||
return true;
|
||||
});
|
||||
|
||||
var dockbarEditor = findPreference("dockbareditor");
|
||||
dockbarEditor.setSummary(AndroidUtils.getString(R.string.vtldocksumm) + ": " + DockBarEditorManager.getInstance().getSelectedTabs().size());
|
||||
dockbarEditor.setOnPreferenceClickListener(preference -> {
|
||||
NavigatorUtils.switchFragment(requireContext(), DockBarEditorFragment.class);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (Preferences.vkme() || AndroidUtils.isTablet()) {
|
||||
dockbarEditor.setVisible(false);
|
||||
}
|
||||
|
||||
if (AndroidUtils.isTablet()) {
|
||||
PreferenceCategory dockbarSettingsPreferenceCategory = (PreferenceCategory) findPreference("dockbarsett");
|
||||
dockbarSettingsPreferenceCategory.setVisible(false);
|
||||
|
||||
@@ -46,6 +46,7 @@ import ru.vtosters.lite.proxy.ProxyUtils;
|
||||
import ru.vtosters.lite.ssfs.Utils;
|
||||
import ru.vtosters.lite.themes.utils.RecolorUtils;
|
||||
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
|
||||
import ru.vtosters.lite.ui.components.DockBarEditorManager;
|
||||
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
||||
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
|
||||
import ru.vtosters.lite.utils.*;
|
||||
@@ -533,6 +534,20 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
|
||||
}
|
||||
);
|
||||
|
||||
if (!Preferences.vkme() && !AndroidUtils.isTablet()) {
|
||||
PreferenceFragmentUtils.addPreference(
|
||||
getPreferenceScreen(),
|
||||
"",
|
||||
requireContext().getString(R.string.dockbar_editor),
|
||||
requireContext().getString(R.string.vtldocksumm) + ": " + DockBarEditorManager.getInstance().getSelectedTabs().size(),
|
||||
R.drawable.ic_pin_outline_28,
|
||||
preference -> {
|
||||
NavigatorUtils.switchFragment(requireContext(), DockBarEditorFragment.class);
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
PreferenceFragmentUtils.addPreference(
|
||||
getPreferenceScreen(),
|
||||
"currcache",
|
||||
|
||||
@@ -17,6 +17,8 @@ import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import com.vk.core.util.AppContextHolder;
|
||||
import com.vk.core.util.ToastUtils;
|
||||
import com.vtosters.lite.general.fragments.WebViewFragment;
|
||||
|
||||
@@ -50,14 +52,8 @@ public class AndroidUtils {
|
||||
|
||||
@NonNull
|
||||
public static Context getGlobalContext() {
|
||||
try {
|
||||
Method getInitialApplicationMtd = ReflectionUtils.findMethod(Class.forName("android.app.AppGlobals"), "getInitialApplication");
|
||||
return (Context) getInitialApplicationMtd.invoke(null);
|
||||
} catch (Exception e) {
|
||||
Log.d("GlobalContext", "Error while fetching context via refl");
|
||||
}
|
||||
return LifecycleUtils.getCurrentActivity();
|
||||
} // Getting the global context through reflection to use context on application initialization
|
||||
return AppContextHolder.a;
|
||||
} // Getting the global context to use context on application initialization
|
||||
|
||||
public static Resources getResources() {
|
||||
return getGlobalContext().getResources();
|
||||
|
||||
@@ -2,17 +2,19 @@ package ru.vtosters.lite.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import com.vk.core.fragments.FragmentImpl;
|
||||
import com.vk.extensions.NavigatorExt;
|
||||
import com.vk.navigation.Navigator;
|
||||
import com.vtosters.lite.TabletDialogActivity;
|
||||
import ru.vtosters.lite.ui.fragments.VTSettings;
|
||||
|
||||
public class NavigatorUtils {
|
||||
|
||||
public static void switchFragment(Context context, Class<? extends FragmentImpl> fragmentClz) {
|
||||
var intent = new Navigator(fragmentClz)
|
||||
.b(context)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
Navigator navigator = new Navigator(fragmentClz, new Bundle());
|
||||
NavigatorExt.a(navigator, new TabletDialogActivity.b());
|
||||
navigator.a(context);
|
||||
}
|
||||
|
||||
public static void switchFragmentNavigator(Context context, Navigator fragmentClz) {
|
||||
|
||||
@@ -9,24 +9,24 @@ import static ru.vtosters.lite.foaf.FoafBase.getBypassedOnlineInfo;
|
||||
|
||||
public class OnlineBypass {
|
||||
public static JSONObject setOnlineInfo(JSONObject json) throws JSONException {
|
||||
var id = json.optInt("id");
|
||||
int id = json.optInt("id");
|
||||
if (id == AccountManagerUtils.getUserId()) {
|
||||
return json;
|
||||
}
|
||||
var onlineinfo = json.optJSONObject("online_info");
|
||||
JSONObject onlineinfo = json.optJSONObject("online_info");
|
||||
if (onlineinfo != null && !onlineinfo.optBoolean("visible") && !Preferences.serverFeaturesDisable()) {
|
||||
var bypassed = getBypassedOnlineInfo(id);
|
||||
if (bypassed.optInt("last_seen", 0) != 0) {
|
||||
json.remove("online_info");
|
||||
|
||||
var online_info = new JSONObject()
|
||||
JSONObject online_info = new JSONObject()
|
||||
.put("visible", true)
|
||||
.put("last_seen", bypassed.optInt("last_seen"))
|
||||
.put("is_online", bypassed.optBoolean("is_online"))
|
||||
.put("app_id", bypassed.optInt("app_id"))
|
||||
.put("is_mobile", bypassed.optBoolean("is_mobile"));
|
||||
|
||||
var last_seen = new JSONObject()
|
||||
JSONObject last_seen = new JSONObject()
|
||||
.put("platform", bypassed.optInt("platform"))
|
||||
.put("time", bypassed.optInt("last_seen"));
|
||||
|
||||
@@ -37,51 +37,95 @@ public class OnlineBypass {
|
||||
return json;
|
||||
}
|
||||
|
||||
public static JSONArray setOnlineInfoUsers(JSONArray profiles) throws JSONException {
|
||||
if (profiles == null || profiles.length() == 0 || Preferences.serverFeaturesDisable()) return profiles;
|
||||
public static JSONArray setOnlineInfoUsers(JSONArray profiles) {
|
||||
if (shouldReturnProfiles(profiles)) return profiles;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
var curVkId = AccountManagerUtils.getUserId();
|
||||
int curVkId = AccountManagerUtils.getUserId();
|
||||
|
||||
for (int i = 0; i < profiles.length(); i++) {
|
||||
JSONObject profile = profiles.getJSONObject(i);
|
||||
JSONObject profile = getProfile(profiles, i);
|
||||
int id = profile.optInt("id", -1);
|
||||
JSONObject onlinfo = profile.optJSONObject("online_info");
|
||||
if (id == curVkId || id < 0 || onlinfo == null || onlinfo.optBoolean("visible")) {
|
||||
continue;
|
||||
}
|
||||
sb.append(id);
|
||||
sb.append(",");
|
||||
}
|
||||
var ids = sb.toString();
|
||||
if (!ids.isEmpty()) {
|
||||
ids = ids.substring(0, ids.length() - 1);
|
||||
} else {
|
||||
return profiles;
|
||||
|
||||
if (shouldSkipProfile(id, curVkId, onlinfo)) continue;
|
||||
|
||||
sb.append(id).append(",");
|
||||
}
|
||||
|
||||
String ids = prepareIds(sb);
|
||||
if (ids.isEmpty()) return profiles;
|
||||
|
||||
JSONObject bypassedObj = getBypassedOnlineInfo(ids);
|
||||
for (int i = 0; i < profiles.length(); i++) {
|
||||
JSONObject profile = profiles.getJSONObject(i);
|
||||
JSONObject profile = getProfile(profiles, i);
|
||||
int id = profile.optInt("id");
|
||||
JSONObject bypassed = bypassedObj.optJSONObject(Integer.toString(id));
|
||||
if (bypassed == null) {
|
||||
continue;
|
||||
}
|
||||
profile.remove("online_info");
|
||||
|
||||
var online_info = new JSONObject()
|
||||
if (bypassed == null) continue;
|
||||
|
||||
JSONObject online_info = createOnlineInfo(bypassed);
|
||||
JSONObject last_seen = createLastSeen(bypassed);
|
||||
|
||||
updateProfile(profile, online_info, last_seen);
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
private static boolean shouldReturnProfiles(JSONArray profiles) {
|
||||
return profiles == null || profiles.length() == 0 || Preferences.serverFeaturesDisable();
|
||||
}
|
||||
|
||||
private static JSONObject getProfile(JSONArray profiles, int i) {
|
||||
try {
|
||||
return profiles.getJSONObject(i);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldSkipProfile(int id, int curVkId, JSONObject onlinfo) {
|
||||
return id == curVkId || id < 0 || onlinfo == null || onlinfo.optBoolean("visible");
|
||||
}
|
||||
|
||||
private static String prepareIds(StringBuilder sb) {
|
||||
String ids = sb.toString();
|
||||
if (!ids.isEmpty()) {
|
||||
ids = ids.substring(0, ids.length() - 1);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private static JSONObject createOnlineInfo(JSONObject bypassed) {
|
||||
try {
|
||||
return new JSONObject()
|
||||
.put("visible", true)
|
||||
.put("last_seen", bypassed.optInt("last_seen"))
|
||||
.put("is_online", bypassed.optBoolean("is_online"))
|
||||
.put("app_id", bypassed.optInt("app_id"))
|
||||
.put("is_mobile", bypassed.optBoolean("is_mobile"));
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
var last_seen = new JSONObject()
|
||||
private static JSONObject createLastSeen(JSONObject bypassed) {
|
||||
try {
|
||||
return new JSONObject()
|
||||
.put("platform", bypassed.optInt("platform"))
|
||||
.put("time", bypassed.optInt("last_seen"));
|
||||
|
||||
profile.put("last_seen", last_seen).put("online_info", online_info);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return profiles;
|
||||
private static void updateProfile(JSONObject profile, JSONObject online_info, JSONObject last_seen) {
|
||||
try {
|
||||
profile.remove("online_info");
|
||||
profile.put("last_seen", last_seen).put("online_info", online_info);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,14 +96,15 @@ public class VTVerifications {
|
||||
}
|
||||
|
||||
private static void processIds(JSONArray jsonIds, List<Integer> member) {
|
||||
if (jsonIds == null || jsonIds.length() == 0)
|
||||
if (jsonIds == null || jsonIds.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < jsonIds.length(); i++) {
|
||||
try {
|
||||
member.add(jsonIds.getInt(i));
|
||||
} catch (JSONException e) {
|
||||
e.fillInStackTrace();
|
||||
int id = jsonIds.optInt(i);
|
||||
|
||||
if (id != 0) {
|
||||
member.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import static ru.vtosters.hooks.other.ThemesUtils.*;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
||||
|
||||
public class WebViewColoringUtils {
|
||||
public static boolean isLoaded = false;
|
||||
public static String loadedCSS = "";
|
||||
public static String loadedCSSAmoled = "";
|
||||
|
||||
@@ -53,7 +52,6 @@ public class WebViewColoringUtils {
|
||||
public static void load() {
|
||||
loadedCSS = readCSS("vt_ui_accent.css");
|
||||
loadedCSSAmoled = readCSS("vt_ui_oled.css");
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
public static void inject(WebView webView, String str) {
|
||||
|
||||
@@ -11,32 +11,40 @@ public class MusicCacheStorageUtils {
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static File getTrackDirById(final String trackId) {
|
||||
final var res = new File(getCacheStorageDir(), trackId);
|
||||
public static File getTrackDirById(String trackId) {
|
||||
File res = new File(getCacheStorageDir(), trackId);
|
||||
res.mkdirs();
|
||||
return res;
|
||||
}
|
||||
|
||||
public static File getTrackFile(final String trackId) {
|
||||
public static File getTrackFile(String trackId) {
|
||||
return new File(getTrackDirById(trackId), "track.mp3");
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static File getThumbDirById(final String trackId) {
|
||||
final var res = new File(getTrackDirById(trackId), "thumbs");
|
||||
public static File getThumbDirById(String trackId) {
|
||||
File res = new File(getTrackDirById(trackId), "thumbs");
|
||||
res.mkdirs();
|
||||
return res;
|
||||
}
|
||||
|
||||
public static File getTrackThumb(final String trackId, final int factor) {
|
||||
public static File getTrackThumb(String trackId, int factor) {
|
||||
return new File(getThumbDirById(trackId), "thumb_" + factor + ".png");
|
||||
}
|
||||
|
||||
public static void removeTrackDirById(final String trackId) {
|
||||
public static File getPlaylistThumb(String playlistId, int factor) {
|
||||
return new File(getThumbDirById(playlistId), "thumb_playlist_" + factor + ".png");
|
||||
}
|
||||
|
||||
public static void removePlaylistThumb(String playlistId) {
|
||||
IOUtils.deleteRecursive(getThumbDirById(playlistId));
|
||||
}
|
||||
|
||||
public static void removeTrackDirById(String trackId) {
|
||||
IOUtils.deleteRecursive(getTrackDirById(trackId));
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
IOUtils.deleteRecursive(getCacheStorageDir());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ru.vtosters.lite.utils.music;
|
||||
|
||||
import com.vk.dto.music.MusicTrack;
|
||||
import com.vk.dto.music.Artist;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MusicTrackUtils {
|
||||
public static String getArtists(MusicTrack musicTrack) {
|
||||
return (musicTrack.L == null || musicTrack.I == null
|
||||
? normalizeMetadata(musicTrack.C)
|
||||
: normalizeMetadata(musicTrack.L.stream().map(Artist::w1).collect(Collectors.joining(", "))));
|
||||
}
|
||||
|
||||
private static String normalizeMetadata(String in) {
|
||||
return in.replaceAll("[\\\\/:*?\"<>|]", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ru.vtosters.lite.utils.music;
|
||||
|
||||
import android.util.Log;
|
||||
import com.vk.dto.music.Playlist;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class PlaylistUtils {
|
||||
public static JSONObject getThumb(Playlist playlist) {
|
||||
try {
|
||||
Log.d("Playlist", "thumb is not null");
|
||||
return playlist.F.J();
|
||||
} catch (Exception e) {
|
||||
Log.d("Playlist", "thumb is null");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package ru.vtosters.sponsorpost.internal;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -22,7 +23,7 @@ public class VotesPreferences {
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return preferences.getBoolean("sponsortesting", false);
|
||||
return preferences.getBoolean("sponsortesting", false) && !Preferences.serverFeaturesDisable();
|
||||
}
|
||||
|
||||
public static void setEnabled(boolean status) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package ru.vtosters.sponsorpost.utils;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import ru.vtosters.hooks.other.Preferences;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.sponsorpost.data.Post;
|
||||
|
||||
@@ -23,7 +24,7 @@ public class PostsPreferences {
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return preferences.getBoolean("sponsorpost", true);
|
||||
return preferences.getBoolean("sponsorpost", true) && !Preferences.serverFeaturesDisable();
|
||||
}
|
||||
|
||||
public static void setEnabled(boolean status) {
|
||||
|
||||
@@ -140,6 +140,7 @@
|
||||
<uses-permission android:name="com.mgoogle.android.c2dm.permission.RECEIVE" />
|
||||
<uses-permission android:name="app.revanced.android.c2dm.permission.RECEIVE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
|
||||
|
||||
<application
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
--header_tint: %header_text% !important;
|
||||
--header_tint_alternate: %header_text% !important;
|
||||
--vkui--color_icon_accent_themed: %header_text% !important;
|
||||
--vkui--color_text_accent_themed: %header_text% !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +140,7 @@
|
||||
--header_tint: %header_text% !important;
|
||||
--header_tint_alternate: %header_text% !important;
|
||||
--vkui--color_icon_accent_themed: %header_text% !important;
|
||||
--vkui--color_text_accent_themed: %header_text% !important;
|
||||
--color_field_border: %field_border% !important;
|
||||
--vkui--color_field_border: %field_border% !important;
|
||||
--color_field_background: %field_background% !important;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,116 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:custom="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:custom="http://schemas.android.com/apk/res-auto">
|
||||
<PreferenceCategory
|
||||
android:key="app"
|
||||
android:title="@string/sett_app">
|
||||
android:key="app"
|
||||
android:title="@string/sett_app">
|
||||
<com.vtosters.lite.ui.SummaryListPreference
|
||||
android:defaultValue="firstname"
|
||||
android:dialogTitle="@string/sett_flist_sort"
|
||||
android:entries="@array/sett_friends_order"
|
||||
android:entryValues="@array/sett_friends_order_v"
|
||||
android:key="friendsOrderNew"
|
||||
android:textColor="?text_secondary"
|
||||
android:title="@string/sett_flist_sort" />
|
||||
android:defaultValue="firstname"
|
||||
android:dialogTitle="@string/sett_flist_sort"
|
||||
android:entries="@array/sett_friends_order"
|
||||
android:entryValues="@array/sett_friends_order_v"
|
||||
android:key="friendsOrderNew"
|
||||
android:textColor="?text_secondary"
|
||||
android:title="@string/sett_flist_sort"/>
|
||||
<com.vtosters.lite.ui.SummaryListPreference
|
||||
android:defaultValue="0"
|
||||
android:dialogTitle="@string/sett_font_size"
|
||||
android:entries="@array/sett_font_size_options"
|
||||
android:entryValues="@array/sett_font_size_values"
|
||||
android:key="fontSize"
|
||||
android:title="@string/sett_font_size" />
|
||||
android:defaultValue="0"
|
||||
android:dialogTitle="@string/sett_font_size"
|
||||
android:entries="@array/sett_font_size_options"
|
||||
android:entryValues="@array/sett_font_size_values"
|
||||
android:key="fontSize"
|
||||
android:title="@string/sett_font_size"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="sendByEnter"
|
||||
android:summary="@string/sett_send_by_enter_summary"
|
||||
android:title="@string/sett_send_by_enter" />
|
||||
android:defaultValue="false"
|
||||
android:key="sendByEnter"
|
||||
android:summary="@string/sett_send_by_enter_summary"
|
||||
android:title="@string/sett_send_by_enter"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="useChromeCustomTabs"
|
||||
android:summary="@string/sett_chrome_custom_tabs_summary"
|
||||
android:title="@string/sett_chrome_custom_tabs" />
|
||||
android:defaultValue="true"
|
||||
android:key="useChromeCustomTabs"
|
||||
android:summary="@string/sett_chrome_custom_tabs_summary"
|
||||
android:title="@string/sett_chrome_custom_tabs"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="trackInstalledApps"
|
||||
android:summary="@string/sett_mytracker_location_crap_summary"
|
||||
android:title="@string/sett_mytracker_apps" />
|
||||
android:defaultValue="true"
|
||||
android:key="trackInstalledApps"
|
||||
android:summary="@string/sett_mytracker_location_crap_summary"
|
||||
android:title="@string/sett_mytracker_apps"/>
|
||||
<!-- <com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="useProxyServer"
|
||||
android:title="@string/sett_use_proxy"
|
||||
android:visibility="gone" /> -->
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="isRoamingState"
|
||||
android:summary="@string/sett_roaming_state_submit"
|
||||
android:title="@string/sett_roaming_state" />
|
||||
<com.vtosters.lite.ui.MaterialSectionDividerPreference />
|
||||
android:defaultValue="false"
|
||||
android:key="isRoamingState"
|
||||
android:summary="@string/sett_roaming_state_submit"
|
||||
android:title="@string/sett_roaming_state"/>
|
||||
<com.vtosters.lite.ui.MaterialSectionDividerPreference/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="multimedia"
|
||||
android:title="@string/sett_media">
|
||||
android:key="multimedia"
|
||||
android:title="@string/sett_media">
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="saveProcessedImage"
|
||||
android:title="@string/sett_save_processed_image"
|
||||
custom:singleLine="false" />
|
||||
android:defaultValue="true"
|
||||
android:key="saveProcessedImage"
|
||||
android:title="@string/sett_save_processed_image"
|
||||
custom:singleLine="false"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="showMusicSuggestNotification"
|
||||
android:title="@string/sett_show_music_suggestion_notification"
|
||||
custom:singleLine="false" />
|
||||
android:defaultValue="true"
|
||||
android:key="showMusicSuggestNotification"
|
||||
android:title="@string/sett_show_music_suggestion_notification"
|
||||
custom:singleLine="false"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="prefetchAudioMsg"
|
||||
android:title="@string/vkim_settings_prefetch_audiomsg"
|
||||
custom:singleLine="false" />
|
||||
android:defaultValue="true"
|
||||
android:key="prefetchAudioMsg"
|
||||
android:title="@string/vkim_settings_prefetch_audiomsg"
|
||||
custom:singleLine="false"/>
|
||||
<ListPreference
|
||||
android:defaultValue="always"
|
||||
android:entries="@array/sett_gif_autoplay_entries"
|
||||
android:entryValues="@array/sett_gif_autoplay_entry_values"
|
||||
android:key="gif_autoplay"
|
||||
android:summary="%s"
|
||||
android:title="@string/sett_gif_autoplay_title" />
|
||||
android:defaultValue="always"
|
||||
android:entries="@array/sett_gif_autoplay_entries"
|
||||
android:entryValues="@array/sett_gif_autoplay_entry_values"
|
||||
android:key="gif_autoplay"
|
||||
android:summary="%s"
|
||||
android:title="@string/sett_gif_autoplay_title"/>
|
||||
<ListPreference
|
||||
android:defaultValue="always"
|
||||
android:entries="@array/sett_gif_autoplay_entries"
|
||||
android:entryValues="@array/sett_gif_autoplay_entry_values"
|
||||
android:key="video_autoplay"
|
||||
android:summary="%s"
|
||||
android:title="@string/sett_video_autoplay_title" />
|
||||
<com.vtosters.lite.ui.MaterialSectionDividerPreference />
|
||||
android:defaultValue="always"
|
||||
android:entries="@array/sett_gif_autoplay_entries"
|
||||
android:entryValues="@array/sett_gif_autoplay_entry_values"
|
||||
android:key="video_autoplay"
|
||||
android:summary="%s"
|
||||
android:title="@string/sett_video_autoplay_title"/>
|
||||
<com.vtosters.lite.ui.MaterialSectionDividerPreference/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="cache"
|
||||
android:title="@string/sett_cache">
|
||||
android:key="cache"
|
||||
android:title="@string/sett_cache">
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="enableAudioCache"
|
||||
android:title="@string/sett_enable_audio_cache" />
|
||||
android:defaultValue="true"
|
||||
android:key="enableAudioCache"
|
||||
android:title="@string/sett_enable_audio_cache"/>
|
||||
<com.vtosters.lite.ui.SummaryListPreference
|
||||
android:defaultValue=""
|
||||
android:dialogTitle="@string/sett_cache_location"
|
||||
android:key="audioCacheLocation"
|
||||
android:title="@string/sett_cache_location" />
|
||||
android:defaultValue=""
|
||||
android:dialogTitle="@string/sett_cache_location"
|
||||
android:key="audioCacheLocation"
|
||||
android:title="@string/sett_cache_location"/>
|
||||
<Preference
|
||||
android:key="downloadAudio"
|
||||
android:title="@string/cache_my_music" />
|
||||
android:key="downloadAudio"
|
||||
android:title="@string/cache_my_music"/>
|
||||
<Preference
|
||||
android:key="clearAudioCache"
|
||||
android:summary=""
|
||||
android:title="@string/sett_clear_audio_cache" />
|
||||
android:key="clearAudioCache"
|
||||
android:summary=""
|
||||
android:title="@string/sett_clear_audio_cache"/>
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="clearImageCache"-->
|
||||
<!-- android:summary=""-->
|
||||
<!-- android:title="@string/sett_clear_img_cache"/>-->
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="clearVideoCache"-->
|
||||
<!-- android:summary=""-->
|
||||
<!-- android:title="@string/sett_clear_video_cache"/>-->
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="clearMessagesCache"-->
|
||||
<!-- android:summary=""-->
|
||||
<!-- android:title="@string/sett_clear_messages_cache"/>-->
|
||||
<Preference
|
||||
android:key="clearImageCache"
|
||||
android:summary=""
|
||||
android:title="@string/sett_clear_img_cache" />
|
||||
<Preference
|
||||
android:key="clearVideoCache"
|
||||
android:summary=""
|
||||
android:title="@string/sett_clear_video_cache" />
|
||||
<Preference
|
||||
android:key="clearMessagesCache"
|
||||
android:summary=""
|
||||
android:title="@string/sett_clear_messages_cache" />
|
||||
<com.vtosters.lite.ui.MaterialSectionDividerPreference />
|
||||
android:enabled="false"
|
||||
android:summary="@string/where_is_cache_settings_summ"
|
||||
android:title="@string/where_is_cache_settings"/>
|
||||
<com.vtosters.lite.ui.MaterialSectionDividerPreference/>
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
@@ -1,115 +1,119 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:custom="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:custom="http://schemas.android.com/apk/res-auto">
|
||||
<PreferenceCategory
|
||||
android:key="app"
|
||||
android:title="@string/sett_app">
|
||||
android:key="app"
|
||||
android:title="@string/sett_app">
|
||||
<com.vtosters.lite.ui.SummaryListPreference
|
||||
android:defaultValue="firstname"
|
||||
android:dialogTitle="@string/sett_flist_sort"
|
||||
android:entries="@array/sett_friends_order"
|
||||
android:entryValues="@array/sett_friends_order_v"
|
||||
android:key="friendsOrderNew"
|
||||
android:layout="@layout/preference_with_value"
|
||||
android:textColor="?text_secondary"
|
||||
android:title="@string/sett_flist_sort" />
|
||||
android:defaultValue="firstname"
|
||||
android:dialogTitle="@string/sett_flist_sort"
|
||||
android:entries="@array/sett_friends_order"
|
||||
android:entryValues="@array/sett_friends_order_v"
|
||||
android:key="friendsOrderNew"
|
||||
android:layout="@layout/preference_with_value"
|
||||
android:textColor="?text_secondary"
|
||||
android:title="@string/sett_flist_sort"/>
|
||||
<com.vtosters.lite.ui.SummaryListPreference
|
||||
android:defaultValue="0"
|
||||
android:dialogTitle="@string/sett_font_size"
|
||||
android:entries="@array/sett_font_size_options"
|
||||
android:entryValues="@array/sett_font_size_values"
|
||||
android:key="fontSize"
|
||||
android:layout="@layout/preference_with_value"
|
||||
android:title="@string/sett_font_size" />
|
||||
android:defaultValue="0"
|
||||
android:dialogTitle="@string/sett_font_size"
|
||||
android:entries="@array/sett_font_size_options"
|
||||
android:entryValues="@array/sett_font_size_values"
|
||||
android:key="fontSize"
|
||||
android:layout="@layout/preference_with_value"
|
||||
android:title="@string/sett_font_size"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="sendByEnter"
|
||||
android:summary="@string/sett_send_by_enter_summary"
|
||||
android:title="@string/sett_send_by_enter" />
|
||||
android:defaultValue="false"
|
||||
android:key="sendByEnter"
|
||||
android:summary="@string/sett_send_by_enter_summary"
|
||||
android:title="@string/sett_send_by_enter"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="useChromeCustomTabs"
|
||||
android:summary="@string/sett_chrome_custom_tabs_summary"
|
||||
android:title="@string/sett_chrome_custom_tabs" />
|
||||
android:defaultValue="true"
|
||||
android:key="useChromeCustomTabs"
|
||||
android:summary="@string/sett_chrome_custom_tabs_summary"
|
||||
android:title="@string/sett_chrome_custom_tabs"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="trackInstalledApps"
|
||||
android:summary="@string/sett_mytracker_location_crap_summary"
|
||||
android:title="@string/sett_mytracker_apps" />
|
||||
android:defaultValue="true"
|
||||
android:key="trackInstalledApps"
|
||||
android:summary="@string/sett_mytracker_location_crap_summary"
|
||||
android:title="@string/sett_mytracker_apps"/>
|
||||
<!-- <com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="useProxyServer"
|
||||
android:title="@string/sett_use_proxy"
|
||||
android:visibility="gone" /> -->
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="isRoamingState"
|
||||
android:summary="@string/sett_roaming_state_submit"
|
||||
android:title="@string/sett_roaming_state" />
|
||||
android:defaultValue="false"
|
||||
android:key="isRoamingState"
|
||||
android:summary="@string/sett_roaming_state_submit"
|
||||
android:title="@string/sett_roaming_state"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="multimedia"
|
||||
android:title="@string/sett_media">
|
||||
android:key="multimedia"
|
||||
android:title="@string/sett_media">
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="saveProcessedImage"
|
||||
android:title="@string/sett_save_processed_image"
|
||||
custom:singleLine="false" />
|
||||
android:defaultValue="true"
|
||||
android:key="saveProcessedImage"
|
||||
android:title="@string/sett_save_processed_image"
|
||||
custom:singleLine="false"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="showMusicSuggestNotification"
|
||||
android:title="@string/sett_show_music_suggestion_notification"
|
||||
custom:singleLine="false" />
|
||||
android:defaultValue="true"
|
||||
android:key="showMusicSuggestNotification"
|
||||
android:title="@string/sett_show_music_suggestion_notification"
|
||||
custom:singleLine="false"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="prefetchAudioMsg"
|
||||
android:title="@string/vkim_settings_prefetch_audiomsg"
|
||||
custom:singleLine="false" />
|
||||
android:defaultValue="true"
|
||||
android:key="prefetchAudioMsg"
|
||||
android:title="@string/vkim_settings_prefetch_audiomsg"
|
||||
custom:singleLine="false"/>
|
||||
<ListPreference
|
||||
android:defaultValue="always"
|
||||
android:entries="@array/sett_gif_autoplay_entries"
|
||||
android:entryValues="@array/sett_gif_autoplay_entry_values"
|
||||
android:key="gif_autoplay"
|
||||
android:summary="%s"
|
||||
android:title="@string/sett_gif_autoplay_title" />
|
||||
android:defaultValue="always"
|
||||
android:entries="@array/sett_gif_autoplay_entries"
|
||||
android:entryValues="@array/sett_gif_autoplay_entry_values"
|
||||
android:key="gif_autoplay"
|
||||
android:summary="%s"
|
||||
android:title="@string/sett_gif_autoplay_title"/>
|
||||
<ListPreference
|
||||
android:defaultValue="always"
|
||||
android:entries="@array/sett_gif_autoplay_entries"
|
||||
android:entryValues="@array/sett_gif_autoplay_entry_values"
|
||||
android:key="video_autoplay"
|
||||
android:summary="%s"
|
||||
android:title="@string/sett_video_autoplay_title" />
|
||||
android:defaultValue="always"
|
||||
android:entries="@array/sett_gif_autoplay_entries"
|
||||
android:entryValues="@array/sett_gif_autoplay_entry_values"
|
||||
android:key="video_autoplay"
|
||||
android:summary="%s"
|
||||
android:title="@string/sett_video_autoplay_title"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="cache"
|
||||
android:title="@string/sett_cache">
|
||||
android:key="cache"
|
||||
android:title="@string/sett_cache">
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="enableAudioCache"
|
||||
android:title="@string/sett_enable_audio_cache" />
|
||||
android:defaultValue="true"
|
||||
android:key="enableAudioCache"
|
||||
android:title="@string/sett_enable_audio_cache"/>
|
||||
<com.vtosters.lite.ui.SummaryListPreference
|
||||
android:defaultValue=""
|
||||
android:dialogTitle="@string/sett_cache_location"
|
||||
android:key="audioCacheLocation"
|
||||
android:title="@string/sett_cache_location" />
|
||||
android:defaultValue=""
|
||||
android:dialogTitle="@string/sett_cache_location"
|
||||
android:key="audioCacheLocation"
|
||||
android:title="@string/sett_cache_location"/>
|
||||
<Preference
|
||||
android:key="downloadAudio"
|
||||
android:title="@string/cache_my_music" />
|
||||
android:key="downloadAudio"
|
||||
android:title="@string/cache_my_music"/>
|
||||
<Preference
|
||||
android:key="clearAudioCache"
|
||||
android:summary=""
|
||||
android:title="@string/sett_clear_audio_cache" />
|
||||
android:key="clearAudioCache"
|
||||
android:summary=""
|
||||
android:title="@string/sett_clear_audio_cache"/>
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="clearImageCache"-->
|
||||
<!-- android:summary=""-->
|
||||
<!-- android:title="@string/sett_clear_img_cache"/>-->
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="clearVideoCache"-->
|
||||
<!-- android:summary=""-->
|
||||
<!-- android:title="@string/sett_clear_video_cache"/>-->
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="clearMessagesCache"-->
|
||||
<!-- android:summary=""-->
|
||||
<!-- android:title="@string/sett_clear_messages_cache"/>-->
|
||||
<Preference
|
||||
android:key="clearImageCache"
|
||||
android:summary=""
|
||||
android:title="@string/sett_clear_img_cache" />
|
||||
<Preference
|
||||
android:key="clearVideoCache"
|
||||
android:summary=""
|
||||
android:title="@string/sett_clear_video_cache" />
|
||||
<Preference
|
||||
android:key="clearMessagesCache"
|
||||
android:summary=""
|
||||
android:title="@string/sett_clear_messages_cache" />
|
||||
android:enabled="false"
|
||||
android:summary="@string/where_is_cache_settings_summ"
|
||||
android:title="@string/where_is_cache_settings"/>
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
@@ -1,18 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory android:title="@string/sett_media">
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="compressPhotos"
|
||||
android:summary="@string/sett_compress_photo_summ"
|
||||
android:title="@string/sett_compress_photos" />
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="compressVideos"
|
||||
android:summary="@string/sett_compress_video_summ"
|
||||
android:title="@string/sett_compress_videos"/>
|
||||
<com.vtosters.lite.ui.SummaryListPreference
|
||||
android:defaultValue="jpeg"
|
||||
android:dialogTitle="@string/compression_types_selection"
|
||||
android:entries="@array/compression_value"
|
||||
android:entryValues="@array/compression"
|
||||
android:key="compression"
|
||||
android:title="@string/compression_types"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/vtl_video_settings">
|
||||
<Preference
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
<Preference
|
||||
android:key="microgsettings"
|
||||
android:title="@string/microgsett"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="batchmessages"
|
||||
android:summary="Поможет исправить скорость загрузки сообщений, но работает нестабильно"
|
||||
android:title="Альтернативная загрузка сообщений"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/vtverify">
|
||||
<Preference
|
||||
|
||||
@@ -47,9 +47,6 @@
|
||||
<PreferenceCategory
|
||||
android:key="dockbarsett"
|
||||
android:title="@string/vtl_settings_dockbar_title">
|
||||
<Preference
|
||||
android:key="dockbareditor"
|
||||
android:title="@string/dockbar_editor"/>
|
||||
<com.vtosters.lite.ui.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="dockbar_tab_titles"
|
||||
|
||||
@@ -139,10 +139,6 @@
|
||||
|
||||
const/16 v2, 0x64
|
||||
|
||||
invoke-static {v2}, Lru/vtosters/hooks/other/Preferences;->compress(I)I
|
||||
|
||||
move-result v2
|
||||
|
||||
invoke-virtual {p2, v0, v2, v1}, Landroid/graphics/Bitmap;->compress(Landroid/graphics/Bitmap$CompressFormat;ILjava/io/OutputStream;)Z
|
||||
|
||||
const/4 p2, 0x0
|
||||
|
||||
@@ -137,10 +137,6 @@
|
||||
|
||||
const/16 v3, 0xa
|
||||
|
||||
invoke-static {v3}, Lru/vtosters/hooks/other/Preferences;->compress(I)I
|
||||
|
||||
move-result v3
|
||||
|
||||
invoke-virtual {v1, v2, v3, v0}, Landroid/graphics/Bitmap;->compress(Landroid/graphics/Bitmap$CompressFormat;ILjava/io/OutputStream;)Z
|
||||
|
||||
.line 10
|
||||
|
||||
@@ -344,10 +344,6 @@
|
||||
|
||||
move-object/from16 v9, p2
|
||||
|
||||
invoke-static {v3}, Lru/vtosters/hooks/other/Preferences;->compress(I)I
|
||||
|
||||
move-result v3
|
||||
|
||||
invoke-virtual {v6, v0, v3, v9}, Landroid/graphics/Bitmap;->compress(Landroid/graphics/Bitmap$CompressFormat;ILjava/io/OutputStream;)Z
|
||||
|
||||
.line 14
|
||||
|
||||
@@ -82,10 +82,6 @@
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-static {p1}, Lru/vtosters/hooks/OnlineFormatterHook;->onlineHookProfiles(Lorg/json/JSONObject;)Lorg/json/JSONObject;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
const-string v0, "items"
|
||||
|
||||
.line 3
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user