You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
11 KiB
Java
245 lines
11 KiB
Java
package com.fox2code.mmm;
|
|
|
|
import android.app.Activity;
|
|
import android.os.Build;
|
|
import android.util.Log;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
|
|
import com.fox2code.mmm.installer.InstallerInitializer;
|
|
import com.fox2code.mmm.manager.ModuleInfo;
|
|
import com.fox2code.mmm.manager.ModuleManager;
|
|
import com.fox2code.mmm.repo.RepoManager;
|
|
import com.fox2code.mmm.repo.RepoModule;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.EnumSet;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.Locale;
|
|
|
|
public class ModuleViewListBuilder {
|
|
private static final String TAG = "ModuleViewListBuilder";
|
|
private final EnumSet<NotificationType> notifications = EnumSet.noneOf(NotificationType.class);
|
|
private final HashMap<String, ModuleHolder> mappedModuleHolders = new HashMap<>();
|
|
private final Object updateLock = new Object();
|
|
private final Object queryLock = new Object();
|
|
private final Activity activity;
|
|
@NonNull
|
|
private String query = "";
|
|
private int footerPx;
|
|
private boolean noUpdate;
|
|
|
|
public ModuleViewListBuilder(Activity activity) {
|
|
this.activity = activity;
|
|
}
|
|
|
|
public void addNotification(NotificationType notificationType) {
|
|
synchronized (this.updateLock) {
|
|
this.notifications.add(notificationType);
|
|
}
|
|
}
|
|
|
|
public void appendInstalledModules() {
|
|
synchronized (this.updateLock) {
|
|
for (ModuleHolder moduleHolder : this.mappedModuleHolders.values()) {
|
|
moduleHolder.moduleInfo = null;
|
|
}
|
|
ModuleManager moduleManager = ModuleManager.getINSTANCE();
|
|
moduleManager.runAfterScan(() -> {
|
|
Log.i(TAG, "A1: " + moduleManager.getModules().size());
|
|
for (ModuleInfo moduleInfo : moduleManager.getModules().values()) {
|
|
ModuleHolder moduleHolder = this.mappedModuleHolders.get(moduleInfo.id);
|
|
if (moduleHolder == null) {
|
|
this.mappedModuleHolders.put(moduleInfo.id,
|
|
moduleHolder = new ModuleHolder(moduleInfo.id));
|
|
}
|
|
moduleHolder.moduleInfo = moduleInfo;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public void appendRemoteModules() {
|
|
synchronized (this.updateLock) {
|
|
boolean showIncompatible = MainApplication.isShowIncompatibleModules();
|
|
for (ModuleHolder moduleHolder : this.mappedModuleHolders.values()) {
|
|
moduleHolder.repoModule = null;
|
|
}
|
|
RepoManager repoManager = RepoManager.getINSTANCE();
|
|
repoManager.runAfterUpdate(() -> {
|
|
Log.i(TAG, "A2: " + repoManager.getModules().size());
|
|
for (RepoModule repoModule : repoManager.getModules().values()) {
|
|
if (!showIncompatible && (repoModule.moduleInfo.minApi > Build.VERSION.SDK_INT ||
|
|
// Only check Magisk compatibility if root is present
|
|
(InstallerInitializer.peekMagiskPath() != null &&
|
|
repoModule.moduleInfo.minMagisk >
|
|
InstallerInitializer.peekMagiskVersion()
|
|
)))
|
|
continue; // Skip adding incompatible modules
|
|
ModuleHolder moduleHolder = this.mappedModuleHolders.get(repoModule.id);
|
|
if (moduleHolder == null) {
|
|
this.mappedModuleHolders.put(repoModule.id,
|
|
moduleHolder = new ModuleHolder(repoModule.id));
|
|
}
|
|
moduleHolder.repoModule = repoModule;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public void applyTo(RecyclerView moduleList, ModuleViewAdapter moduleViewAdapter) {
|
|
if (this.noUpdate) return;
|
|
this.noUpdate = true;
|
|
final ArrayList<ModuleHolder> moduleHolders;
|
|
final int newNotificationsLen;
|
|
try {
|
|
synchronized (this.updateLock) {
|
|
// Build start
|
|
moduleHolders = new ArrayList<>();
|
|
int special = 0;
|
|
Iterator<NotificationType> notificationTypeIterator = this.notifications.iterator();
|
|
while (notificationTypeIterator.hasNext()) {
|
|
NotificationType notificationType = notificationTypeIterator.next();
|
|
if (notificationType.shouldRemove()) {
|
|
notificationTypeIterator.remove();
|
|
} else {
|
|
if (notificationType.special) special++;
|
|
moduleHolders.add(new ModuleHolder(notificationType));
|
|
}
|
|
}
|
|
newNotificationsLen = this.notifications.size() - special;
|
|
EnumSet<ModuleHolder.Type> headerTypes = EnumSet.of(
|
|
ModuleHolder.Type.NOTIFICATION, ModuleHolder.Type.SEPARATOR);
|
|
Iterator<ModuleHolder> moduleHolderIterator = this.mappedModuleHolders.values().iterator();
|
|
synchronized (this.queryLock) {
|
|
while (moduleHolderIterator.hasNext()) {
|
|
ModuleHolder moduleHolder = moduleHolderIterator.next();
|
|
if (moduleHolder.shouldRemove()) {
|
|
moduleHolderIterator.remove();
|
|
} else {
|
|
ModuleHolder.Type type = moduleHolder.getType();
|
|
if (matchFilter(moduleHolder)) {
|
|
if (headerTypes.add(type)) {
|
|
moduleHolders.add(new ModuleHolder(type));
|
|
}
|
|
moduleHolders.add(moduleHolder);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Collections.sort(moduleHolders, ModuleHolder::compareTo);
|
|
Log.i(TAG, "Got " + moduleHolders.size() + " entries!");
|
|
// Build end
|
|
}
|
|
} finally {
|
|
this.noUpdate = false;
|
|
}
|
|
this.activity.runOnUiThread(() -> {
|
|
final EnumSet<NotificationType> oldNotifications =
|
|
EnumSet.noneOf(NotificationType.class);
|
|
if (this.footerPx != 0) {
|
|
moduleHolders.add(new ModuleHolder(this.footerPx));
|
|
}
|
|
boolean isTop = !moduleList.canScrollVertically(-1);
|
|
boolean isBottom = !isTop && !moduleList.canScrollVertically(1);
|
|
int oldNotificationsLen = 0;
|
|
int oldOfflineModulesLen = 0;
|
|
for (ModuleHolder moduleHolder : moduleViewAdapter.moduleHolders) {
|
|
NotificationType notificationType = moduleHolder.notificationType;
|
|
if (notificationType != null) {
|
|
oldNotifications.add(notificationType);
|
|
if (!notificationType.special)
|
|
oldNotificationsLen++;
|
|
}
|
|
if (moduleHolder.separator == ModuleHolder.Type.INSTALLABLE)
|
|
break;
|
|
oldOfflineModulesLen++;
|
|
}
|
|
oldOfflineModulesLen -= oldNotificationsLen;
|
|
int newOfflineModulesLen = 0;
|
|
for (ModuleHolder moduleHolder : moduleHolders) {
|
|
if (moduleHolder.separator == ModuleHolder.Type.INSTALLABLE)
|
|
break;
|
|
newOfflineModulesLen++;
|
|
}
|
|
newOfflineModulesLen -= newNotificationsLen;
|
|
moduleViewAdapter.moduleHolders.size();
|
|
int newLen = moduleHolders.size();
|
|
int oldLen = moduleViewAdapter.moduleHolders.size();
|
|
moduleViewAdapter.moduleHolders.clear();
|
|
moduleViewAdapter.moduleHolders.addAll(moduleHolders);
|
|
if (oldNotificationsLen != newNotificationsLen ||
|
|
!oldNotifications.equals(this.notifications)) {
|
|
notifySizeChanged(moduleViewAdapter, 0,
|
|
oldNotificationsLen, newNotificationsLen);
|
|
}
|
|
if (newLen - newNotificationsLen == 0) {
|
|
notifySizeChanged(moduleViewAdapter, newNotificationsLen,
|
|
oldLen - oldNotificationsLen, 0);
|
|
} else {
|
|
notifySizeChanged(moduleViewAdapter, newNotificationsLen,
|
|
oldOfflineModulesLen, newOfflineModulesLen);
|
|
notifySizeChanged(moduleViewAdapter,
|
|
newNotificationsLen + newOfflineModulesLen,
|
|
oldLen - oldNotificationsLen - oldOfflineModulesLen,
|
|
newLen - newNotificationsLen - newOfflineModulesLen);
|
|
}
|
|
if (isTop) moduleList.scrollToPosition(0);
|
|
if (isBottom) moduleList.scrollToPosition(newLen);
|
|
});
|
|
}
|
|
|
|
public void setFooterPx(int footerPx) {
|
|
this.footerPx = Math.max(footerPx, 0);
|
|
}
|
|
|
|
private boolean matchFilter(ModuleHolder moduleHolder) {
|
|
if (this.query.isEmpty()) return true;
|
|
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
|
|
return moduleInfo.id.toLowerCase(Locale.ROOT).contains(this.query) ||
|
|
moduleInfo.name.toLowerCase(Locale.ROOT).contains(this.query);
|
|
}
|
|
|
|
private static void notifySizeChanged(ModuleViewAdapter moduleViewAdapter,
|
|
int index, int oldLen, int newLen) {
|
|
// Log.i(TAG, "A: " + index + " " + oldLen + " " + newLen);
|
|
if (oldLen == newLen) {
|
|
if (newLen != 0)
|
|
moduleViewAdapter.notifyItemRangeChanged(index, newLen);
|
|
} else if (oldLen < newLen) {
|
|
if (oldLen != 0)
|
|
moduleViewAdapter.notifyItemRangeChanged(index, oldLen);
|
|
moduleViewAdapter.notifyItemRangeInserted(
|
|
index + oldLen, newLen - oldLen);
|
|
} else {
|
|
if (newLen != 0)
|
|
moduleViewAdapter.notifyItemRangeChanged(index, newLen);
|
|
moduleViewAdapter.notifyItemRangeRemoved(
|
|
index + newLen, oldLen - newLen);
|
|
}
|
|
}
|
|
|
|
public void setQuery(String query) {
|
|
synchronized (this.queryLock) {
|
|
Log.i(TAG, "Query " + this.query + " -> " + query);
|
|
this.query = query == null ? "" :
|
|
query.trim().toLowerCase(Locale.ROOT);
|
|
}
|
|
}
|
|
|
|
public boolean setQueryChange(String query) {
|
|
synchronized (this.queryLock) {
|
|
String newQuery = query == null ? "" :
|
|
query.trim().toLowerCase(Locale.ROOT);
|
|
Log.i(TAG, "Query change " + this.query + " -> " + newQuery);
|
|
if (this.query.equals(newQuery))
|
|
return false;
|
|
this.query = newQuery;
|
|
}
|
|
return true;
|
|
}
|
|
}
|