diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java index 3f2692349..f152d94b9 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java @@ -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) { diff --git a/app/src/main/res/layout/app_list_item.xml b/app/src/main/res/layout/app_list_item.xml index e477a7912..bfcaac064 100644 --- a/app/src/main/res/layout/app_list_item.xml +++ b/app/src/main/res/layout/app_list_item.xml @@ -53,11 +53,14 @@ android:layout_marginStart="8dp" android:layout_marginLeft="8dp" /> -