Merge branch 'new-ui--categories-imagery' into 'master'

Categories artwork

Closes #851

See merge request !448
This commit is contained in:
Peter Serwylo 2017-03-21 21:53:49 +00:00
commit c1cf153852
23 changed files with 173 additions and 43 deletions

View File

@ -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));
} }
}); });
} }

View File

@ -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,

View File

@ -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);

View File

@ -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();
} }
} }
} }

View File

@ -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);

View File

@ -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);
} }
} }

View File

@ -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));
} }
}); });
} }

View File

@ -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) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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() {