diff --git a/app/build.gradle b/app/build.gradle
index 4527751c..8b195a07 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -67,9 +67,16 @@ dependencies {
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
- implementation 'com.google.android.material:material:1.2.1'
+ implementation 'com.github.bumptech.glide:okhttp3-integration:4.11.0'
+ implementation 'com.google.android.material:material:1.3.0'
+ implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.takisoft.preferencex:preferencex:1.1.0'
implementation 'com.takisoft.preferencex:preferencex-colorpicker:1.1.0'
+ implementation 'com.squareup.okhttp3:okhttp:4.9.0'
+ final def markwon_version = '4.6.2'
+ implementation "io.noties.markwon:core:$markwon_version"
+ implementation "io.noties.markwon:image:$markwon_version"
+ implementation "io.noties.markwon:html:$markwon_version"
implementation 'rikka.insets:insets:1.0.1'
implementation 'rikka.recyclerview:recyclerview-utils:1.2.0'
implementation "rikka.widget:switchbar:1.0.2"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d2261b8f..3b9cbd72 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="io.github.lsposed.manager">
+
+
+
.
+ *
+ * Copyright (C) 2020 EdXposed Contributors
+ * Copyright (C) 2021 LSPosed Contributors
+ */
+
+package io.github.lsposed.manager.repo;
+
+import androidx.annotation.NonNull;
+
+import com.google.gson.Gson;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import io.github.lsposed.manager.App;
+import io.github.lsposed.manager.repo.model.OnlineModule;
+import io.github.lsposed.manager.util.ModuleUtil;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+public class RepoLoader {
+ private static RepoLoader instance = null;
+ private OnlineModule[] onlineModules = new OnlineModule[0];
+ private final Path repoFile = Paths.get(App.getInstance().getFilesDir().getAbsolutePath(), "repo.json");
+ private final List listeners = new CopyOnWriteArrayList<>();
+ private boolean isLoading = false;
+
+ public static synchronized RepoLoader getInstance() {
+ if (instance == null) {
+ instance = new RepoLoader();
+ instance.loadRemoteData();
+ }
+ return instance;
+ }
+
+ public void loadRemoteData() {
+ synchronized (this) {
+ if (isLoading) {
+ return;
+ }
+ isLoading = true;
+ }
+ App.getOkHttpClient().newCall(new Request.Builder()
+ .url("https://modules.lsposed.org/modules.json")
+ .build()).enqueue(new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
+ synchronized (this) {
+ isLoading = false;
+ }
+ }
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
+ ResponseBody body = response.body();
+ if (body != null) {
+ String bodyString = body.string();
+ Gson gson = new Gson();
+ onlineModules = gson.fromJson(bodyString, OnlineModule[].class);
+ Files.write(repoFile, bodyString.getBytes(StandardCharsets.UTF_8));
+ }
+ for (Listener listener : listeners) {
+ listener.repoLoaded();
+ }
+ synchronized (this) {
+ isLoading = false;
+ }
+ }
+ });
+ }
+
+ public void addListener(Listener listener) {
+ if (!listeners.contains(listener))
+ listeners.add(listener);
+ }
+
+ public void removeListener(Listener listener) {
+ listeners.remove(listener);
+ }
+
+ public OnlineModule[] getOnlineModules() {
+ return onlineModules;
+ }
+
+ public interface Listener {
+ void repoLoaded();
+ }
+}
diff --git a/app/src/main/java/io/github/lsposed/manager/repo/model/Collaborator.java b/app/src/main/java/io/github/lsposed/manager/repo/model/Collaborator.java
new file mode 100644
index 00000000..734f922e
--- /dev/null
+++ b/app/src/main/java/io/github/lsposed/manager/repo/model/Collaborator.java
@@ -0,0 +1,85 @@
+/*
+ * This file is part of LSPosed.
+ *
+ * LSPosed is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LSPosed is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LSPosed. If not, see .
+ *
+ * Copyright (C) 2020 EdXposed Contributors
+ * Copyright (C) 2021 LSPosed Contributors
+ */
+
+package io.github.lsposed.manager.repo.model;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+public class Collaborator implements Serializable, Parcelable {
+
+ @SerializedName("login")
+ @Expose
+ private String login;
+ @SerializedName("name")
+ @Expose
+ private Object name;
+ public final static Creator CREATOR = new Creator() {
+
+ public Collaborator createFromParcel(Parcel in) {
+ return new Collaborator(in);
+ }
+
+ public Collaborator[] newArray(int size) {
+ return (new Collaborator[size]);
+ }
+
+ };
+ private final static long serialVersionUID = -7125602393430154154L;
+
+ protected Collaborator(Parcel in) {
+ this.login = ((String) in.readValue((String.class.getClassLoader())));
+ this.name = in.readValue((Object.class.getClassLoader()));
+ }
+
+ public Collaborator() {
+ }
+
+ public String getLogin() {
+ return login;
+ }
+
+ public void setLogin(String login) {
+ this.login = login;
+ }
+
+ public Object getName() {
+ return name;
+ }
+
+ public void setName(Object name) {
+ this.name = name;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeValue(login);
+ dest.writeValue(name);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+}
diff --git a/app/src/main/java/io/github/lsposed/manager/repo/model/OnlineModule.java b/app/src/main/java/io/github/lsposed/manager/repo/model/OnlineModule.java
new file mode 100644
index 00000000..3f4553c8
--- /dev/null
+++ b/app/src/main/java/io/github/lsposed/manager/repo/model/OnlineModule.java
@@ -0,0 +1,217 @@
+/*
+ * This file is part of LSPosed.
+ *
+ * LSPosed is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LSPosed is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LSPosed. If not, see .
+ *
+ * Copyright (C) 2020 EdXposed Contributors
+ * Copyright (C) 2021 LSPosed Contributors
+ */
+
+package io.github.lsposed.manager.repo.model;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class OnlineModule implements Serializable, Parcelable {
+
+ @SerializedName("name")
+ @Expose
+ private String name;
+ @SerializedName("description")
+ @Expose
+ private String description;
+ @SerializedName("url")
+ @Expose
+ private String url;
+ @SerializedName("homepageUrl")
+ @Expose
+ private String homepageUrl;
+ @SerializedName("collaborators")
+ @Expose
+ private List collaborators = new ArrayList<>();
+ @SerializedName("releases")
+ @Expose
+ private List releases = new ArrayList<>();
+ @SerializedName("readme")
+ @Expose
+ private String readme;
+ @SerializedName("summary")
+ @Expose
+ private String summary;
+ @SerializedName("scope")
+ @Expose
+ private List scope = new ArrayList<>();
+ @SerializedName("sourceUrl")
+ @Expose
+ private String sourceUrl;
+ @SerializedName("hide")
+ @Expose
+ private Boolean hide;
+ @SerializedName("additionalAuthors")
+ @Expose
+ private List