[app] Add load more to releases
This commit is contained in:
parent
6535ea0f8e
commit
affa6fe643
|
|
@ -107,6 +107,32 @@ public class RepoLoader {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void loadRemoteReleases(String packageName) {
|
||||||
|
App.getOkHttpClient().newCall(new Request.Builder()
|
||||||
|
.url(String.format("https://modules.lsposed.org/module/%s.json", packageName))
|
||||||
|
.build()).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
App.getInstance().runOnUiThread(() -> Toast.makeText(App.getInstance(), e.getMessage(), Toast.LENGTH_LONG).show());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
OnlineModule module = gson.fromJson(bodyString, OnlineModule.class);
|
||||||
|
module.releasesLoaded = true;
|
||||||
|
onlineModules.replace(packageName, module);
|
||||||
|
for (Listener listener : listeners) {
|
||||||
|
listener.moduleReleasesLoaded(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void addListener(Listener listener) {
|
public void addListener(Listener listener) {
|
||||||
if (!listeners.contains(listener))
|
if (!listeners.contains(listener))
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
|
|
@ -126,5 +152,7 @@ public class RepoLoader {
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
void repoLoaded();
|
void repoLoaded();
|
||||||
|
|
||||||
|
void moduleReleasesLoaded(OnlineModule module);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ public class OnlineModule implements Serializable, Parcelable {
|
||||||
@SerializedName("stargazerCount")
|
@SerializedName("stargazerCount")
|
||||||
@Expose
|
@Expose
|
||||||
private Integer stargazerCount;
|
private Integer stargazerCount;
|
||||||
|
public boolean releasesLoaded = false;
|
||||||
public final static Creator<OnlineModule> CREATOR = new Creator<OnlineModule>() {
|
public final static Creator<OnlineModule> CREATOR = new Creator<OnlineModule>() {
|
||||||
|
|
||||||
public OnlineModule createFromParcel(Parcel in) {
|
public OnlineModule createFromParcel(Parcel in) {
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,11 @@ public class RepoActivity extends ListActivity implements RepoLoader.Listener {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moduleReleasesLoaded(OnlineModule module) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
int itemId = item.getItemId();
|
int itemId = item.getItemId();
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.google.android.material.tabs.TabLayoutMediator;
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -47,6 +49,7 @@ import java.util.ListIterator;
|
||||||
|
|
||||||
import io.github.lsposed.manager.R;
|
import io.github.lsposed.manager.R;
|
||||||
import io.github.lsposed.manager.databinding.ActivityModuleDetailBinding;
|
import io.github.lsposed.manager.databinding.ActivityModuleDetailBinding;
|
||||||
|
import io.github.lsposed.manager.databinding.ItemRepoLoadmoreBinding;
|
||||||
import io.github.lsposed.manager.databinding.ItemRepoReadmeBinding;
|
import io.github.lsposed.manager.databinding.ItemRepoReadmeBinding;
|
||||||
import io.github.lsposed.manager.databinding.ItemRepoRecyclerviewBinding;
|
import io.github.lsposed.manager.databinding.ItemRepoRecyclerviewBinding;
|
||||||
import io.github.lsposed.manager.databinding.ItemRepoReleaseBinding;
|
import io.github.lsposed.manager.databinding.ItemRepoReleaseBinding;
|
||||||
|
|
@ -77,13 +80,15 @@ import rikka.widget.borderview.BorderNestedScrollView;
|
||||||
import rikka.widget.borderview.BorderRecyclerView;
|
import rikka.widget.borderview.BorderRecyclerView;
|
||||||
import rikka.widget.borderview.BorderView;
|
import rikka.widget.borderview.BorderView;
|
||||||
|
|
||||||
public class RepoItemActivity extends BaseActivity {
|
public class RepoItemActivity extends BaseActivity implements RepoLoader.Listener {
|
||||||
ActivityModuleDetailBinding binding;
|
ActivityModuleDetailBinding binding;
|
||||||
private Markwon markwon;
|
private Markwon markwon;
|
||||||
private OnlineModule module;
|
private OnlineModule module;
|
||||||
|
private ReleaseAdapter releaseAdapter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
RepoLoader.getInstance().addListener(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
binding = ActivityModuleDetailBinding.inflate(getLayoutInflater());
|
binding = ActivityModuleDetailBinding.inflate(getLayoutInflater());
|
||||||
String modulePackageName = getIntent().getStringExtra("modulePackageName");
|
String modulePackageName = getIntent().getStringExtra("modulePackageName");
|
||||||
|
|
@ -136,12 +141,36 @@ public class RepoItemActivity extends BaseActivity {
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
if (id == R.id.menu_open_in_browser) {
|
if (id == R.id.menu_open_in_browser) {
|
||||||
NavUtil.startURL(this, module.getUrl());
|
NavUtil.startURL(this, "https://modules.lsposed.org/module/" + module.getName());
|
||||||
// TODO: replace with web version
|
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void repoLoaded() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moduleReleasesLoaded(OnlineModule module) {
|
||||||
|
this.module = module;
|
||||||
|
if (releaseAdapter != null) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (module.getReleases().size() == 1) {
|
||||||
|
Snackbar.make(binding.snackbar, R.string.module_release_no_more, Snackbar.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
releaseAdapter.loadItems();
|
||||||
|
releaseAdapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
RepoLoader.getInstance().removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
private class InformationAdapter extends RecyclerView.Adapter<InformationAdapter.ViewHolder> {
|
private class InformationAdapter extends RecyclerView.Adapter<InformationAdapter.ViewHolder> {
|
||||||
private final OnlineModule module;
|
private final OnlineModule module;
|
||||||
|
|
||||||
|
|
@ -229,52 +258,76 @@ public class RepoItemActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReleaseAdapter extends RecyclerView.Adapter<ReleaseAdapter.ViewHolder> {
|
private class ReleaseAdapter extends RecyclerView.Adapter<ReleaseAdapter.ViewHolder> {
|
||||||
private final List<Release> items;
|
private List<Release> items;
|
||||||
|
|
||||||
public ReleaseAdapter(List<Release> items) {
|
public ReleaseAdapter() {
|
||||||
this.items = items;
|
loadItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadItems() {
|
||||||
|
this.items = module.getReleases();
|
||||||
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
return new ViewHolder(ItemRepoReleaseBinding.inflate(getLayoutInflater(), parent, false));
|
if (viewType == 0) {
|
||||||
|
return new ViewHolder(ItemRepoReleaseBinding.inflate(getLayoutInflater(), parent, false).getRoot());
|
||||||
|
} else {
|
||||||
|
return new ViewHolder(ItemRepoLoadmoreBinding.inflate(getLayoutInflater(), parent, false).getRoot());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
Release release = items.get(position);
|
if (position == items.size()) {
|
||||||
holder.title.setText(release.getName());
|
holder.itemView.setOnClickListener(v -> {
|
||||||
holder.description.setTransformationMethod(new LinkTransformationMethod(RepoItemActivity.this));
|
if (holder.progress.getVisibility() == View.GONE) {
|
||||||
holder.description.setSpannableFactory(NoCopySpannableFactory.getInstance());
|
holder.title.setVisibility(View.GONE);
|
||||||
markwon.setMarkdown(holder.description, release.getDescription());
|
holder.progress.show();
|
||||||
holder.description.setMovementMethod(null);
|
RepoLoader.getInstance().loadRemoteReleases(module.getName());
|
||||||
holder.openInBrowser.setOnClickListener(v -> NavUtil.startURL(RepoItemActivity.this, release.getUrl()));
|
}
|
||||||
List<ReleaseAsset> assets = release.getReleaseAssets();
|
|
||||||
if (assets != null && !assets.isEmpty()) {
|
|
||||||
holder.viewAssets.setOnClickListener(v -> {
|
|
||||||
ArrayList<String> names = new ArrayList<>();
|
|
||||||
assets.forEach(releaseAsset -> names.add(releaseAsset.getName()));
|
|
||||||
new AlertDialog.Builder(RepoItemActivity.this)
|
|
||||||
.setItems(names.toArray(new String[0]), (dialog, which) -> NavUtil.startURL(RepoItemActivity.this, assets.get(which).getDownloadUrl()))
|
|
||||||
.show();
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
holder.viewAssets.setVisibility(View.GONE);
|
Release release = items.get(position);
|
||||||
}
|
holder.title.setText(release.getName());
|
||||||
holder.itemView.setOnClickListener(v -> {
|
holder.description.setTransformationMethod(new LinkTransformationMethod(RepoItemActivity.this));
|
||||||
ClickableSpan span = holder.description.getCurrentSpan();
|
holder.description.setSpannableFactory(NoCopySpannableFactory.getInstance());
|
||||||
holder.description.clearCurrentSpan();
|
markwon.setMarkdown(holder.description, release.getDescription());
|
||||||
|
holder.description.setMovementMethod(null);
|
||||||
if (span instanceof CustomTabsURLSpan) {
|
holder.openInBrowser.setOnClickListener(v -> NavUtil.startURL(RepoItemActivity.this, release.getUrl()));
|
||||||
span.onClick(v);
|
List<ReleaseAsset> assets = release.getReleaseAssets();
|
||||||
|
if (assets != null && !assets.isEmpty()) {
|
||||||
|
holder.viewAssets.setOnClickListener(v -> {
|
||||||
|
ArrayList<String> names = new ArrayList<>();
|
||||||
|
assets.forEach(releaseAsset -> names.add(releaseAsset.getName()));
|
||||||
|
new AlertDialog.Builder(RepoItemActivity.this)
|
||||||
|
.setItems(names.toArray(new String[0]), (dialog, which) -> NavUtil.startURL(RepoItemActivity.this, assets.get(which).getDownloadUrl()))
|
||||||
|
.show();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
holder.viewAssets.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
});
|
holder.itemView.setOnClickListener(v -> {
|
||||||
|
ClickableSpan span = holder.description.getCurrentSpan();
|
||||||
|
holder.description.clearCurrentSpan();
|
||||||
|
|
||||||
|
if (span instanceof CustomTabsURLSpan) {
|
||||||
|
span.onClick(v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return items.size();
|
return items.size() + (module.releasesLoaded ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
return !module.releasesLoaded && position == getItemCount() - 1 ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder extends RecyclerView.ViewHolder {
|
class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
@ -282,13 +335,15 @@ public class RepoItemActivity extends BaseActivity {
|
||||||
LinkifyTextView description;
|
LinkifyTextView description;
|
||||||
View openInBrowser;
|
View openInBrowser;
|
||||||
View viewAssets;
|
View viewAssets;
|
||||||
|
CircularProgressIndicator progress;
|
||||||
|
|
||||||
public ViewHolder(ItemRepoReleaseBinding binding) {
|
public ViewHolder(View view) {
|
||||||
super(binding.getRoot());
|
super(view);
|
||||||
title = binding.title;
|
title = view.findViewById(R.id.title);
|
||||||
description = binding.description;
|
description = view.findViewById(R.id.description);
|
||||||
openInBrowser = binding.openInBrowser;
|
openInBrowser = view.findViewById(R.id.open_in_browser);
|
||||||
viewAssets = binding.viewAssets;
|
viewAssets = view.findViewById(R.id.view_assets);
|
||||||
|
progress = view.findViewById(R.id.progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -315,7 +370,11 @@ public class RepoItemActivity extends BaseActivity {
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
holder.recyclerView.setAdapter(position == 1 ? new ReleaseAdapter(module.getReleases()) : new InformationAdapter(module));
|
if (position == 1) {
|
||||||
|
holder.recyclerView.setAdapter(releaseAdapter = new ReleaseAdapter());
|
||||||
|
} else {
|
||||||
|
holder.recyclerView.setAdapter(new InformationAdapter(module));
|
||||||
|
}
|
||||||
holder.recyclerView.setLayoutManager(new LinearLayoutManagerFix(RepoItemActivity.this));
|
holder.recyclerView.setLayoutManager(new LinearLayoutManagerFix(RepoItemActivity.this));
|
||||||
holder.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setRaised(!top));
|
holder.recyclerView.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setRaised(!top));
|
||||||
RecyclerViewKt.fixEdgeEffect(holder.recyclerView, false, true);
|
RecyclerViewKt.fixEdgeEffect(holder.recyclerView, false, true);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<!--
|
||||||
|
~ 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 <https://www.gnu.org/licenses/>.
|
||||||
|
~
|
||||||
|
~ Copyright (C) 2020 EdXposed Contributors
|
||||||
|
~ Copyright (C) 2021 LSPosed Contributors
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
<!--
|
||||||
|
~ 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 <https://www.gnu.org/licenses/>.
|
||||||
|
~
|
||||||
|
~ Copyright (C) 2020 EdXposed Contributors
|
||||||
|
~ Copyright (C) 2021 LSPosed Contributors
|
||||||
|
-->
|
||||||
|
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:background="@drawable/item_background"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/progress"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:showAnimationBehavior="outward"
|
||||||
|
app:hideAnimationBehavior="inward" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawableStart="@drawable/ic_keyboard_arrow_down"
|
||||||
|
android:textAppearance="?textAppearanceListItem"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:drawablePadding="4dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="@string/module_release_load_more" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
@ -175,4 +175,6 @@
|
||||||
<string name="dns_over_http">DNS over HTTPS</string>
|
<string name="dns_over_http">DNS over HTTPS</string>
|
||||||
<string name="dns_over_http_summary">Workaround DNS poisoning in some nations</string>
|
<string name="dns_over_http_summary">Workaround DNS poisoning in some nations</string>
|
||||||
<string name="about_view_source_code"><![CDATA[View source code at %s<br/>Join our %s channel]]></string>
|
<string name="about_view_source_code"><![CDATA[View source code at %s<br/>Join our %s channel]]></string>
|
||||||
|
<string name="module_release_load_more">Load more</string>
|
||||||
|
<string name="module_release_no_more">No more release</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue