[app] Add load more to releases

This commit is contained in:
tehcneko 2021-02-13 18:09:59 +08:00
parent 6535ea0f8e
commit affa6fe643
7 changed files with 214 additions and 39 deletions

View File

@ -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) {
if (!listeners.contains(listener))
listeners.add(listener);
@ -126,5 +152,7 @@ public class RepoLoader {
public interface Listener {
void repoLoaded();
void moduleReleasesLoaded(OnlineModule module);
}
}

View File

@ -77,6 +77,7 @@ public class OnlineModule implements Serializable, Parcelable {
@SerializedName("stargazerCount")
@Expose
private Integer stargazerCount;
public boolean releasesLoaded = false;
public final static Creator<OnlineModule> CREATOR = new Creator<OnlineModule>() {
public OnlineModule createFromParcel(Parcel in) {

View File

@ -82,6 +82,11 @@ public class RepoActivity extends ListActivity implements RepoLoader.Listener {
});
}
@Override
public void moduleReleasesLoaded(OnlineModule module) {
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int itemId = item.getItemId();

View File

@ -39,6 +39,8 @@ import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
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 java.util.ArrayList;
@ -47,6 +49,7 @@ import java.util.ListIterator;
import io.github.lsposed.manager.R;
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.ItemRepoRecyclerviewBinding;
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.BorderView;
public class RepoItemActivity extends BaseActivity {
public class RepoItemActivity extends BaseActivity implements RepoLoader.Listener {
ActivityModuleDetailBinding binding;
private Markwon markwon;
private OnlineModule module;
private ReleaseAdapter releaseAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
RepoLoader.getInstance().addListener(this);
super.onCreate(savedInstanceState);
binding = ActivityModuleDetailBinding.inflate(getLayoutInflater());
String modulePackageName = getIntent().getStringExtra("modulePackageName");
@ -136,12 +141,36 @@ public class RepoItemActivity extends BaseActivity {
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
if (id == R.id.menu_open_in_browser) {
NavUtil.startURL(this, module.getUrl());
// TODO: replace with web version
NavUtil.startURL(this, "https://modules.lsposed.org/module/" + module.getName());
}
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 final OnlineModule module;
@ -229,52 +258,76 @@ public class RepoItemActivity extends BaseActivity {
}
private class ReleaseAdapter extends RecyclerView.Adapter<ReleaseAdapter.ViewHolder> {
private final List<Release> items;
private List<Release> items;
public ReleaseAdapter(List<Release> items) {
this.items = items;
public ReleaseAdapter() {
loadItems();
}
public void loadItems() {
this.items = module.getReleases();
notifyDataSetChanged();
}
@NonNull
@Override
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
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Release release = items.get(position);
holder.title.setText(release.getName());
holder.description.setTransformationMethod(new LinkTransformationMethod(RepoItemActivity.this));
holder.description.setSpannableFactory(NoCopySpannableFactory.getInstance());
markwon.setMarkdown(holder.description, release.getDescription());
holder.description.setMovementMethod(null);
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();
if (position == items.size()) {
holder.itemView.setOnClickListener(v -> {
if (holder.progress.getVisibility() == View.GONE) {
holder.title.setVisibility(View.GONE);
holder.progress.show();
RepoLoader.getInstance().loadRemoteReleases(module.getName());
}
});
} 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);
Release release = items.get(position);
holder.title.setText(release.getName());
holder.description.setTransformationMethod(new LinkTransformationMethod(RepoItemActivity.this));
holder.description.setSpannableFactory(NoCopySpannableFactory.getInstance());
markwon.setMarkdown(holder.description, release.getDescription());
holder.description.setMovementMethod(null);
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 {
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
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 {
@ -282,13 +335,15 @@ public class RepoItemActivity extends BaseActivity {
LinkifyTextView description;
View openInBrowser;
View viewAssets;
CircularProgressIndicator progress;
public ViewHolder(ItemRepoReleaseBinding binding) {
super(binding.getRoot());
title = binding.title;
description = binding.description;
openInBrowser = binding.openInBrowser;
viewAssets = binding.viewAssets;
public ViewHolder(View view) {
super(view);
title = view.findViewById(R.id.title);
description = view.findViewById(R.id.description);
openInBrowser = view.findViewById(R.id.open_in_browser);
viewAssets = view.findViewById(R.id.view_assets);
progress = view.findViewById(R.id.progress);
}
}
}
@ -315,7 +370,11 @@ public class RepoItemActivity extends BaseActivity {
break;
case 1:
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.getBorderViewDelegate().setBorderVisibilityChangedListener((top, oldTop, bottom, oldBottom) -> binding.appBar.setRaised(!top));
RecyclerViewKt.fixEdgeEffect(holder.recyclerView, false, true);

View File

@ -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>

View File

@ -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>

View File

@ -175,4 +175,6 @@
<string name="dns_over_http">DNS over HTTPS</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="module_release_load_more">Load more</string>
<string name="module_release_no_more">No more release</string>
</resources>