Merge branch 'improve-updates-tab-stability' into 'master'
Refactor `AppListItemController` to improve updates tab stability See merge request !549
This commit is contained in:
commit
8f680bc1aa
@ -7,7 +7,7 @@ import android.view.ViewGroup;
|
|||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
|
|
||||||
class AppListAdapter extends RecyclerView.Adapter<AppListItemController> {
|
class AppListAdapter extends RecyclerView.Adapter<StandardAppListItemController> {
|
||||||
|
|
||||||
private Cursor cursor;
|
private Cursor cursor;
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
@ -24,13 +24,13 @@ class AppListAdapter extends RecyclerView.Adapter<AppListItemController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AppListItemController onCreateViewHolder(ViewGroup parent, int viewType) {
|
public StandardAppListItemController onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
return new AppListItemController(activity, activity.getLayoutInflater()
|
return new StandardAppListItemController(activity, activity.getLayoutInflater()
|
||||||
.inflate(R.layout.app_list_item, parent, false));
|
.inflate(R.layout.app_list_item, parent, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(AppListItemController holder, int position) {
|
public void onBindViewHolder(StandardAppListItemController holder, int position) {
|
||||||
cursor.moveToPosition(position);
|
cursor.moveToPosition(position);
|
||||||
holder.bindModel(new App(cursor));
|
holder.bindModel(new App(cursor));
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,6 @@ import org.fdroid.fdroid.Utils;
|
|||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.data.ApkProvider;
|
import org.fdroid.fdroid.data.ApkProvider;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.data.AppPrefs;
|
|
||||||
import org.fdroid.fdroid.installer.ApkCache;
|
import org.fdroid.fdroid.installer.ApkCache;
|
||||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||||
import org.fdroid.fdroid.installer.Installer;
|
import org.fdroid.fdroid.installer.Installer;
|
||||||
@ -46,13 +45,25 @@ import org.fdroid.fdroid.installer.InstallerFactory;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
// TODO: Support cancelling of downloads by tapping the install button a second time.
|
/**
|
||||||
@SuppressWarnings("LineLength")
|
* Supports the following layouts:
|
||||||
public class AppListItemController extends RecyclerView.ViewHolder {
|
* <ul>
|
||||||
|
* <li>app_list_item (see {@link StandardAppListItemController}</li>
|
||||||
|
* <li>updateable_app_list_status_item (see
|
||||||
|
* {@link org.fdroid.fdroid.views.updates.items.AppStatusListItemController}</li>
|
||||||
|
* <li>updateable_app_list_item (see
|
||||||
|
* {@link org.fdroid.fdroid.views.updates.items.UpdateableAppListItemController}</li>
|
||||||
|
* <li>installed_app_list_item (see {@link StandardAppListItemController}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* The state of the UI is defined in a dumb {@link AppListItemState} class, then applied to the UI
|
||||||
|
* in the {@link #refreshView(App, AppUpdateStatusManager.AppUpdateStatus)} method.
|
||||||
|
*/
|
||||||
|
public abstract class AppListItemController extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
private static final String TAG = "AppListItemController";
|
private static final String TAG = "AppListItemController";
|
||||||
|
|
||||||
private final Activity activity;
|
protected final Activity activity;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final ImageView icon;
|
private final ImageView icon;
|
||||||
@ -67,13 +78,7 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
|||||||
private final TextView status;
|
private final TextView status;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final TextView downloadReady;
|
private final TextView secondaryStatus;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final TextView installedVersion;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final TextView ignoredStatus;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final ProgressBar progressBar;
|
private final ProgressBar progressBar;
|
||||||
@ -111,13 +116,15 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
|||||||
public void getOutline(View view, Outline outline) {
|
public void getOutline(View view, Outline outline) {
|
||||||
float density = activity.getResources().getDisplayMetrics().density;
|
float density = activity.getResources().getDisplayMetrics().density;
|
||||||
|
|
||||||
// TODO: This is a bit hacky/hardcoded/too-specific to the particular icons we're using.
|
// 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"
|
// 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
|
// 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.
|
// the width/height of the view to calculate the outline size.
|
||||||
int xPadding = (int) (8 * density);
|
int xPadding = (int) (8 * density);
|
||||||
int yPadding = (int) (9 * density);
|
int yPadding = (int) (9 * density);
|
||||||
outline.setOval(xPadding, yPadding, installButton.getWidth() - xPadding, installButton.getHeight() - yPadding);
|
int right = installButton.getWidth() - xPadding;
|
||||||
|
int bottom = installButton.getHeight() - yPadding;
|
||||||
|
outline.setOval(xPadding, yPadding, right, bottom);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -126,9 +133,7 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
|||||||
icon = (ImageView) itemView.findViewById(R.id.icon);
|
icon = (ImageView) itemView.findViewById(R.id.icon);
|
||||||
name = (TextView) itemView.findViewById(R.id.app_name);
|
name = (TextView) itemView.findViewById(R.id.app_name);
|
||||||
status = (TextView) itemView.findViewById(R.id.status);
|
status = (TextView) itemView.findViewById(R.id.status);
|
||||||
downloadReady = (TextView) itemView.findViewById(R.id.download_ready);
|
secondaryStatus = (TextView) itemView.findViewById(R.id.secondary_status);
|
||||||
installedVersion = (TextView) itemView.findViewById(R.id.installed_version);
|
|
||||||
ignoredStatus = (TextView) itemView.findViewById(R.id.ignored_status);
|
|
||||||
progressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar);
|
progressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar);
|
||||||
cancelButton = (ImageButton) itemView.findViewById(R.id.cancel_button);
|
cancelButton = (ImageButton) itemView.findViewById(R.id.cancel_button);
|
||||||
actionButton = (Button) itemView.findViewById(R.id.action_button);
|
actionButton = (Button) itemView.findViewById(R.id.action_button);
|
||||||
@ -146,247 +151,197 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
|||||||
itemView.setOnClickListener(onAppClicked);
|
itemView.setOnClickListener(onAppClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Figures out the current install/update/download/etc status for the app we are viewing.
|
|
||||||
* Then, asks the view to update itself to reflect this status.
|
|
||||||
*/
|
|
||||||
private void refreshStatus(@NonNull App app) {
|
|
||||||
Iterator<AppUpdateStatusManager.AppUpdateStatus> statuses = AppUpdateStatusManager.getInstance(activity).getByPackageName(app.packageName).iterator();
|
|
||||||
if (statuses.hasNext()) {
|
|
||||||
AppUpdateStatusManager.AppUpdateStatus status = statuses.next();
|
|
||||||
updateAppStatus(app, status);
|
|
||||||
} else {
|
|
||||||
currentStatus = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bindModel(@NonNull App app) {
|
public void bindModel(@NonNull App app) {
|
||||||
currentApp = app;
|
currentApp = app;
|
||||||
|
|
||||||
ImageLoader.getInstance().displayImage(app.iconUrl, icon, displayImageOptions);
|
ImageLoader.getInstance().displayImage(app.iconUrl, icon, displayImageOptions);
|
||||||
|
|
||||||
refreshStatus(app);
|
// Figures out the current install/update/download/etc status for the app we are viewing.
|
||||||
|
// Then, asks the view to update itself to reflect this status.
|
||||||
|
Iterator<AppUpdateStatusManager.AppUpdateStatus> statuses =
|
||||||
|
AppUpdateStatusManager.getInstance(activity).getByPackageName(app.packageName).iterator();
|
||||||
|
if (statuses.hasNext()) {
|
||||||
|
AppUpdateStatusManager.AppUpdateStatus status = statuses.next();
|
||||||
|
updateAppStatus(app, status);
|
||||||
|
} else {
|
||||||
|
updateAppStatus(app, null);
|
||||||
|
}
|
||||||
|
|
||||||
final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(activity.getApplicationContext());
|
final LocalBroadcastManager broadcastManager =
|
||||||
broadcastManager.unregisterReceiver(onInstallAction);
|
LocalBroadcastManager.getInstance(activity.getApplicationContext());
|
||||||
broadcastManager.unregisterReceiver(onStatusChanged);
|
broadcastManager.unregisterReceiver(onStatusChanged);
|
||||||
|
|
||||||
// broadcastManager.registerReceiver(onInstallAction, Installer.getInstallIntentFilter(Uri.parse(currentAppDownloadUrl)));
|
|
||||||
|
|
||||||
|
|
||||||
IntentFilter intentFilter = new IntentFilter();
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
intentFilter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_ADDED);
|
intentFilter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_ADDED);
|
||||||
intentFilter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED);
|
intentFilter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED);
|
||||||
intentFilter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_CHANGED);
|
intentFilter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_CHANGED);
|
||||||
broadcastManager.registerReceiver(onStatusChanged, intentFilter);
|
broadcastManager.registerReceiver(onStatusChanged, intentFilter);
|
||||||
|
|
||||||
configureAppName(app);
|
|
||||||
configureStatusText(app);
|
|
||||||
configureInstalledVersion(app);
|
|
||||||
configureIgnoredStatus(app);
|
|
||||||
configureInstallButton(app);
|
|
||||||
configureActionButton(app);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the text/visibility of the {@link R.id#status} {@link TextView} based on whether the app:
|
* Updates both the progress bar and the circular install button (which shows progress around the outside of
|
||||||
* * Is compatible with the users device
|
* the circle). Also updates the app label to indicate that the app is being downloaded.
|
||||||
* * Is installed
|
|
||||||
* * Can be updated
|
|
||||||
*/
|
*/
|
||||||
private void configureStatusText(@NonNull App app) {
|
private void updateAppStatus(@NonNull App app, @Nullable AppUpdateStatusManager.AppUpdateStatus status) {
|
||||||
if (status == null) {
|
currentStatus = status;
|
||||||
return;
|
refreshView(app, status);
|
||||||
}
|
|
||||||
|
|
||||||
if (!app.compatible) {
|
|
||||||
status.setText(activity.getString(R.string.app_incompatible));
|
|
||||||
status.setVisibility(View.VISIBLE);
|
|
||||||
} else if (app.isInstalled()) {
|
|
||||||
if (app.canAndWantToUpdate(activity)) {
|
|
||||||
String upgradeFromTo = activity.getString(R.string.app_version_x_available, app.getSuggestedVersionName());
|
|
||||||
status.setText(upgradeFromTo);
|
|
||||||
} else {
|
|
||||||
String installed = activity.getString(R.string.app_version_x_installed, app.installedVersionName);
|
|
||||||
status.setText(installed);
|
|
||||||
}
|
|
||||||
|
|
||||||
status.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
status.setVisibility(View.INVISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the currently installed version name, and whether or not it is the recommended version.
|
* Queries the current state via {@link #getCurrentViewState(App, AppUpdateStatusManager.AppUpdateStatus)}
|
||||||
* Binds to the {@link R.id#installed_version} {@link TextView}.
|
* and then updates the relevant widgets depending on that state.
|
||||||
|
*
|
||||||
|
* Should contain little to no business logic, this all belongs to
|
||||||
|
* {@link #getCurrentViewState(App, AppUpdateStatusManager.AppUpdateStatus)}.
|
||||||
|
*
|
||||||
|
* @see AppListItemState
|
||||||
|
* @see #getCurrentViewState(App, AppUpdateStatusManager.AppUpdateStatus)
|
||||||
*/
|
*/
|
||||||
private void configureInstalledVersion(@NonNull App app) {
|
private void refreshView(@NonNull App app,
|
||||||
if (installedVersion == null) {
|
@Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int res = (app.suggestedVersionCode == app.installedVersionCode)
|
AppListItemState viewState = getCurrentViewState(app, appStatus);
|
||||||
? R.string.app_recommended_version_installed : R.string.app_version_x_installed;
|
|
||||||
|
|
||||||
installedVersion.setText(activity.getString(res, app.installedVersionName));
|
name.setText(viewState.getMainText());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (actionButton != null) {
|
||||||
* Shows whether the user has previously asked to ignore updates for this app entirely, or for a
|
if (viewState.shouldShowActionButton()) {
|
||||||
* specific version of this app. Binds to the {@link R.id#ignored_status} {@link TextView}.
|
actionButton.setVisibility(View.VISIBLE);
|
||||||
*/
|
actionButton.setText(viewState.getActionButtonText());
|
||||||
private void configureIgnoredStatus(@NonNull App app) {
|
|
||||||
if (ignoredStatus == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppPrefs prefs = app.getPrefs(activity);
|
|
||||||
if (prefs.ignoreAllUpdates) {
|
|
||||||
ignoredStatus.setText(activity.getString(R.string.installed_app__updates_ignored));
|
|
||||||
ignoredStatus.setVisibility(View.VISIBLE);
|
|
||||||
} else if (prefs.ignoreThisUpdate > 0 && prefs.ignoreThisUpdate == app.suggestedVersionCode) {
|
|
||||||
ignoredStatus.setText(activity.getString(R.string.installed_app__updates_ignored_for_suggested_version, app.getSuggestedVersionName()));
|
|
||||||
ignoredStatus.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
ignoredStatus.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queries the {@link AppUpdateStatusManager} to find out if there are any apks corresponding to
|
|
||||||
* `app` which are ready to install.
|
|
||||||
*/
|
|
||||||
private boolean isReadyToInstall(@NonNull App app) {
|
|
||||||
for (AppUpdateStatusManager.AppUpdateStatus appStatus : AppUpdateStatusManager.getInstance(activity).getByPackageName(app.packageName)) {
|
|
||||||
if (appStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The app name {@link TextView} is used for a few reasons:
|
|
||||||
* <li> Display name + summary of the app (most common).
|
|
||||||
* <li> If downloading, mention that it is downloading instead of showing the summary.
|
|
||||||
* <li> If downloaded and ready to install, mention that it is ready to update/install.
|
|
||||||
*/
|
|
||||||
private void configureAppName(@NonNull App app) {
|
|
||||||
if (downloadReady != null) {
|
|
||||||
downloadReady.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
|
|
||||||
name.setText(app.name);
|
|
||||||
if (downloadReady != null) {
|
|
||||||
downloadReady.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
} else if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Downloading) {
|
|
||||||
name.setText(activity.getString(R.string.app_list__name__downloading_in_progress, app.name));
|
|
||||||
} else if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Installed) {
|
|
||||||
name.setText(activity.getString(R.string.app_list__name__successfully_installed, app.name));
|
|
||||||
} else {
|
|
||||||
name.setText(Utils.formatAppNameAndSummary(app.name, app.summary));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The action button will either tell the user to "Update" or "Install" the app. Both actually do
|
|
||||||
* the same thing (launch the package manager). It depends on whether the app has a previous
|
|
||||||
* version installed or not as to the chosen terminology.
|
|
||||||
*/
|
|
||||||
private void configureActionButton(@NonNull App app) {
|
|
||||||
if (actionButton == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
actionButton.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Installed) {
|
|
||||||
if (activity.getPackageManager().getLaunchIntentForPackage(app.packageName) != null) {
|
|
||||||
actionButton.setText(R.string.menu_launch);
|
|
||||||
} else {
|
} else {
|
||||||
actionButton.setVisibility(View.GONE);
|
actionButton.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
} else if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
|
}
|
||||||
if (app.isInstalled()) {
|
|
||||||
actionButton.setText(R.string.app__install_downloaded_update);
|
if (progressBar != null) {
|
||||||
|
if (viewState.showProgress()) {
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
if (viewState.isProgressIndeterminate()) {
|
||||||
|
progressBar.setIndeterminate(true);
|
||||||
|
} else {
|
||||||
|
progressBar.setIndeterminate(false);
|
||||||
|
progressBar.setMax(viewState.getProgressMax());
|
||||||
|
progressBar.setProgress(viewState.getProgressCurrent());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
actionButton.setText(R.string.menu_install);
|
progressBar.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
actionButton.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The install button is shown when an app:
|
|
||||||
* * Is compatible with the users device.
|
|
||||||
* * Has not been filtered due to anti-features/root/etc.
|
|
||||||
* * Is either not installed or installed but can be updated.
|
|
||||||
*/
|
|
||||||
private void configureInstallButton(@NonNull App app) {
|
|
||||||
if (installButton == null) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isReadyToInstall(app)) {
|
if (cancelButton != null) {
|
||||||
installButton.setVisibility(View.GONE);
|
if (viewState.showProgress()) {
|
||||||
} else {
|
cancelButton.setVisibility(View.VISIBLE);
|
||||||
boolean installable = app.canAndWantToUpdate(activity) || !app.isInstalled();
|
} else {
|
||||||
boolean shouldAllow = app.compatible && !app.isFiltered();
|
cancelButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldAllow && installable) {
|
if (installButton != null) {
|
||||||
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download));
|
if (viewState.shouldShowActionButton()) {
|
||||||
|
installButton.setVisibility(View.GONE);
|
||||||
|
} else if (viewState.showProgress()) {
|
||||||
installButton.setVisibility(View.VISIBLE);
|
installButton.setVisibility(View.VISIBLE);
|
||||||
|
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download_progress));
|
||||||
|
int progressAsDegrees = viewState.getProgressMax() <= 0 ? 0 :
|
||||||
|
(int) (((float) viewState.getProgressCurrent() / viewState.getProgressMax()) * 360);
|
||||||
|
installButton.setImageLevel(progressAsDegrees);
|
||||||
|
} else if (viewState.shouldShowInstall()) {
|
||||||
|
installButton.setVisibility(View.VISIBLE);
|
||||||
|
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download));
|
||||||
} else {
|
} else {
|
||||||
installButton.setVisibility(View.GONE);
|
installButton.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void onDownloadProgressUpdated(int bytesRead, int totalBytes) {
|
if (status != null) {
|
||||||
if (installButton != null) {
|
CharSequence statusText = viewState.getStatusText();
|
||||||
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download_progress));
|
if (statusText == null) {
|
||||||
int progressAsDegrees = totalBytes <= 0 ? 0 : (int) (((float) bytesRead / totalBytes) * 360);
|
status.setVisibility(View.GONE);
|
||||||
installButton.setImageLevel(progressAsDegrees);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progressBar != null) {
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
if (totalBytes <= 0) {
|
|
||||||
progressBar.setIndeterminate(true);
|
|
||||||
} else {
|
} else {
|
||||||
progressBar.setIndeterminate(false);
|
status.setVisibility(View.VISIBLE);
|
||||||
progressBar.setMax(totalBytes);
|
status.setText(statusText);
|
||||||
progressBar.setProgress(bytesRead);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelButton != null) {
|
if (secondaryStatus != null) {
|
||||||
cancelButton.setVisibility(View.VISIBLE);
|
CharSequence statusText = viewState.getSecondaryStatusText();
|
||||||
|
if (statusText == null) {
|
||||||
|
secondaryStatus.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
secondaryStatus.setVisibility(View.VISIBLE);
|
||||||
|
secondaryStatus.setText(statusText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDownloadComplete() {
|
@NonNull
|
||||||
if (installButton != null) {
|
protected AppListItemState getCurrentViewState(
|
||||||
installButton.setVisibility(View.GONE);
|
@NonNull App app, @Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) {
|
||||||
}
|
if (appStatus == null) {
|
||||||
|
return getViewStateDefault(app);
|
||||||
|
} else {
|
||||||
|
switch (appStatus.status) {
|
||||||
|
case ReadyToInstall:
|
||||||
|
return getViewStateReadyToInstall(app);
|
||||||
|
|
||||||
if (progressBar != null) {
|
case Downloading:
|
||||||
progressBar.setVisibility(View.GONE);
|
return getViewStateDownloading(app, appStatus);
|
||||||
}
|
|
||||||
|
|
||||||
if (cancelButton != null) {
|
case Installed:
|
||||||
cancelButton.setVisibility(View.GONE);
|
return getViewStateInstalled(app);
|
||||||
}
|
|
||||||
|
|
||||||
if (currentApp != null) {
|
default:
|
||||||
configureActionButton(currentApp);
|
return getViewStateDefault(app);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected AppListItemState getViewStateInstalled(@NonNull App app) {
|
||||||
|
CharSequence mainText = activity.getString(
|
||||||
|
R.string.app_list__name__successfully_installed, app.name);
|
||||||
|
|
||||||
|
AppListItemState state = new AppListItemState(app)
|
||||||
|
.setMainText(mainText)
|
||||||
|
.setStatusText(activity.getString(R.string.notification_content_single_installed));
|
||||||
|
|
||||||
|
if (activity.getPackageManager().getLaunchIntentForPackage(app.packageName) != null) {
|
||||||
|
state.showActionButton(activity.getString(R.string.menu_launch));
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AppListItemState getViewStateDownloading(
|
||||||
|
@NonNull App app, @NonNull AppUpdateStatusManager.AppUpdateStatus currentStatus) {
|
||||||
|
CharSequence mainText = activity.getString(
|
||||||
|
R.string.app_list__name__downloading_in_progress, app.name);
|
||||||
|
|
||||||
|
return new AppListItemState(app)
|
||||||
|
.setMainText(mainText)
|
||||||
|
.setProgress(currentStatus.progressCurrent, currentStatus.progressMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AppListItemState getViewStateReadyToInstall(@NonNull App app) {
|
||||||
|
int actionButtonLabel = app.isInstalled()
|
||||||
|
? R.string.app__install_downloaded_update
|
||||||
|
: R.string.menu_install;
|
||||||
|
|
||||||
|
return new AppListItemState(app)
|
||||||
|
.setMainText(app.name)
|
||||||
|
.showActionButton(activity.getString(actionButtonLabel))
|
||||||
|
.setStatusText(activity.getString(R.string.app_list_download_ready));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AppListItemState getViewStateDefault(@NonNull App app) {
|
||||||
|
return new AppListItemState(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
* Various listeners for each different click/broadcast that we need
|
||||||
|
* to respond to.
|
||||||
|
* =================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
private final View.OnClickListener onAppClicked = new View.OnClickListener() {
|
private final View.OnClickListener onAppClicked = new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -398,8 +353,10 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
|||||||
Intent intent = new Intent(activity, AppDetails2.class);
|
Intent intent = new Intent(activity, AppDetails2.class);
|
||||||
intent.putExtra(AppDetails2.EXTRA_APPID, currentApp.packageName);
|
intent.putExtra(AppDetails2.EXTRA_APPID, currentApp.packageName);
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
Pair<View, String> iconTransitionPair = Pair.create((View) icon, activity.getString(R.string.transition_app_item_icon));
|
String transitionAppIcon = activity.getString(R.string.transition_app_item_icon);
|
||||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, iconTransitionPair).toBundle();
|
Pair<View, String> iconTransitionPair = Pair.create((View) icon, transitionAppIcon);
|
||||||
|
Bundle bundle = ActivityOptionsCompat
|
||||||
|
.makeSceneTransitionAnimation(activity, iconTransitionPair).toBundle();
|
||||||
activity.startActivity(intent, bundle);
|
activity.startActivity(intent, bundle);
|
||||||
} else {
|
} else {
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
@ -407,41 +364,15 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates both the progress bar and the circular install button (which shows progress around the outside of the circle).
|
|
||||||
* Also updates the app label to indicate that the app is being downloaded.
|
|
||||||
*/
|
|
||||||
private void updateAppStatus(@NonNull App app, @NonNull AppUpdateStatusManager.AppUpdateStatus status) {
|
|
||||||
currentStatus = status;
|
|
||||||
|
|
||||||
configureAppName(app);
|
|
||||||
configureActionButton(app);
|
|
||||||
|
|
||||||
switch (status.status) {
|
|
||||||
case Downloading:
|
|
||||||
onDownloadProgressUpdated(status.progressCurrent, status.progressMax);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ReadyToInstall:
|
|
||||||
onDownloadComplete();
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
|
||||||
case Installed:
|
|
||||||
case Installing:
|
|
||||||
case InstallError:
|
|
||||||
case UpdateAvailable:
|
|
||||||
case DownloadInterrupted:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final BroadcastReceiver onStatusChanged = new BroadcastReceiver() {
|
private final BroadcastReceiver onStatusChanged = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
AppUpdateStatusManager.AppUpdateStatus newStatus = intent.getParcelableExtra(AppUpdateStatusManager.EXTRA_STATUS);
|
AppUpdateStatusManager.AppUpdateStatus newStatus =
|
||||||
|
intent.getParcelableExtra(AppUpdateStatusManager.EXTRA_STATUS);
|
||||||
|
|
||||||
if (currentApp == null || !TextUtils.equals(newStatus.app.packageName, currentApp.packageName) || (installButton == null && progressBar == null)) {
|
if (currentApp == null
|
||||||
|
|| !TextUtils.equals(newStatus.app.packageName, currentApp.packageName)
|
||||||
|
|| (installButton == null && progressBar == null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,34 +380,6 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final BroadcastReceiver onInstallAction = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
|
||||||
if (currentApp == null || !TextUtils.equals(apk.packageName, currentApp.packageName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
configureAppName(currentApp);
|
|
||||||
configureActionButton(currentApp);
|
|
||||||
|
|
||||||
if (installButton == null) {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
private final View.OnClickListener onActionClicked = new View.OnClickListener() {
|
private final View.OnClickListener onActionClicked = new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -500,8 +403,10 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
|
if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
|
||||||
File apkFilePath = ApkCache.getApkDownloadPath(activity, Uri.parse(currentStatus.apk.getUrl()));
|
Uri apkDownloadUri = Uri.parse(currentStatus.apk.getUrl());
|
||||||
Utils.debugLog(TAG, "skip download, we have already downloaded " + currentStatus.apk.getUrl() + " to " + apkFilePath);
|
File apkFilePath = ApkCache.getApkDownloadPath(activity, apkDownloadUri);
|
||||||
|
Utils.debugLog(TAG, "skip download, we have already downloaded " + currentStatus.apk.getUrl() +
|
||||||
|
" to " + apkFilePath);
|
||||||
|
|
||||||
// TODO: This seems like a bit of a hack. Is there a better way to do this by changing
|
// TODO: This seems like a bit of a hack. Is there a better way to do this by changing
|
||||||
// the Installer API so that we can ask it to install without having to get it to fire
|
// the Installer API so that we can ask it to install without having to get it to fire
|
||||||
@ -513,7 +418,8 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
|||||||
broadcastManager.unregisterReceiver(this);
|
broadcastManager.unregisterReceiver(this);
|
||||||
|
|
||||||
if (Installer.ACTION_INSTALL_USER_INTERACTION.equals(intent.getAction())) {
|
if (Installer.ACTION_INSTALL_USER_INTERACTION.equals(intent.getAction())) {
|
||||||
PendingIntent pendingIntent = intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
|
PendingIntent pendingIntent =
|
||||||
|
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
|
||||||
try {
|
try {
|
||||||
pendingIntent.send();
|
pendingIntent.send();
|
||||||
} catch (PendingIntent.CanceledException ignored) { }
|
} catch (PendingIntent.CanceledException ignored) { }
|
||||||
@ -521,9 +427,9 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
broadcastManager.registerReceiver(receiver, Installer.getInstallIntentFilter(Uri.parse(currentStatus.apk.getUrl())));
|
broadcastManager.registerReceiver(receiver, Installer.getInstallIntentFilter(apkDownloadUri));
|
||||||
Installer installer = InstallerFactory.create(activity, currentStatus.apk);
|
Installer installer = InstallerFactory.create(activity, currentStatus.apk);
|
||||||
installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), Uri.parse(currentStatus.apk.getUrl()));
|
installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), apkDownloadUri);
|
||||||
} else {
|
} else {
|
||||||
final Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, currentApp);
|
final Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, currentApp);
|
||||||
InstallManagerService.queue(activity, currentApp, suggestedApk);
|
InstallManagerService.queue(activity, currentApp, suggestedApk);
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
package org.fdroid.fdroid.views.apps;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dumb model which is used to specify what should/should not be shown in an {@link AppListItemController}.
|
||||||
|
* @see AppListItemController and its subclasses.
|
||||||
|
*/
|
||||||
|
public class AppListItemState {
|
||||||
|
private final App app;
|
||||||
|
private CharSequence mainText = null;
|
||||||
|
private CharSequence actionButtonText = null;
|
||||||
|
private CharSequence statusText = null;
|
||||||
|
private CharSequence secondaryStatusText = null;
|
||||||
|
private int progressCurrent = -1;
|
||||||
|
private int progressMax = -1;
|
||||||
|
private boolean showInstallButton;
|
||||||
|
|
||||||
|
public AppListItemState(@NonNull App app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppListItemState setMainText(CharSequence mainText) {
|
||||||
|
this.mainText = mainText;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppListItemState showActionButton(CharSequence label) {
|
||||||
|
actionButtonText = label;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppListItemState setStatusText(CharSequence text) {
|
||||||
|
this.statusText = text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppListItemState setSecondaryStatusText(CharSequence text) {
|
||||||
|
this.secondaryStatusText = text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppListItemState setProgress(int progressCurrent, int progressMax) {
|
||||||
|
this.progressCurrent = progressCurrent;
|
||||||
|
this.progressMax = progressMax;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppListItemState setShowInstallButton(boolean show) {
|
||||||
|
this.showInstallButton = show;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public CharSequence getMainText() {
|
||||||
|
return mainText != null
|
||||||
|
? mainText
|
||||||
|
: Utils.formatAppNameAndSummary(app.name, app.summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldShowInstall() {
|
||||||
|
return showInstallButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldShowActionButton() {
|
||||||
|
return actionButtonText != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getActionButtonText() {
|
||||||
|
return actionButtonText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean showProgress() {
|
||||||
|
return progressCurrent >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProgressIndeterminate() {
|
||||||
|
return progressMax <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProgressCurrent() {
|
||||||
|
return progressCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProgressMax() {
|
||||||
|
return progressMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public CharSequence getStatusText() {
|
||||||
|
return statusText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public CharSequence getSecondaryStatusText() {
|
||||||
|
return secondaryStatusText;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package org.fdroid.fdroid.views.apps;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.AppUpdateStatusManager;
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for search results or for category lists.
|
||||||
|
* Shows an inline download button, and also (if appropriate):
|
||||||
|
* * Whether the app is incompatible.
|
||||||
|
* * Version that app can be upgraded to.
|
||||||
|
* * Installed version.
|
||||||
|
*/
|
||||||
|
public class StandardAppListItemController extends AppListItemController {
|
||||||
|
public StandardAppListItemController(Activity activity, View itemView) {
|
||||||
|
super(activity, itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AppListItemState getCurrentViewState(
|
||||||
|
@NonNull App app, @Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) {
|
||||||
|
|
||||||
|
return super.getCurrentViewState(app, appStatus)
|
||||||
|
.setStatusText(getStatusText(app))
|
||||||
|
.setShowInstallButton(shouldShowInstall(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private CharSequence getStatusText(@NonNull App app) {
|
||||||
|
if (!app.compatible) {
|
||||||
|
return activity.getString(R.string.app_incompatible);
|
||||||
|
} else if (app.isInstalled()) {
|
||||||
|
if (app.canAndWantToUpdate(activity)) {
|
||||||
|
return activity.getString(R.string.app_version_x_available, app.getSuggestedVersionName());
|
||||||
|
} else {
|
||||||
|
return activity.getString(R.string.app_version_x_installed, app.installedVersionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldShowInstall(@NonNull App app) {
|
||||||
|
boolean installable = app.canAndWantToUpdate(activity) || !app.isInstalled();
|
||||||
|
boolean shouldAllow = app.compatible && !app.isFiltered();
|
||||||
|
|
||||||
|
return installable && shouldAllow;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package org.fdroid.fdroid.views.installed;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
import org.fdroid.fdroid.data.Schema;
|
||||||
|
|
||||||
|
class InstalledAppListAdapter extends RecyclerView.Adapter<InstalledAppListItemController> {
|
||||||
|
|
||||||
|
private final Activity activity;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Cursor cursor;
|
||||||
|
|
||||||
|
InstalledAppListAdapter(Activity activity) {
|
||||||
|
this.activity = activity;
|
||||||
|
setHasStableIds(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
if (cursor == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.moveToPosition(position);
|
||||||
|
return cursor.getLong(cursor.getColumnIndex(Schema.AppMetadataTable.Cols.ROW_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InstalledAppListItemController onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View view = activity.getLayoutInflater().inflate(R.layout.installed_app_list_item, parent, false);
|
||||||
|
return new InstalledAppListItemController(activity, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(InstalledAppListItemController holder, int position) {
|
||||||
|
if (cursor == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.moveToPosition(position);
|
||||||
|
holder.bindModel(new App(cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return cursor == null ? 0 : cursor.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApps(@Nullable Cursor cursor) {
|
||||||
|
this.cursor = cursor;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package org.fdroid.fdroid.views.installed;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.AppUpdateStatusManager;
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
import org.fdroid.fdroid.data.AppPrefs;
|
||||||
|
import org.fdroid.fdroid.views.apps.AppListItemController;
|
||||||
|
import org.fdroid.fdroid.views.apps.AppListItemState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the currently installed version name, and whether or not it is the recommended version.
|
||||||
|
* Also shows whether the user has previously asked to ignore updates for this app entirely, or for
|
||||||
|
* a specific version of this app.
|
||||||
|
*/
|
||||||
|
public class InstalledAppListItemController extends AppListItemController {
|
||||||
|
public InstalledAppListItemController(Activity activity, View itemView) {
|
||||||
|
super(activity, itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AppListItemState getCurrentViewState(
|
||||||
|
@NonNull App app, @Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) {
|
||||||
|
return new AppListItemState(app)
|
||||||
|
.setStatusText(getInstalledVersion(app))
|
||||||
|
.setSecondaryStatusText(getIgnoreStatus(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Either "Version X" or "Version Y (Recommended)", depending on the installed version.
|
||||||
|
*/
|
||||||
|
private CharSequence getInstalledVersion(@NonNull App app) {
|
||||||
|
int statusStringRes = (app.suggestedVersionCode == app.installedVersionCode)
|
||||||
|
? R.string.app_recommended_version_installed
|
||||||
|
: R.string.app_version_x_installed;
|
||||||
|
|
||||||
|
return activity.getString(statusStringRes, app.installedVersionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show whether the user has ignored a specific version ("Updates ignored for Version X"), or
|
||||||
|
* all versions ("Updates ignored").
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private CharSequence getIgnoreStatus(@NonNull App app) {
|
||||||
|
AppPrefs prefs = app.getPrefs(activity);
|
||||||
|
if (prefs.ignoreAllUpdates) {
|
||||||
|
return activity.getString(R.string.installed_app__updates_ignored);
|
||||||
|
} else if (prefs.ignoreThisUpdate > 0 && prefs.ignoreThisUpdate == app.suggestedVersionCode) {
|
||||||
|
return activity.getString(
|
||||||
|
R.string.installed_app__updates_ignored_for_suggested_version,
|
||||||
|
app.getSuggestedVersionName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -19,10 +19,8 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid.views.installed;
|
package org.fdroid.fdroid.views.installed;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
@ -31,15 +29,12 @@ import android.support.v7.widget.LinearLayoutManager;
|
|||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.App;
|
|
||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
import org.fdroid.fdroid.data.Schema;
|
import org.fdroid.fdroid.data.Schema;
|
||||||
import org.fdroid.fdroid.views.apps.AppListItemController;
|
|
||||||
|
|
||||||
public class InstalledAppsActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
|
public class InstalledAppsActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
@ -105,52 +100,4 @@ public class InstalledAppsActivity extends AppCompatActivity implements LoaderMa
|
|||||||
adapter.setApps(null);
|
adapter.setApps(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class InstalledAppListAdapter extends RecyclerView.Adapter<AppListItemController> {
|
|
||||||
|
|
||||||
private final Activity activity;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private Cursor cursor;
|
|
||||||
|
|
||||||
InstalledAppListAdapter(Activity activity) {
|
|
||||||
this.activity = activity;
|
|
||||||
setHasStableIds(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
if (cursor == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor.moveToPosition(position);
|
|
||||||
return cursor.getLong(cursor.getColumnIndex(Schema.AppMetadataTable.Cols.ROW_ID));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AppListItemController onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
View view = activity.getLayoutInflater().inflate(R.layout.installed_app_list_item, parent, false);
|
|
||||||
return new AppListItemController(activity, view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(AppListItemController holder, int position) {
|
|
||||||
if (cursor == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor.moveToPosition(position);
|
|
||||||
holder.bindModel(new App(cursor));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return cursor == null ? 0 : cursor.getCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setApps(@Nullable Cursor cursor) {
|
|
||||||
this.cursor = cursor;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import com.hannesdorfmann.adapterdelegates3.AdapterDelegate;
|
|||||||
import org.fdroid.fdroid.AppUpdateStatusManager;
|
import org.fdroid.fdroid.AppUpdateStatusManager;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.views.apps.AppListItemController;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -16,8 +15,8 @@ import java.util.List;
|
|||||||
* Apps which we want to show some more substantial information about.
|
* Apps which we want to show some more substantial information about.
|
||||||
*
|
*
|
||||||
* @see R.layout#updateable_app_status_item The view that this binds to
|
* @see R.layout#updateable_app_status_item The view that this binds to
|
||||||
* @see AppListItemController Used for binding the {@link App} to the
|
* @see AppStatusListItemController Used for binding the {@link App} to the
|
||||||
* {@link R.layout#updateable_app_status_item}
|
* {@link R.layout#updateable_app_status_item}.
|
||||||
*/
|
*/
|
||||||
public class AppStatus extends AppUpdateData {
|
public class AppStatus extends AppUpdateData {
|
||||||
|
|
||||||
@ -44,7 +43,7 @@ public class AppStatus extends AppUpdateData {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
protected RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||||
return new AppListItemController(activity, activity.getLayoutInflater()
|
return new AppStatusListItemController(activity, activity.getLayoutInflater()
|
||||||
.inflate(R.layout.updateable_app_status_item, parent, false));
|
.inflate(R.layout.updateable_app_status_item, parent, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +51,7 @@ public class AppStatus extends AppUpdateData {
|
|||||||
protected void onBindViewHolder(@NonNull List<AppUpdateData> items, int position,
|
protected void onBindViewHolder(@NonNull List<AppUpdateData> items, int position,
|
||||||
@NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads) {
|
@NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads) {
|
||||||
AppStatus app = (AppStatus) items.get(position);
|
AppStatus app = (AppStatus) items.get(position);
|
||||||
((AppListItemController) holder).bindModel(app.status.app);
|
((AppStatusListItemController) holder).bindModel(app.status.app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package org.fdroid.fdroid.views.updates.items;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.AppUpdateStatusManager;
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
import org.fdroid.fdroid.views.apps.AppListItemController;
|
||||||
|
import org.fdroid.fdroid.views.apps.AppListItemState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows apps which are:
|
||||||
|
* * In the process of being downloaded.
|
||||||
|
* * Downloaded and ready to install.
|
||||||
|
* * Recently installed and ready to run.
|
||||||
|
*/
|
||||||
|
public class AppStatusListItemController extends AppListItemController {
|
||||||
|
public AppStatusListItemController(Activity activity, View itemView) {
|
||||||
|
super(activity, itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AppListItemState getCurrentViewState(
|
||||||
|
@NonNull App app, @Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) {
|
||||||
|
|
||||||
|
return super.getCurrentViewState(app, appStatus)
|
||||||
|
.setStatusText(getStatusText(appStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private CharSequence getStatusText(@Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) {
|
||||||
|
if (appStatus != null) {
|
||||||
|
switch (appStatus.status) {
|
||||||
|
case ReadyToInstall:
|
||||||
|
return activity.getString(R.string.app_list_download_ready);
|
||||||
|
|
||||||
|
case Installed:
|
||||||
|
return activity.getString(R.string.notification_content_single_installed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ import android.view.ViewGroup;
|
|||||||
import com.hannesdorfmann.adapterdelegates3.AdapterDelegate;
|
import com.hannesdorfmann.adapterdelegates3.AdapterDelegate;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.views.apps.AppListItemController;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -16,7 +15,8 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* @see UpdateableApp The data that is bound to this view.
|
* @see UpdateableApp The data that is bound to this view.
|
||||||
* @see R.layout#updateable_app_list_item The view that this binds to.
|
* @see R.layout#updateable_app_list_item The view that this binds to.
|
||||||
* @see AppListItemController Used for binding the {@link App} to the {@link R.layout#updateable_app_list_item}
|
* @see UpdateableAppListItemController Used for binding the {@link App} to
|
||||||
|
* the {@link R.layout#updateable_app_list_item}
|
||||||
*/
|
*/
|
||||||
public class UpdateableApp extends AppUpdateData {
|
public class UpdateableApp extends AppUpdateData {
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ public class UpdateableApp extends AppUpdateData {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
protected RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||||
return new AppListItemController(activity, activity.getLayoutInflater()
|
return new UpdateableAppListItemController(activity, activity.getLayoutInflater()
|
||||||
.inflate(R.layout.updateable_app_list_item, parent, false));
|
.inflate(R.layout.updateable_app_list_item, parent, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ public class UpdateableApp extends AppUpdateData {
|
|||||||
protected void onBindViewHolder(@NonNull List<AppUpdateData> items, int position,
|
protected void onBindViewHolder(@NonNull List<AppUpdateData> items, int position,
|
||||||
@NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads) {
|
@NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads) {
|
||||||
UpdateableApp app = (UpdateableApp) items.get(position);
|
UpdateableApp app = (UpdateableApp) items.get(position);
|
||||||
((AppListItemController) holder).bindModel(app.app);
|
((UpdateableAppListItemController) holder).bindModel(app.app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.fdroid.fdroid.views.updates.items;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.AppUpdateStatusManager;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
import org.fdroid.fdroid.views.apps.AppListItemController;
|
||||||
|
import org.fdroid.fdroid.views.apps.AppListItemState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very trimmed down list item. Only displays the app icon, name, and a download button.
|
||||||
|
* We don't even need to show download progress, because the intention is that as soon as
|
||||||
|
* we have started downloading the app, it is removed from the list (and replaced with an
|
||||||
|
* {@link AppStatusListItemController}.
|
||||||
|
*/
|
||||||
|
public class UpdateableAppListItemController extends AppListItemController {
|
||||||
|
public UpdateableAppListItemController(Activity activity, View itemView) {
|
||||||
|
super(activity, itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AppListItemState getCurrentViewState(
|
||||||
|
@NonNull App app, @Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) {
|
||||||
|
return new AppListItemState(app)
|
||||||
|
.setShowInstallButton(true);
|
||||||
|
}
|
||||||
|
}
|
@ -46,12 +46,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
tools:text="Installed"
|
tools:text="Installed"
|
||||||
android:textStyle="italic"
|
style="@style/AppListItemStatusText"
|
||||||
android:textSize="14sp"
|
|
||||||
android:textColor="#424242"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:fontFamily="sans-serif-light"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/app_name"
|
app:layout_constraintTop_toBottomOf="@+id/app_name"
|
||||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
|
@ -44,24 +44,20 @@
|
|||||||
android:layout_marginRight="8dp" />
|
android:layout_marginRight="8dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/installed_version"
|
android:id="@+id/status"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
tools:text="Version 4.7.3 (recommended)"
|
tools:text="Version 4.7.3 (recommended)"
|
||||||
android:textStyle="italic"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textColor="?attr/installedApps"
|
android:textColor="?attr/installedApps"
|
||||||
android:maxLines="1"
|
style="@style/AppListItemStatusText"
|
||||||
android:ellipsize="end"
|
|
||||||
android:fontFamily="sans-serif-light"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/app_name"
|
app:layout_constraintTop_toBottomOf="@+id/app_name"
|
||||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginLeft="8dp" />
|
android:layout_marginLeft="8dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/ignored_status"
|
android:id="@+id/secondary_status"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
@ -69,7 +65,7 @@
|
|||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/installed_version"
|
app:layout_constraintTop_toBottomOf="@+id/status"
|
||||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginLeft="8dp" />
|
android:layout_marginLeft="8dp" />
|
||||||
|
@ -55,22 +55,8 @@
|
|||||||
android:layout_marginRight="16dp"
|
android:layout_marginRight="16dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/action_button"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@+id/icon"
|
app:layout_constraintTop_toTopOf="@+id/icon"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/icon" />
|
app:layout_constraintBottom_toBottomOf="@+id/icon" />
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/action_button"
|
|
||||||
style="@style/DetailsPrimaryButtonStyleSmall"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="Update" />
|
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
@ -36,20 +36,18 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:layout_constraintVertical_bias="0.333" />
|
app:layout_constraintVertical_bias="0.333" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/download_ready"
|
android:id="@+id/status"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/app_list_download_ready"
|
tools:text="@string/app_list_download_ready"
|
||||||
android:textSize="14sp"
|
style="@style/AppListItemStatusText"
|
||||||
android:textColor="?attr/lightGrayTextColor"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:fontFamily="sans-serif-light"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/app_name"
|
app:layout_constraintTop_toBottomOf="@+id/app_name"
|
||||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
|
@ -6,4 +6,7 @@
|
|||||||
</style>
|
</style>
|
||||||
<style name="BodyText" parent="BodyTextV16" />
|
<style name="BodyText" parent="BodyTextV16" />
|
||||||
|
|
||||||
|
<style name="AppListItemStatusText" parent="AppListItemStatusTextBase">
|
||||||
|
<item name="android:fontFamily">sans-serif-light</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -121,6 +121,17 @@
|
|||||||
<item name="android:textColor">?attr/lightGrayTextColor</item>
|
<item name="android:textColor">?attr/lightGrayTextColor</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- Used for supplementary information to show below an app name in app lists, such as
|
||||||
|
whether it is incompatible, what version is installed, etc -->
|
||||||
|
<style name="AppListItemStatusTextBase">
|
||||||
|
<item name="android:textStyle">italic</item>
|
||||||
|
<item name="android:textSize">14sp</item>
|
||||||
|
<item name="android:textColor">?attr/lightGrayTextColor</item>
|
||||||
|
<item name="android:maxLines">1</item>
|
||||||
|
<item name="android:ellipsize">end</item>
|
||||||
|
</style>
|
||||||
|
<style name="AppListItemStatusText" parent="AppListItemStatusTextBase" />
|
||||||
|
|
||||||
<style name="SwapTheme.Wizard" parent="Theme.AppCompat.Light.NoActionBar">
|
<style name="SwapTheme.Wizard" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
<item name="colorButtonNormal">@color/swap_bright_blue</item>
|
<item name="colorButtonNormal">@color/swap_bright_blue</item>
|
||||||
<item name="actionButtonStyle">@style/SwapTheme.Wizard.ActionButton</item>
|
<item name="actionButtonStyle">@style/SwapTheme.Wizard.ActionButton</item>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user