Initial work to support inline Download+Install button in app lists

Shows progress and download complete now, but left a few TODOs
lying around. These are mainly around the ability to then deal with
installing an app once download is complete.
This commit is contained in:
Peter Serwylo 2016-12-12 00:03:17 +11:00
parent 405279c0ff
commit 952024768a
2 changed files with 119 additions and 13 deletions

View File

@ -1,15 +1,23 @@
package org.fdroid.fdroid.views.apps;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.Outline;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.util.Pair;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
import android.widget.TextView;
@ -18,31 +26,57 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import org.fdroid.fdroid.AppDetails;
import org.fdroid.fdroid.AppDetails2;
import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.installer.InstallManagerService;
import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.DownloaderService;
// TODO: Support cancelling of downloads by tapping the install button a second time.
// TODO: Support installing of an app once downloaded by tapping the install button a second time.
public class AppListItemController extends RecyclerView.ViewHolder {
private final Activity activity;
private final Button installButton;
private final ImageView installButton;
private final ImageView icon;
private final TextView name;
private final TextView status;
private final DisplayImageOptions displayImageOptions;
private App currentApp;
private String currentAppDownloadUrl;
public AppListItemController(Activity activity, View itemView) {
@TargetApi(21)
public AppListItemController(final Activity activity, View itemView) {
super(itemView);
this.activity = activity;
installButton = (Button) itemView.findViewById(R.id.install);
installButton = (ImageView) itemView.findViewById(R.id.install);
installButton.setOnClickListener(onInstallClicked);
if (Build.VERSION.SDK_INT >= 21) {
installButton.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
float density = activity.getResources().getDisplayMetrics().density;
// TODO: This is a bit hacky/hardcoded/too-specific to the particular icons we're using.
// This is because the default "download & install" and "downloaded & ready to install"
// icons are smaller than the "downloading progress" button. Hence, we can't just use
// the width/height of the view to calculate the outline size.
int xPadding = (int) (8 * density);
int yPadding = (int) (9 * density);
outline.setOval(xPadding, yPadding, installButton.getWidth() - xPadding, installButton.getHeight() - yPadding);
}
});
}
icon = (ImageView) itemView.findViewById(R.id.icon);
name = (TextView) itemView.findViewById(R.id.app_name);
status = (TextView) itemView.findViewById(R.id.status);
@ -58,6 +92,16 @@ public class AppListItemController extends RecyclerView.ViewHolder {
ImageLoader.getInstance().displayImage(app.iconUrl, icon, displayImageOptions);
Apk apkToInstall = ApkProvider.Helper.findApkFromAnyRepo(activity, app.packageName, app.suggestedVersionCode);
currentAppDownloadUrl = apkToInstall.getUrl();
final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(activity.getApplicationContext());
broadcastManager.unregisterReceiver(onDownloadProgress);
broadcastManager.unregisterReceiver(onInstallAction);
broadcastManager.registerReceiver(onDownloadProgress, DownloaderService.getIntentFilter(currentAppDownloadUrl));
broadcastManager.registerReceiver(onInstallAction, Installer.getInstallIntentFilter(Uri.parse(currentAppDownloadUrl)));
configureStatusText(app);
configureInstallButton(app);
}
@ -107,13 +151,27 @@ public class AppListItemController extends RecyclerView.ViewHolder {
return;
}
boolean installable = app.canAndWantToUpdate(activity) || !app.isInstalled();
boolean shouldAllow = app.compatible && !app.isFiltered();
boolean readyToInstall = false;
for (AppUpdateStatusManager.AppUpdateStatus appStatus : AppUpdateStatusManager.getInstance(activity).getByPackageName(app.packageName)) {
if (appStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
readyToInstall = true;
break;
}
}
if (shouldAllow && installable) {
installButton.setVisibility(View.VISIBLE);
if (readyToInstall) {
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download_complete));
// TODO: If in the downloading phase, then need to reflect that instead of this "download complete" icon.
} else {
installButton.setVisibility(View.GONE);
boolean installable = app.canAndWantToUpdate(activity) || !app.isInstalled();
boolean shouldAllow = app.compatible && !app.isFiltered();
if (shouldAllow && installable) {
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download));
installButton.setVisibility(View.VISIBLE);
} else {
installButton.setVisibility(View.GONE);
}
}
}
@ -136,6 +194,51 @@ public class AppListItemController extends RecyclerView.ViewHolder {
}
};
private final BroadcastReceiver onDownloadProgress = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (currentApp == null || !TextUtils.equals(currentAppDownloadUrl, intent.getDataString())) {
return;
}
if (Downloader.ACTION_PROGRESS.equals(intent.getAction())) {
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download_progress));
int bytesRead = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, 0);
int totalBytes = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, 100);
int progressAsDegrees = (int) (((float) bytesRead / totalBytes) * 360);
installButton.setImageLevel(progressAsDegrees);
} else if (Downloader.ACTION_COMPLETE.equals(intent.getAction())) {
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download_complete));
}
}
};
private final BroadcastReceiver onInstallAction = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (currentApp == null) {
return;
}
Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK);
if (!TextUtils.equals(apk.packageName, currentApp.packageName)) {
return;
}
if (Installer.ACTION_INSTALL_STARTED.equals(intent.getAction())) {
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download_progress));
installButton.setImageLevel(0);
} else if (Installer.ACTION_INSTALL_COMPLETE.equals(intent.getAction())) {
installButton.setVisibility(View.GONE);
// TODO: It could've been a different version other than the current suggested version.
// In these cases, don't hide the button but rather set it back to the default install image.
} else if (Installer.ACTION_INSTALL_INTERRUPTED.equals(intent.getAction())) {
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download));
}
}
};
private final View.OnClickListener onInstallClicked = new View.OnClickListener() {
@Override
public void onClick(View v) {

View File

@ -53,11 +53,14 @@
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" />
<Button
<ImageView
android:id="@+id/install"
android:background="@drawable/download_button"
android:layout_width="48dp"
android:layout_height="48dp"
tools:src="@drawable/ic_download"
android:scaleType="fitXY"
android:contentDescription="@string/menu_install"
android:layout_width="64dp"
android:layout_height="64dp"
android:elevation="2dp"
android:padding="4dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"