Merge branch 'new-ui--categories-imagery' into 'master'
Categories artwork Closes #851 See merge request !448
@ -10,6 +10,7 @@ import android.content.Intent;
|
|||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.design.widget.CoordinatorLayout;
|
import android.support.design.widget.CoordinatorLayout;
|
||||||
@ -108,7 +109,7 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
|||||||
new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() {
|
new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onGenerated(Palette palette) {
|
public void onGenerated(Palette palette) {
|
||||||
featureImage.setPalette(palette);
|
featureImage.setColour(palette.getDominantColor(Color.LTGRAY));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ import org.fdroid.fdroid.data.AppProvider;
|
|||||||
import org.fdroid.fdroid.data.InstalledAppProviderService;
|
import org.fdroid.fdroid.data.InstalledAppProviderService;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.installer.InstallHistoryService;
|
import org.fdroid.fdroid.installer.InstallHistoryService;
|
||||||
import org.fdroid.fdroid.net.IconDownloader;
|
import org.fdroid.fdroid.net.ImageLoaderForUIL;
|
||||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -280,7 +280,7 @@ public class FDroidApp extends Application {
|
|||||||
bluetoothAdapter = getBluetoothAdapter();
|
bluetoothAdapter = getBluetoothAdapter();
|
||||||
|
|
||||||
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
|
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
|
||||||
.imageDownloader(new IconDownloader(getApplicationContext()))
|
.imageDownloader(new ImageLoaderForUIL(getApplicationContext()))
|
||||||
.diskCache(new LimitedAgeDiskCache(
|
.diskCache(new LimitedAgeDiskCache(
|
||||||
Utils.getIconsCacheDir(this),
|
Utils.getIconsCacheDir(this),
|
||||||
null,
|
null,
|
||||||
|
@ -11,6 +11,8 @@ import android.support.annotation.NonNull;
|
|||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.Schema.CatJoinTable;
|
import org.fdroid.fdroid.data.Schema.CatJoinTable;
|
||||||
import org.fdroid.fdroid.data.Schema.CategoryTable;
|
import org.fdroid.fdroid.data.Schema.CategoryTable;
|
||||||
|
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
|
||||||
|
import org.fdroid.fdroid.data.Schema.PackageTable;
|
||||||
import org.fdroid.fdroid.data.Schema.CategoryTable.Cols;
|
import org.fdroid.fdroid.data.Schema.CategoryTable.Cols;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -96,13 +98,9 @@ public class CategoryProvider extends FDroidProvider {
|
|||||||
|
|
||||||
private class Query extends QueryBuilder {
|
private class Query extends QueryBuilder {
|
||||||
|
|
||||||
private boolean onlyCategoriesWithApps;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getRequiredTables() {
|
protected String getRequiredTables() {
|
||||||
String joinType = onlyCategoriesWithApps ? " JOIN " : " LEFT JOIN ";
|
return CategoryTable.NAME + " LEFT JOIN " + CatJoinTable.NAME + " ON (" +
|
||||||
|
|
||||||
return CategoryTable.NAME + joinType + CatJoinTable.NAME + " ON (" +
|
|
||||||
CatJoinTable.Cols.CATEGORY_ID + " = " + CategoryTable.NAME + "." + Cols.ROW_ID + ") ";
|
CatJoinTable.Cols.CATEGORY_ID + " = " + CategoryTable.NAME + "." + Cols.ROW_ID + ") ";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,8 +114,11 @@ public class CategoryProvider extends FDroidProvider {
|
|||||||
return CategoryTable.NAME + "." + Cols.ROW_ID;
|
return CategoryTable.NAME + "." + Cols.ROW_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnlyCategoriesWithApps(boolean onlyCategoriesWithApps) {
|
public void setOnlyCategoriesWithApps() {
|
||||||
this.onlyCategoriesWithApps = onlyCategoriesWithApps;
|
// Make sure that metadata from the preferred repository is used to determine if
|
||||||
|
// there is an app present or not.
|
||||||
|
join(AppMetadataTable.NAME, "app", "app." + AppMetadataTable.Cols.ROW_ID + " = " + CatJoinTable.NAME + "." + CatJoinTable.Cols.APP_METADATA_ID);
|
||||||
|
join(PackageTable.NAME, "pkg", "pkg." + PackageTable.Cols.PREFERRED_METADATA + " = " + "app." + AppMetadataTable.Cols.ROW_ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +219,10 @@ public class CategoryProvider extends FDroidProvider {
|
|||||||
query.addSelection(selection);
|
query.addSelection(selection);
|
||||||
query.addFields(projection);
|
query.addFields(projection);
|
||||||
query.addOrderBy(sortOrder);
|
query.addOrderBy(sortOrder);
|
||||||
query.setOnlyCategoriesWithApps(onlyCategoriesWithApps);
|
|
||||||
|
if (onlyCategoriesWithApps) {
|
||||||
|
query.setOnlyCategoriesWithApps();
|
||||||
|
}
|
||||||
|
|
||||||
Cursor cursor = LoggingQuery.query(db(), query.toString(), query.getArgs());
|
Cursor cursor = LoggingQuery.query(db(), query.toString(), query.getArgs());
|
||||||
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||||
|
@ -2,16 +2,20 @@ package org.fdroid.fdroid.net;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.download.ImageDownloader;
|
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class IconDownloader implements ImageDownloader {
|
/**
|
||||||
|
* Class used by the Universal Image Loader library (UIL) to fetch images for displaying in F-Droid.
|
||||||
|
* See {@link org.fdroid.fdroid.FDroidApp} for where this gets configured.
|
||||||
|
*/
|
||||||
|
public class ImageLoaderForUIL implements com.nostra13.universalimageloader.core.download.ImageDownloader {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
public IconDownloader(Context context) {
|
public ImageLoaderForUIL(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,8 +24,13 @@ public class IconDownloader implements ImageDownloader {
|
|||||||
switch (Scheme.ofUri(imageUri)) {
|
switch (Scheme.ofUri(imageUri)) {
|
||||||
case ASSETS:
|
case ASSETS:
|
||||||
return context.getAssets().open(Scheme.ASSETS.crop(imageUri));
|
return context.getAssets().open(Scheme.ASSETS.crop(imageUri));
|
||||||
|
|
||||||
|
case DRAWABLE:
|
||||||
|
return new BaseImageDownloader(context).getStream(imageUri, extra);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return DownloaderFactory.create(context, imageUri).getInputStream();
|
return DownloaderFactory.create(context, imageUri).getInputStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -107,7 +107,7 @@ public class CategorySpan extends ReplacementSpan {
|
|||||||
|
|
||||||
// The background which goes behind the text.
|
// The background which goes behind the text.
|
||||||
Paint backgroundPaint = new Paint();
|
Paint backgroundPaint = new Paint();
|
||||||
backgroundPaint.setColor(CategoryController.getBackgroundColour(categoryName.toString()));
|
backgroundPaint.setColor(CategoryController.getBackgroundColour(context, categoryName.toString()));
|
||||||
backgroundPaint.setAntiAlias(true);
|
backgroundPaint.setAntiAlias(true);
|
||||||
canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint);
|
canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint);
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ import android.graphics.Color;
|
|||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.support.annotation.ColorInt;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.graphics.Palette;
|
import android.support.v7.graphics.Palette;
|
||||||
import android.support.v7.widget.AppCompatImageView;
|
import android.support.v7.widget.AppCompatImageView;
|
||||||
@ -71,8 +73,8 @@ public class FeatureImage extends AppCompatImageView {
|
|||||||
* then creates a second variation that is slightly dimmer still. These two colours are then
|
* then creates a second variation that is slightly dimmer still. These two colours are then
|
||||||
* randomly allocated to each triangle which is expected to be rendered.
|
* randomly allocated to each triangle which is expected to be rendered.
|
||||||
*/
|
*/
|
||||||
public void setPalette(@Nullable Palette palette) {
|
public void setColour(@ColorInt int colour) {
|
||||||
if (palette == null) {
|
if (colour == 0) {
|
||||||
trianglePaints = null;
|
trianglePaints = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -80,7 +82,7 @@ public class FeatureImage extends AppCompatImageView {
|
|||||||
// It is easier to dull al colour in the HSV space, so convert to that then adjust the
|
// It is easier to dull al colour in the HSV space, so convert to that then adjust the
|
||||||
// saturation down and the colour value down.
|
// saturation down and the colour value down.
|
||||||
float[] hsv = new float[3];
|
float[] hsv = new float[3];
|
||||||
Color.colorToHSV(palette.getDominantColor(Color.LTGRAY), hsv);
|
Color.colorToHSV(colour, hsv);
|
||||||
hsv[1] *= 0.5f;
|
hsv[1] *= 0.5f;
|
||||||
hsv[2] *= 0.7f;
|
hsv[2] *= 0.7f;
|
||||||
int colourOne = Color.HSVToColor(hsv);
|
int colourOne = Color.HSVToColor(hsv);
|
||||||
@ -187,9 +189,8 @@ public class FeatureImage extends AppCompatImageView {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* First try to draw whatever image was given to this view. If that doesn't exist, try to draw
|
* First try to draw whatever image was given to this view. If that doesn't exist, try to draw
|
||||||
* a geometric pattern based on the palette that was given to us. If we haven't had a palette
|
* a geometric pattern based on the palette that was given to us. If we haven't had a colour
|
||||||
* assigned to us (using {@link FeatureImage#setPalette(Palette)}) then clear the
|
* assigned to us (using {@link #setColour(int)}) then clear the view by filling it with white.
|
||||||
* view by filling it with white.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
@ -200,12 +201,11 @@ public class FeatureImage extends AppCompatImageView {
|
|||||||
paint.setAlpha(currentAlpha);
|
paint.setAlpha(currentAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.drawRect(0, 0, getWidth(), getHeight(), WHITE_PAINT);
|
|
||||||
for (int i = 0; i < triangles.length; i++) {
|
for (int i = 0; i < triangles.length; i++) {
|
||||||
canvas.drawPath(triangles[i], trianglePaints[i]);
|
canvas.drawPath(triangles[i], trianglePaints[i]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
canvas.drawRect(0, 0, getWidth(), getHeight(), WHITE_PAINT);
|
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package org.fdroid.fdroid.views.categories;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.IdRes;
|
import android.support.annotation.IdRes;
|
||||||
@ -111,7 +112,7 @@ public class AppCardController extends RecyclerView.ViewHolder implements ImageL
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (featuredImage != null) {
|
if (featuredImage != null) {
|
||||||
featuredImage.setPalette(null);
|
featuredImage.setColour(0);
|
||||||
featuredImage.setImageDrawable(null);
|
featuredImage.setImageDrawable(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ public class AppCardController extends RecyclerView.ViewHolder implements ImageL
|
|||||||
new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() {
|
new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onGenerated(Palette palette) {
|
public void onGenerated(Palette palette) {
|
||||||
featuredImage.setPalette(palette);
|
featuredImage.setColour(palette.getDominantColor(Color.LTGRAY));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,14 @@ import android.app.Activity;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.ColorInt;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.support.v4.view.ViewCompat;
|
import android.support.v4.view.ViewCompat;
|
||||||
@ -18,21 +21,29 @@ import android.widget.Button;
|
|||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
|
||||||
|
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
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.AppListActivity;
|
import org.fdroid.fdroid.views.apps.AppListActivity;
|
||||||
|
import org.fdroid.fdroid.views.apps.FeatureImage;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class CategoryController extends RecyclerView.ViewHolder implements LoaderManager.LoaderCallbacks<Cursor> {
|
public class CategoryController extends RecyclerView.ViewHolder implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
private final Button viewAll;
|
private final Button viewAll;
|
||||||
private final TextView heading;
|
private final TextView heading;
|
||||||
|
private final FeatureImage image;
|
||||||
private final AppPreviewAdapter appCardsAdapter;
|
private final AppPreviewAdapter appCardsAdapter;
|
||||||
private final FrameLayout background;
|
private final FrameLayout background;
|
||||||
|
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
private final LoaderManager loaderManager;
|
private final LoaderManager loaderManager;
|
||||||
|
private final DisplayImageOptions displayImageOptions;
|
||||||
|
|
||||||
private String currentCategory;
|
private String currentCategory;
|
||||||
|
|
||||||
@ -48,25 +59,66 @@ public class CategoryController extends RecyclerView.ViewHolder implements Loade
|
|||||||
viewAll.setOnClickListener(onViewAll);
|
viewAll.setOnClickListener(onViewAll);
|
||||||
|
|
||||||
heading = (TextView) itemView.findViewById(R.id.name);
|
heading = (TextView) itemView.findViewById(R.id.name);
|
||||||
|
image = (FeatureImage) itemView.findViewById(R.id.category_image);
|
||||||
background = (FrameLayout) itemView.findViewById(R.id.category_background);
|
background = (FrameLayout) itemView.findViewById(R.id.category_background);
|
||||||
|
|
||||||
RecyclerView appCards = (RecyclerView) itemView.findViewById(R.id.app_cards);
|
RecyclerView appCards = (RecyclerView) itemView.findViewById(R.id.app_cards);
|
||||||
appCards.setAdapter(appCardsAdapter);
|
appCards.setAdapter(appCardsAdapter);
|
||||||
appCards.addItemDecoration(new ItemDecorator(activity));
|
appCards.addItemDecoration(new ItemDecorator(activity));
|
||||||
|
|
||||||
|
displayImageOptions = new DisplayImageOptions.Builder()
|
||||||
|
.cacheInMemory(true)
|
||||||
|
.imageScaleType(ImageScaleType.NONE)
|
||||||
|
.displayer(new FadeInBitmapDisplayer(100, true, true, false))
|
||||||
|
.bitmapConfig(Bitmap.Config.RGB_565)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
void bindModel(@NonNull String categoryName) {
|
void bindModel(@NonNull String categoryName) {
|
||||||
currentCategory = categoryName;
|
currentCategory = categoryName;
|
||||||
heading.setText(categoryName);
|
|
||||||
|
int categoryNameId = getCategoryResource(activity, categoryName, "string", false);
|
||||||
|
String translatedName = categoryNameId == 0 ? categoryName : activity.getString(categoryNameId);
|
||||||
|
heading.setText(translatedName);
|
||||||
|
|
||||||
viewAll.setVisibility(View.INVISIBLE);
|
viewAll.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
loaderManager.initLoader(currentCategory.hashCode(), null, this);
|
loaderManager.initLoader(currentCategory.hashCode(), null, this);
|
||||||
loaderManager.initLoader(currentCategory.hashCode() + 1, null, this);
|
loaderManager.initLoader(currentCategory.hashCode() + 1, null, this);
|
||||||
|
|
||||||
background.setBackgroundColor(getBackgroundColour(categoryName));
|
@ColorInt int backgroundColour = getBackgroundColour(activity, categoryName);
|
||||||
|
background.setBackgroundColor(backgroundColour);
|
||||||
|
|
||||||
|
int categoryImageId = getCategoryResource(activity, categoryName, "drawable", true);
|
||||||
|
if (categoryImageId == 0) {
|
||||||
|
image.setColour(backgroundColour);
|
||||||
|
image.setImageDrawable(null);
|
||||||
|
} else {
|
||||||
|
image.setColour(0);
|
||||||
|
ImageLoader.getInstance().displayImage("drawable://" + categoryImageId, image, displayImageOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param requiresLowerCaseId Previously categories were translated using strings such as "category_Reading" for
|
||||||
|
* the "Reading" category. Now we also need to have drawable resources such as
|
||||||
|
* "category_reading". Note how drawables must have only lower case letters, whereas
|
||||||
|
* we already have upper case letters in strings.xml. Hence this flag.
|
||||||
|
*/
|
||||||
|
private static int getCategoryResource(Context context, @NonNull String categoryName, String resourceType, boolean requiresLowerCaseId) {
|
||||||
|
String suffix = categoryName.replace(" & ", "_").replace(" ", "_").replace("'", "");
|
||||||
|
if (requiresLowerCaseId) {
|
||||||
|
suffix = suffix.toLowerCase();
|
||||||
|
}
|
||||||
|
return context.getResources().getIdentifier("category_" + suffix, resourceType, context.getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getBackgroundColour(Context context, @NonNull String categoryName) {
|
||||||
|
int colourId = getCategoryResource(context, categoryName, "color", false);
|
||||||
|
if (colourId > 0) {
|
||||||
|
return ContextCompat.getColor(context, colourId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getBackgroundColour(@NonNull String categoryName) {
|
|
||||||
// Seed based on the categoryName, so that each time we try to choose a colour for the same
|
// Seed based on the categoryName, so that each time we try to choose a colour for the same
|
||||||
// category it will look the same for each different user, and each different session.
|
// category it will look the same for each different user, and each different session.
|
||||||
Random random = new Random(categoryName.toLowerCase().hashCode());
|
Random random = new Random(categoryName.toLowerCase().hashCode());
|
||||||
@ -130,6 +182,7 @@ public class CategoryController extends RecyclerView.ViewHolder implements Loade
|
|||||||
appCardsAdapter.setAppCursor(null);
|
appCardsAdapter.setAppCursor(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
private final View.OnClickListener onViewAll = new View.OnClickListener() {
|
private final View.OnClickListener onViewAll = new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
BIN
app/src/main/res/drawable/category_connectivity.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
app/src/main/res/drawable/category_development.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
app/src/main/res/drawable/category_graphics.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
app/src/main/res/drawable/category_internet.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
app/src/main/res/drawable/category_money.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
app/src/main/res/drawable/category_navigation.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
app/src/main/res/drawable/category_reading.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
app/src/main/res/drawable/category_science_education.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
app/src/main/res/drawable/category_security.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
app/src/main/res/drawable/category_system.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/drawable/category_theming.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
app/src/main/res/drawable/category_writing.png
Normal file
After Width: | Height: | Size: 30 KiB |
@ -29,7 +29,7 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:background="@android:color/transparent"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:paddingLeft="18dp"
|
android:paddingLeft="18dp"
|
||||||
android:paddingStart="18dp"
|
android:paddingStart="18dp"
|
||||||
android:paddingRight="18dp"
|
android:paddingRight="18dp"
|
||||||
@ -52,12 +52,17 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
tools:background="#ffffbbbb" />
|
tools:background="#ffffbbbb" />
|
||||||
|
|
||||||
<ImageView
|
<org.fdroid.fdroid.views.apps.FeatureImage
|
||||||
android:id="@+id/category_image"
|
android:id="@+id/category_image"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="@+id/category_background"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/button"
|
app:layout_constraintEnd_toEndOf="@+id/category_background"
|
||||||
android:layout_width="100dp"
|
app:layout_constraintTop_toTopOf="@+id/category_background"
|
||||||
android:layout_height="100dp"
|
app:layout_constraintBottom_toBottomOf="@+id/category_background"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
tools:src="@drawable/category_graphics"
|
||||||
|
android:scaleType="fitStart"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<android.support.v7.widget.RecyclerView
|
||||||
@ -76,6 +81,8 @@
|
|||||||
android:paddingBottom="@dimen/category_preview__app_list__padding__vertical"
|
android:paddingBottom="@dimen/category_preview__app_list__padding__vertical"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
android:layout_marginRight="8dp"/>
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp" />
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
@ -5,6 +5,24 @@
|
|||||||
<color name="unverified">#ff999999</color>
|
<color name="unverified">#ff999999</color>
|
||||||
<color name="red">#ffdd2c00</color>
|
<color name="red">#ffdd2c00</color>
|
||||||
|
|
||||||
|
<color name="category_connectivity">#CBEFEC</color>
|
||||||
|
<color name="category_development">#E2D6BC</color>
|
||||||
|
<color name="category_games">#6D6862</color>
|
||||||
|
<color name="category_graphics">#F25050</color>
|
||||||
|
<color name="category_internet">#DBDDC9</color>
|
||||||
|
<color name="category_money">#DDDDD0</color>
|
||||||
|
<color name="category_multimedia">#FF7F66</color>
|
||||||
|
<color name="category_navigation">#94D6FD</color>
|
||||||
|
<color name="category_phone_sms">#F3CFC0</color>
|
||||||
|
<color name="category_reading">#D6A07A</color>
|
||||||
|
<color name="category_science_education">#F4F4EC</color>
|
||||||
|
<color name="category_security">#6D6862</color>
|
||||||
|
<color name="category_sports_health">#72C7EA</color>
|
||||||
|
<color name="category_system">#D3DB77</color>
|
||||||
|
<color name="category_theming">#DEEFE9</color>
|
||||||
|
<color name="category_time">#FF7043</color>
|
||||||
|
<color name="category_writing">#F2E9CE</color>
|
||||||
|
|
||||||
<color name="fdroid_blue">#ff1976d2</color>
|
<color name="fdroid_blue">#ff1976d2</color>
|
||||||
<color name="fdroid_blue_dark">#ff0d47a1</color>
|
<color name="fdroid_blue_dark">#ff0d47a1</color>
|
||||||
<color name="fdroid_blue_darkest">#ff042570</color>
|
<color name="fdroid_blue_darkest">#ff042570</color>
|
||||||
|
@ -30,12 +30,49 @@ public class CategoryProviderTest extends FDroidProviderTest {
|
|||||||
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
|
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================================================
|
/**
|
||||||
// "Categories"
|
* Different repositories can specify a different set of categories for the same package.
|
||||||
// (at this point) not an additional table, but we treat them sort of
|
* In this case, only the repository with the highest priority should get to choose which
|
||||||
// like they are. That means that if we change the implementation to
|
* category the app goes in.
|
||||||
// use a separate table in the future, these should still pass.
|
*/
|
||||||
// ========================================================================
|
@Test
|
||||||
|
public void onlyHighestPriorityMetadataDefinesCategories() {
|
||||||
|
long mainRepo = 1;
|
||||||
|
long gpRepo = 3;
|
||||||
|
|
||||||
|
insertAppWithCategory("info.guardianproject.notepadbot", "NoteCipher", "Writing,Security", mainRepo);
|
||||||
|
insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable", mainRepo);
|
||||||
|
insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable", mainRepo);
|
||||||
|
|
||||||
|
List<String> categories = CategoryProvider.Helper.categories(context);
|
||||||
|
String[] expected = new String[] {
|
||||||
|
context.getResources().getString(R.string.category_Whats_New),
|
||||||
|
context.getResources().getString(R.string.category_Recently_Updated),
|
||||||
|
context.getResources().getString(R.string.category_All),
|
||||||
|
|
||||||
|
"Animal",
|
||||||
|
"Mineral",
|
||||||
|
"Security",
|
||||||
|
"Vegetable",
|
||||||
|
"Writing",
|
||||||
|
};
|
||||||
|
assertContainsOnly(categories, expected);
|
||||||
|
|
||||||
|
insertAppWithCategory("info.guardianproject.notepadbot", "NoteCipher", "Office,GuardianProject", gpRepo);
|
||||||
|
assertContainsOnly(CategoryProvider.Helper.categories(context), expected);
|
||||||
|
|
||||||
|
RepoProvider.Helper.purgeApps(context, new MockRepo(mainRepo));
|
||||||
|
String[] expectedGp = new String[] {
|
||||||
|
context.getResources().getString(R.string.category_Whats_New),
|
||||||
|
context.getResources().getString(R.string.category_Recently_Updated),
|
||||||
|
context.getResources().getString(R.string.category_All),
|
||||||
|
|
||||||
|
"GuardianProject",
|
||||||
|
"Office",
|
||||||
|
};
|
||||||
|
List<String> categoriesAfterPurge = CategoryProvider.Helper.categories(context);
|
||||||
|
assertContainsOnly(categoriesAfterPurge, expectedGp);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void queryFreeTextAndCategories() {
|
public void queryFreeTextAndCategories() {
|
||||||
|