prefer complete, localized apps in the category overview cards
To further the goal of providing a fully localized experience based on the user's Language Settings, this applies similar logic as the Latest Tab to the apps that are featured for each category.
This commit is contained in:
parent
a0c809f0ad
commit
22cffbb0d8
@ -101,7 +101,7 @@ public final class Utils {
|
|||||||
|
|
||||||
// The date format used for storing dates (e.g. lastupdated, added) in the
|
// The date format used for storing dates (e.g. lastupdated, added) in the
|
||||||
// database.
|
// database.
|
||||||
private static final SimpleDateFormat DATE_FORMAT =
|
public static final SimpleDateFormat DATE_FORMAT =
|
||||||
new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
|
new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
|
||||||
|
|
||||||
private static final SimpleDateFormat TIME_FORMAT =
|
private static final SimpleDateFormat TIME_FORMAT =
|
||||||
|
@ -506,6 +506,9 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return Uri.withAppendedPath(getContentUri(), PATH_CALC_SUGGESTED_APKS);
|
return Uri.withAppendedPath(getContentUri(), PATH_CALC_SUGGESTED_APKS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all {@link App} entries in the given {@code category}
|
||||||
|
*/
|
||||||
public static Uri getCategoryUri(String category) {
|
public static Uri getCategoryUri(String category) {
|
||||||
return getContentUri().buildUpon()
|
return getContentUri().buildUpon()
|
||||||
.appendPath(PATH_CATEGORY)
|
.appendPath(PATH_CATEGORY)
|
||||||
@ -519,6 +522,13 @@ public class AppProvider extends FDroidProvider {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the top {@link App} entries in the given {@code category} to display
|
||||||
|
* in the overview screen in {@link org.fdroid.fdroid.views.categories.CategoryController}.
|
||||||
|
* The number of entries is defined by {@code limit}.
|
||||||
|
*
|
||||||
|
* @see org.fdroid.fdroid.views.categories.CategoryController#onCreateLoader(int, android.os.Bundle)
|
||||||
|
*/
|
||||||
public static Uri getTopFromCategoryUri(String category, int limit) {
|
public static Uri getTopFromCategoryUri(String category, int limit) {
|
||||||
return getContentUri().buildUpon()
|
return getContentUri().buildUpon()
|
||||||
.appendPath(PATH_TOP_FROM_CATEGORY)
|
.appendPath(PATH_TOP_FROM_CATEGORY)
|
||||||
@ -842,7 +852,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
case TOP_FROM_CATEGORY:
|
case TOP_FROM_CATEGORY:
|
||||||
selection = selection.add(queryCategory(pathSegments.get(2)));
|
selection = selection.add(queryCategory(pathSegments.get(2)));
|
||||||
limit = Integer.parseInt(pathSegments.get(1));
|
limit = Integer.parseInt(pathSegments.get(1));
|
||||||
sortOrder = getTableName() + "." + Cols.LAST_UPDATED + " DESC";
|
|
||||||
includeSwap = false;
|
includeSwap = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -881,6 +890,12 @@ public class AppProvider extends FDroidProvider {
|
|||||||
/**
|
/**
|
||||||
* Helper method used by both the genuine {@link AppProvider} and the temporary version used
|
* Helper method used by both the genuine {@link AppProvider} and the temporary version used
|
||||||
* by the repo updater ({@link TempAppProvider}).
|
* by the repo updater ({@link TempAppProvider}).
|
||||||
|
* <p>
|
||||||
|
* Query the database table specified by {@code uri}, which is usually (always?)
|
||||||
|
* {@link AppMetadataTable} with specified {@code selection} and {@code sortOrder}.
|
||||||
|
* <b>WARNING:</b> This contains a hack if {@code sortOrder} is equal to {@link Cols#NAME},
|
||||||
|
* i.e. not a complete table.column name, but just that single column name. In that case,
|
||||||
|
* a {@code sortOrder} is built out into a {@code sortOrder} that includes localized sorting.
|
||||||
*/
|
*/
|
||||||
protected Cursor runQuery(Uri uri, AppQuerySelection selection, String[] projection, boolean includeSwap, String sortOrder, int limit) {
|
protected Cursor runQuery(Uri uri, AppQuerySelection selection, String[] projection, boolean includeSwap, String sortOrder, int limit) {
|
||||||
if (!includeSwap) {
|
if (!includeSwap) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.fdroid.fdroid.views.categories;
|
package org.fdroid.fdroid.views.categories;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
@ -8,18 +7,19 @@ import android.database.Cursor;
|
|||||||
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 androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.loader.content.CursorLoader;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.core.view.ViewCompat;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
import androidx.loader.content.CursorLoader;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
||||||
@ -27,6 +27,7 @@ import org.fdroid.fdroid.R;
|
|||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
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.data.Schema.AppMetadataTable.Cols;
|
||||||
import org.fdroid.fdroid.views.apps.AppListActivity;
|
import org.fdroid.fdroid.views.apps.AppListActivity;
|
||||||
import org.fdroid.fdroid.views.apps.FeatureImage;
|
import org.fdroid.fdroid.views.apps.FeatureImage;
|
||||||
|
|
||||||
@ -133,9 +134,24 @@ public class CategoryController extends RecyclerView.ViewHolder implements Loade
|
|||||||
return Color.HSVToColor(hsv);
|
return Color.HSVToColor(hsv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return either the total apps in the category, or the entries to display
|
||||||
|
* for a category, depending on the value of {@code id}. This uses a sort
|
||||||
|
* similar to the one in {@link org.fdroid.fdroid.views.main.LatestViewBinder#onCreateLoader(int, Bundle)}.
|
||||||
|
* The difference is that this does not treat "new" app any differently.
|
||||||
|
*
|
||||||
|
* @see AppProvider#getCategoryUri(String)
|
||||||
|
* @see AppProvider#getTopFromCategoryUri(String, int)
|
||||||
|
* @see AppProvider#query(android.net.Uri, String[], String, String[], String)
|
||||||
|
* @see AppProvider#TOP_FROM_CATEGORY
|
||||||
|
* @see org.fdroid.fdroid.views.main.LatestViewBinder#onCreateLoader(int, Bundle)
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
final String table = Schema.AppMetadataTable.NAME;
|
||||||
|
final String added = table + "." + Cols.ADDED;
|
||||||
|
final String lastUpdated = table + "." + Cols.LAST_UPDATED;
|
||||||
if (id == currentCategory.hashCode() + 1) {
|
if (id == currentCategory.hashCode() + 1) {
|
||||||
return new CursorLoader(
|
return new CursorLoader(
|
||||||
activity,
|
activity,
|
||||||
@ -159,7 +175,22 @@ public class CategoryController extends RecyclerView.ViewHolder implements Loade
|
|||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
Schema.AppMetadataTable.Cols.NAME
|
table + "." + Cols.IS_LOCALIZED + " DESC"
|
||||||
|
+ ", " + table + "." + Cols.NAME + " IS NULL ASC"
|
||||||
|
+ ", " + table + "." + Cols.ICON + " IS NULL ASC"
|
||||||
|
+ ", " + table + "." + Cols.SUMMARY + " IS NULL ASC"
|
||||||
|
+ ", " + table + "." + Cols.DESCRIPTION + " IS NULL ASC"
|
||||||
|
+ ", CASE WHEN " + table + "." + Cols.PHONE_SCREENSHOTS + " IS NULL"
|
||||||
|
+ " AND " + table + "." + Cols.SEVEN_INCH_SCREENSHOTS + " IS NULL"
|
||||||
|
+ " AND " + table + "." + Cols.TEN_INCH_SCREENSHOTS + " IS NULL"
|
||||||
|
+ " AND " + table + "." + Cols.TV_SCREENSHOTS + " IS NULL"
|
||||||
|
+ " AND " + table + "." + Cols.WEAR_SCREENSHOTS + " IS NULL"
|
||||||
|
+ " AND " + table + "." + Cols.FEATURE_GRAPHIC + " IS NULL"
|
||||||
|
+ " AND " + table + "." + Cols.PROMO_GRAPHIC + " IS NULL"
|
||||||
|
+ " AND " + table + "." + Cols.TV_BANNER + " IS NULL"
|
||||||
|
+ " THEN 1 ELSE 0 END"
|
||||||
|
+ ", " + lastUpdated + " DESC"
|
||||||
|
+ ", " + added + " ASC"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import android.content.ContentValues;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import org.fdroid.fdroid.TestUtils;
|
import org.fdroid.fdroid.TestUtils;
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
|
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
|
||||||
import org.fdroid.fdroid.mock.MockRepo;
|
import org.fdroid.fdroid.mock.MockRepo;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@ -163,6 +164,11 @@ public class CategoryProviderTest extends FDroidProviderTest {
|
|||||||
AppProviderTest.assertContainsOnlyIds(apps, expectedPackages);
|
AppProviderTest.assertContainsOnlyIds(apps, expectedPackages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This does not include {@code sortOrder} since that is defined in
|
||||||
|
* {@link org.fdroid.fdroid.views.categories.CategoryController#onCreateLoader(int, android.os.Bundle)}
|
||||||
|
* so these results are sorted by the default sort.
|
||||||
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void topAppsFromCategory() {
|
public void topAppsFromCategory() {
|
||||||
insertAppWithCategory("com.dog", "Dog", "Animal", new Date(2017, 2, 6));
|
insertAppWithCategory("com.dog", "Dog", "Animal", new Date(2017, 2, 6));
|
||||||
@ -178,13 +184,13 @@ public class CategoryProviderTest extends FDroidProviderTest {
|
|||||||
insertAppWithCategory("com.banana", "Banana", "Vegetable", new Date(2015, 1, 1));
|
insertAppWithCategory("com.banana", "Banana", "Vegetable", new Date(2015, 1, 1));
|
||||||
insertAppWithCategory("com.tomato", "Tomato", "Vegetable", new Date(2017, 4, 4));
|
insertAppWithCategory("com.tomato", "Tomato", "Vegetable", new Date(2017, 4, 4));
|
||||||
|
|
||||||
assertArrayEquals(getTopAppsFromCategory("Animal", 3), new String[]{"com.dog", "com.cat", "com.bird"});
|
assertArrayEquals(new String[]{"com.bird", "com.cat", "com.dog"}, getTopAppsFromCategory("Animal", 3));
|
||||||
assertArrayEquals(getTopAppsFromCategory("Animal", 2), new String[]{"com.dog", "com.cat"});
|
assertArrayEquals(new String[]{"com.bird", "com.cat"}, getTopAppsFromCategory("Animal", 2));
|
||||||
assertArrayEquals(getTopAppsFromCategory("Animal", 1), new String[]{"com.dog"});
|
assertArrayEquals(new String[]{"com.bird"}, getTopAppsFromCategory("Animal", 1));
|
||||||
|
|
||||||
assertArrayEquals(getTopAppsFromCategory("Mineral", 2), new String[]{"com.rock", "com.stone"});
|
assertArrayEquals(new String[]{"com.boulder", "com.rock"}, getTopAppsFromCategory("Mineral", 2));
|
||||||
|
|
||||||
assertArrayEquals(getTopAppsFromCategory("Vegetable", 10), new String[]{"com.tomato", "com.banana"});
|
assertArrayEquals(new String[]{"com.banana", "com.tomato"}, getTopAppsFromCategory("Vegetable", 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getTopAppsFromCategory(String category, int numToGet) {
|
public String[] getTopAppsFromCategory(String category, int numToGet) {
|
||||||
@ -275,7 +281,7 @@ public class CategoryProviderTest extends FDroidProviderTest {
|
|||||||
private void insertAppWithCategory(String id, String name, String categories, Date lastUpdated, long repoId) {
|
private void insertAppWithCategory(String id, String name, String categories, Date lastUpdated, long repoId) {
|
||||||
ContentValues values = new ContentValues(2);
|
ContentValues values = new ContentValues(2);
|
||||||
values.put(Cols.ForWriting.Categories.CATEGORIES, categories);
|
values.put(Cols.ForWriting.Categories.CATEGORIES, categories);
|
||||||
values.put(Cols.LAST_UPDATED, lastUpdated.getTime() / 1000);
|
values.put(Cols.LAST_UPDATED, Utils.DATE_FORMAT.format(lastUpdated));
|
||||||
AppProviderTest.insertApp(contentResolver, context, id, name, values, repoId);
|
AppProviderTest.insertApp(contentResolver, context, id, name, values, repoId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user