Improve wikivoyage download / update card
This commit is contained in:
parent
5e3cc47816
commit
4946440e57
5 changed files with 139 additions and 37 deletions
|
@ -55,6 +55,7 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/file_data_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="@dimen/content_padding"
|
android:layout_marginBottom="@dimen/content_padding"
|
||||||
|
|
|
@ -12,14 +12,14 @@ import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.AsyncTask.Status;
|
import android.os.AsyncTask.Status;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.StatFs;
|
import android.os.StatFs;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.app.NotificationCompat.Builder;
|
import android.support.v4.app.NotificationCompat.Builder;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.osmand.IndexConstants;
|
import net.osmand.IndexConstants;
|
||||||
import net.osmand.PlatformUtil;
|
import net.osmand.PlatformUtil;
|
||||||
import net.osmand.map.WorldRegion;
|
import net.osmand.map.WorldRegion;
|
||||||
|
@ -52,7 +52,7 @@ public class DownloadIndexesThread {
|
||||||
private static final int NOTIFICATION_ID = 45;
|
private static final int NOTIFICATION_ID = 45;
|
||||||
private OsmandApplication app;
|
private OsmandApplication app;
|
||||||
|
|
||||||
private DownloadEvents uiActivity = null;
|
private Set<DownloadEvents> uiCallbacks = new HashSet<>();
|
||||||
private DatabaseHelper dbHelper;
|
private DatabaseHelper dbHelper;
|
||||||
private DownloadFileHelper downloadFileHelper;
|
private DownloadFileHelper downloadFileHelper;
|
||||||
private List<BasicProgressAsyncTask<?, ?, ?, ?>> currentRunningTask = Collections.synchronizedList(new ArrayList<BasicProgressAsyncTask<?, ?, ?, ?>>());
|
private List<BasicProgressAsyncTask<?, ?, ?, ?>> currentRunningTask = Collections.synchronizedList(new ArrayList<BasicProgressAsyncTask<?, ?, ?, ?>>());
|
||||||
|
@ -87,19 +87,19 @@ public class DownloadIndexesThread {
|
||||||
|
|
||||||
/// UI notifications methods
|
/// UI notifications methods
|
||||||
public void setUiActivity(DownloadEvents uiActivity) {
|
public void setUiActivity(DownloadEvents uiActivity) {
|
||||||
this.uiActivity = uiActivity;
|
uiCallbacks.add(uiActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetUiActivity(DownloadEvents uiActivity) {
|
public void resetUiActivity(DownloadEvents uiActivity) {
|
||||||
if (this.uiActivity == uiActivity) {
|
uiCallbacks.remove(uiActivity);
|
||||||
this.uiActivity = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
protected void downloadInProgress() {
|
protected void downloadInProgress() {
|
||||||
if (uiActivity != null) {
|
for (DownloadEvents uiActivity : uiCallbacks) {
|
||||||
uiActivity.downloadInProgress();
|
if (uiActivity != null) {
|
||||||
|
uiActivity.downloadInProgress();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateNotification();
|
updateNotification();
|
||||||
}
|
}
|
||||||
|
@ -151,8 +151,10 @@ public class DownloadIndexesThread {
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
protected void downloadHasFinished() {
|
protected void downloadHasFinished() {
|
||||||
if (uiActivity != null) {
|
for (DownloadEvents uiActivity : uiCallbacks) {
|
||||||
uiActivity.downloadHasFinished();
|
if (uiActivity != null) {
|
||||||
|
uiActivity.downloadHasFinished();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateNotification();
|
updateNotification();
|
||||||
}
|
}
|
||||||
|
@ -186,8 +188,10 @@ public class DownloadIndexesThread {
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
protected void newDownloadIndexes() {
|
protected void newDownloadIndexes() {
|
||||||
if (uiActivity != null) {
|
for (DownloadEvents uiActivity : uiCallbacks) {
|
||||||
uiActivity.newDownloadIndexes();
|
if (uiActivity != null) {
|
||||||
|
uiActivity.newDownloadIndexes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,8 +258,10 @@ public class DownloadIndexesThread {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(uiActivity instanceof Activity) {
|
for (DownloadEvents uiActivity : uiCallbacks) {
|
||||||
app.logEvent((Activity) uiActivity, "download_files");
|
if(uiActivity instanceof Activity) {
|
||||||
|
app.logEvent((Activity) uiActivity, "download_files");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for(IndexItem item : items) {
|
for(IndexItem item : items) {
|
||||||
if (!item.equals(currentDownloadingItem) && !indexItemDownloading.contains(item)) {
|
if (!item.equals(currentDownloadingItem) && !indexItemDownloading.contains(item)) {
|
||||||
|
@ -454,10 +460,12 @@ public class DownloadIndexesThread {
|
||||||
currentRunningTask.add(this);
|
currentRunningTask.add(this);
|
||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
downloadFileHelper.setInterruptDownloading(false);
|
downloadFileHelper.setInterruptDownloading(false);
|
||||||
if (uiActivity instanceof Activity) {
|
for (DownloadEvents uiActivity : uiCallbacks) {
|
||||||
View mainView = ((Activity) uiActivity).findViewById(R.id.MainLayout);
|
if (uiActivity instanceof Activity) {
|
||||||
if (mainView != null) {
|
View mainView = ((Activity) uiActivity).findViewById(R.id.MainLayout);
|
||||||
mainView.setKeepScreenOn(true);
|
if (mainView != null) {
|
||||||
|
mainView.setKeepScreenOn(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
startTask(ctx.getString(R.string.shared_string_downloading) + ctx.getString(R.string.shared_string_ellipsis), -1);
|
startTask(ctx.getString(R.string.shared_string_downloading) + ctx.getString(R.string.shared_string_ellipsis), -1);
|
||||||
|
@ -468,10 +476,12 @@ public class DownloadIndexesThread {
|
||||||
if (result != null && result.length() > 0) {
|
if (result != null && result.length() > 0) {
|
||||||
Toast.makeText(ctx, result, Toast.LENGTH_LONG).show();
|
Toast.makeText(ctx, result, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
if (uiActivity instanceof Activity) {
|
for (DownloadEvents uiActivity : uiCallbacks) {
|
||||||
View mainView = ((Activity) uiActivity).findViewById(R.id.MainLayout);
|
if (uiActivity instanceof Activity) {
|
||||||
if (mainView != null) {
|
View mainView = ((Activity) uiActivity).findViewById(R.id.MainLayout);
|
||||||
mainView.setKeepScreenOn(false);
|
if (mainView != null) {
|
||||||
|
mainView.setKeepScreenOn(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentRunningTask.remove(this);
|
currentRunningTask.remove(this);
|
||||||
|
|
|
@ -71,6 +71,27 @@ public class DownloadResources extends DownloadResourceGroup {
|
||||||
return worldMap;
|
return worldMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IndexItem getWorldWikivoyageItem() {
|
||||||
|
DownloadResourceGroup travelGroup = getSubGroupById(DownloadResourceGroupType.TRAVEL_GROUP.getDefaultId());
|
||||||
|
if (travelGroup != null) {
|
||||||
|
DownloadResourceGroup wikivoyageMaps = travelGroup.getSubGroupById(DownloadResourceGroupType.WIKIVOYAGE_MAPS.getDefaultId());
|
||||||
|
if (wikivoyageMaps != null) {
|
||||||
|
DownloadResourceGroup wikivoyageHeader = wikivoyageMaps.getSubGroupById(DownloadResourceGroupType.WIKIVOYAGE_HEADER.getDefaultId());
|
||||||
|
if (wikivoyageHeader != null) {
|
||||||
|
List<IndexItem> items = wikivoyageHeader.getIndividualResources();
|
||||||
|
if (items != null) {
|
||||||
|
for (IndexItem ii : items) {
|
||||||
|
if (ii.getBasename().equalsIgnoreCase(DownloadResources.WORLD_WIKIVOYAGE_NAME)) {
|
||||||
|
return ii;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public IndexItem getIndexItem(String fileName) {
|
public IndexItem getIndexItem(String fileName) {
|
||||||
IndexItem res = null;
|
IndexItem res = null;
|
||||||
if (rawResources == null) {
|
if (rawResources == null) {
|
||||||
|
|
|
@ -17,7 +17,9 @@ import net.osmand.plus.activities.LocalIndexHelper;
|
||||||
import net.osmand.plus.activities.LocalIndexInfo;
|
import net.osmand.plus.activities.LocalIndexInfo;
|
||||||
import net.osmand.plus.activities.OsmandActionBarActivity;
|
import net.osmand.plus.activities.OsmandActionBarActivity;
|
||||||
import net.osmand.plus.base.BaseOsmAndFragment;
|
import net.osmand.plus.base.BaseOsmAndFragment;
|
||||||
|
import net.osmand.plus.download.DownloadIndexesThread;
|
||||||
import net.osmand.plus.download.DownloadResources;
|
import net.osmand.plus.download.DownloadResources;
|
||||||
|
import net.osmand.plus.download.IndexItem;
|
||||||
import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask;
|
import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask;
|
||||||
import net.osmand.plus.wikivoyage.data.TravelArticle;
|
import net.osmand.plus.wikivoyage.data.TravelArticle;
|
||||||
import net.osmand.plus.wikivoyage.data.TravelDbHelper;
|
import net.osmand.plus.wikivoyage.data.TravelDbHelper;
|
||||||
|
@ -32,7 +34,7 @@ import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ExploreTabFragment extends BaseOsmAndFragment {
|
public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadIndexesThread.DownloadEvents {
|
||||||
|
|
||||||
private static final int DOWNLOAD_UPDATE_CARD_POSITION = 0;
|
private static final int DOWNLOAD_UPDATE_CARD_POSITION = 0;
|
||||||
|
|
||||||
|
@ -41,6 +43,9 @@ public class ExploreTabFragment extends BaseOsmAndFragment {
|
||||||
|
|
||||||
private boolean nightMode;
|
private boolean nightMode;
|
||||||
|
|
||||||
|
private boolean worldWikivoyageDownloaded;
|
||||||
|
private boolean downloadIndexesRequested;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
@ -57,11 +62,52 @@ public class ExploreTabFragment extends BaseOsmAndFragment {
|
||||||
return mainView;
|
return mainView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
getMyApplication().getDownloadThread().setUiActivity(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
getMyApplication().getDownloadThread().resetUiActivity(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void newDownloadIndexes() {
|
||||||
|
if (downloadIndexesRequested) {
|
||||||
|
downloadIndexesRequested = false;
|
||||||
|
OsmandApplication app = getMyApplication();
|
||||||
|
|
||||||
|
IndexItem wikivoyageItem = app.getDownloadThread().getIndexes().getWorldWikivoyageItem();
|
||||||
|
boolean outdated = wikivoyageItem != null && wikivoyageItem.isOutdated();
|
||||||
|
|
||||||
|
if (!worldWikivoyageDownloaded || outdated) {
|
||||||
|
TravelDownloadUpdateCard card = new TravelDownloadUpdateCard(app, nightMode, !outdated);
|
||||||
|
card.setIndexItem(wikivoyageItem);
|
||||||
|
if (adapter.addItem(DOWNLOAD_UPDATE_CARD_POSITION, card)) {
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadInProgress() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadHasFinished() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private List<BaseTravelCard> generateItems() {
|
private List<BaseTravelCard> generateItems() {
|
||||||
final List<BaseTravelCard> items = new ArrayList<>();
|
final List<BaseTravelCard> items = new ArrayList<>();
|
||||||
final OsmandApplication app = getMyApplication();
|
final OsmandApplication app = getMyApplication();
|
||||||
|
|
||||||
addDownloadUpdateCard();
|
runWorldWikivoyageFileCheck();
|
||||||
startEditingTravelCard = new StartEditingTravelCard(app, nightMode);
|
startEditingTravelCard = new StartEditingTravelCard(app, nightMode);
|
||||||
addOpenBetaTravelCard(items, nightMode);
|
addOpenBetaTravelCard(items, nightMode);
|
||||||
items.add(startEditingTravelCard);
|
items.add(startEditingTravelCard);
|
||||||
|
@ -71,17 +117,14 @@ public class ExploreTabFragment extends BaseOsmAndFragment {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDownloadUpdateCard() {
|
private void runWorldWikivoyageFileCheck() {
|
||||||
final OsmandApplication app = getMyApplication();
|
final OsmandApplication app = getMyApplication();
|
||||||
new CheckWorldWikivoyageTask(app, new CheckWorldWikivoyageTask.Callback() {
|
new CheckWorldWikivoyageTask(app, new CheckWorldWikivoyageTask.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onCheckFinished(boolean worldWikivoyageDownloaded) {
|
public void onCheckFinished(boolean worldWikivoyageDownloaded) {
|
||||||
if (!worldWikivoyageDownloaded && adapter != null) {
|
ExploreTabFragment.this.worldWikivoyageDownloaded = worldWikivoyageDownloaded;
|
||||||
TravelDownloadUpdateCard card = new TravelDownloadUpdateCard(app, nightMode, true);
|
downloadIndexesRequested = true;
|
||||||
if (adapter.addItem(DOWNLOAD_UPDATE_CARD_POSITION, card)) {
|
app.getDownloadThread().runReloadIndexFilesSilent();
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package net.osmand.plus.wikivoyage.explore.travelcards;
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -12,6 +13,9 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import net.osmand.plus.OsmandApplication;
|
import net.osmand.plus.OsmandApplication;
|
||||||
import net.osmand.plus.R;
|
import net.osmand.plus.R;
|
||||||
|
import net.osmand.plus.download.IndexItem;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
|
||||||
public class TravelDownloadUpdateCard extends BaseTravelCard {
|
public class TravelDownloadUpdateCard extends BaseTravelCard {
|
||||||
|
|
||||||
|
@ -20,9 +24,19 @@ public class TravelDownloadUpdateCard extends BaseTravelCard {
|
||||||
private boolean download;
|
private boolean download;
|
||||||
private boolean loadingInProgress;
|
private boolean loadingInProgress;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private IndexItem indexItem;
|
||||||
|
|
||||||
|
private DateFormat dateFormat;
|
||||||
|
|
||||||
|
public void setIndexItem(@Nullable IndexItem indexItem) {
|
||||||
|
this.indexItem = indexItem;
|
||||||
|
}
|
||||||
|
|
||||||
public TravelDownloadUpdateCard(OsmandApplication app, boolean nightMode, boolean download) {
|
public TravelDownloadUpdateCard(OsmandApplication app, boolean nightMode, boolean download) {
|
||||||
super(app, nightMode);
|
super(app, nightMode);
|
||||||
this.download = download;
|
this.download = download;
|
||||||
|
dateFormat = android.text.format.DateFormat.getMediumDateFormat(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -32,9 +46,14 @@ public class TravelDownloadUpdateCard extends BaseTravelCard {
|
||||||
holder.title.setText(getTitle());
|
holder.title.setText(getTitle());
|
||||||
holder.icon.setImageDrawable(getIcon());
|
holder.icon.setImageDrawable(getIcon());
|
||||||
holder.description.setText(getDescription());
|
holder.description.setText(getDescription());
|
||||||
holder.fileIcon.setImageDrawable(getFileIcon());
|
if (indexItem == null) {
|
||||||
holder.fileTitle.setText(getFileTitle());
|
holder.fileDataContainer.setVisibility(View.GONE);
|
||||||
holder.fileDescription.setText(getFileDescription());
|
} else {
|
||||||
|
holder.fileDataContainer.setVisibility(View.VISIBLE);
|
||||||
|
holder.fileIcon.setImageDrawable(getFileIcon());
|
||||||
|
holder.fileTitle.setText(getFileTitle());
|
||||||
|
holder.fileDescription.setText(getFileDescription());
|
||||||
|
}
|
||||||
boolean primaryBtnVisible = updatePrimaryButton(holder);
|
boolean primaryBtnVisible = updatePrimaryButton(holder);
|
||||||
boolean secondaryBtnVisible = updateSecondaryButton(holder);
|
boolean secondaryBtnVisible = updateSecondaryButton(holder);
|
||||||
holder.buttonsDivider.setVisibility(primaryBtnVisible && secondaryBtnVisible ? View.VISIBLE : View.GONE);
|
holder.buttonsDivider.setVisibility(primaryBtnVisible && secondaryBtnVisible ? View.VISIBLE : View.GONE);
|
||||||
|
@ -66,12 +85,18 @@ public class TravelDownloadUpdateCard extends BaseTravelCard {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private String getFileTitle() {
|
private String getFileTitle() {
|
||||||
return "Some file"; // TODO
|
return indexItem == null ? "" : indexItem.getBasename().replace("_", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private String getFileDescription() {
|
private String getFileDescription() {
|
||||||
return "Some description"; // TODO
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (indexItem != null) {
|
||||||
|
sb.append(app.getString(R.string.file_size_in_mb, indexItem.getArchiveSizeMB()));
|
||||||
|
sb.append(" • ");
|
||||||
|
sb.append(indexItem.getRemoteDate(dateFormat));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getFileIcon() {
|
private Drawable getFileIcon() {
|
||||||
|
@ -129,6 +154,7 @@ public class TravelDownloadUpdateCard extends BaseTravelCard {
|
||||||
final TextView title;
|
final TextView title;
|
||||||
final ImageView icon;
|
final ImageView icon;
|
||||||
final TextView description;
|
final TextView description;
|
||||||
|
final View fileDataContainer;
|
||||||
final ImageView fileIcon;
|
final ImageView fileIcon;
|
||||||
final TextView fileTitle;
|
final TextView fileTitle;
|
||||||
final TextView fileDescription;
|
final TextView fileDescription;
|
||||||
|
@ -145,6 +171,7 @@ public class TravelDownloadUpdateCard extends BaseTravelCard {
|
||||||
title = (TextView) itemView.findViewById(R.id.title);
|
title = (TextView) itemView.findViewById(R.id.title);
|
||||||
icon = (ImageView) itemView.findViewById(R.id.icon);
|
icon = (ImageView) itemView.findViewById(R.id.icon);
|
||||||
description = (TextView) itemView.findViewById(R.id.description);
|
description = (TextView) itemView.findViewById(R.id.description);
|
||||||
|
fileDataContainer = itemView.findViewById(R.id.file_data_container);
|
||||||
fileIcon = (ImageView) itemView.findViewById(R.id.file_icon);
|
fileIcon = (ImageView) itemView.findViewById(R.id.file_icon);
|
||||||
fileTitle = (TextView) itemView.findViewById(R.id.file_title);
|
fileTitle = (TextView) itemView.findViewById(R.id.file_title);
|
||||||
fileDescription = (TextView) itemView.findViewById(R.id.file_description);
|
fileDescription = (TextView) itemView.findViewById(R.id.file_description);
|
||||||
|
|
Loading…
Reference in a new issue