Whats New: Added list of recently updated apps to the main view.
Smooshes the recently updated and recently added lists into one, and adds a status line under each app saying which of the two it is (i.e. "Recnelty Updated" or "Whats New".
This commit is contained in:
parent
517301194b
commit
69b58c7646
@ -29,7 +29,11 @@ import android.support.annotation.Nullable;
|
|||||||
import android.support.annotation.RequiresApi;
|
import android.support.annotation.RequiresApi;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.text.style.CharacterStyle;
|
||||||
|
import android.text.style.TypefaceSpan;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
@ -487,6 +491,23 @@ public final class Utils {
|
|||||||
return formatDateFormat(TIME_FORMAT, date, fallback);
|
return formatDateFormat(TIME_FORMAT, date, fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the app name using "sans-serif" and then appends the summary after a space with
|
||||||
|
* "sans-serif-light". Doesn't mandate any font sizes or any other styles, that is up to the
|
||||||
|
* {@link android.widget.TextView} which it ends up being displayed in.
|
||||||
|
*/
|
||||||
|
public static CharSequence formatAppNameAndSummary(String appName, String summary) {
|
||||||
|
String toFormat = appName + ' ' + summary;
|
||||||
|
CharacterStyle normal = new TypefaceSpan("sans-serif");
|
||||||
|
CharacterStyle light = new TypefaceSpan("sans-serif-light");
|
||||||
|
|
||||||
|
SpannableStringBuilder sb = new SpannableStringBuilder(toFormat);
|
||||||
|
sb.setSpan(normal, 0, appName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
sb.setSpan(light, appName.length(), toFormat.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
// Need this to add the unimplemented support for ordered and unordered
|
// Need this to add the unimplemented support for ordered and unordered
|
||||||
// lists to Html.fromHtml().
|
// lists to Html.fromHtml().
|
||||||
public static class HtmlTagHandler implements Html.TagHandler {
|
public static class HtmlTagHandler implements Html.TagHandler {
|
||||||
|
@ -373,7 +373,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
protected static final String PATH_APPS = "apps";
|
protected static final String PATH_APPS = "apps";
|
||||||
protected static final String PATH_SPECIFIC_APP = "app";
|
protected static final String PATH_SPECIFIC_APP = "app";
|
||||||
private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated";
|
private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated";
|
||||||
private static final String PATH_NEWLY_ADDED = "newlyAdded";
|
|
||||||
private static final String PATH_CATEGORY = "category";
|
private static final String PATH_CATEGORY = "category";
|
||||||
private static final String PATH_REPO = "repo";
|
private static final String PATH_REPO = "repo";
|
||||||
private static final String PATH_HIGHEST_PRIORITY = "highestPriority";
|
private static final String PATH_HIGHEST_PRIORITY = "highestPriority";
|
||||||
@ -386,8 +385,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
private static final int SEARCH_TEXT = INSTALLED + 1;
|
private static final int SEARCH_TEXT = INSTALLED + 1;
|
||||||
private static final int SEARCH_TEXT_AND_CATEGORIES = SEARCH_TEXT + 1;
|
private static final int SEARCH_TEXT_AND_CATEGORIES = SEARCH_TEXT + 1;
|
||||||
private static final int RECENTLY_UPDATED = SEARCH_TEXT_AND_CATEGORIES + 1;
|
private static final int RECENTLY_UPDATED = SEARCH_TEXT_AND_CATEGORIES + 1;
|
||||||
private static final int NEWLY_ADDED = RECENTLY_UPDATED + 1;
|
private static final int CATEGORY = RECENTLY_UPDATED + 1;
|
||||||
private static final int CATEGORY = NEWLY_ADDED + 1;
|
|
||||||
private static final int CALC_SUGGESTED_APKS = CATEGORY + 1;
|
private static final int CALC_SUGGESTED_APKS = CATEGORY + 1;
|
||||||
private static final int REPO = CALC_SUGGESTED_APKS + 1;
|
private static final int REPO = CALC_SUGGESTED_APKS + 1;
|
||||||
private static final int SEARCH_REPO = REPO + 1;
|
private static final int SEARCH_REPO = REPO + 1;
|
||||||
@ -401,7 +399,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
MATCHER.addURI(getAuthority(), null, CODE_LIST);
|
MATCHER.addURI(getAuthority(), null, CODE_LIST);
|
||||||
MATCHER.addURI(getAuthority(), PATH_CALC_SUGGESTED_APKS, CALC_SUGGESTED_APKS);
|
MATCHER.addURI(getAuthority(), PATH_CALC_SUGGESTED_APKS, CALC_SUGGESTED_APKS);
|
||||||
MATCHER.addURI(getAuthority(), PATH_RECENTLY_UPDATED, RECENTLY_UPDATED);
|
MATCHER.addURI(getAuthority(), PATH_RECENTLY_UPDATED, RECENTLY_UPDATED);
|
||||||
MATCHER.addURI(getAuthority(), PATH_NEWLY_ADDED, NEWLY_ADDED);
|
|
||||||
MATCHER.addURI(getAuthority(), PATH_CATEGORY + "/*", CATEGORY);
|
MATCHER.addURI(getAuthority(), PATH_CATEGORY + "/*", CATEGORY);
|
||||||
MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*/*", SEARCH_TEXT_AND_CATEGORIES);
|
MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*/*", SEARCH_TEXT_AND_CATEGORIES);
|
||||||
MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*", SEARCH_TEXT);
|
MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*", SEARCH_TEXT);
|
||||||
@ -425,10 +422,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return Uri.withAppendedPath(getContentUri(), PATH_RECENTLY_UPDATED);
|
return Uri.withAppendedPath(getContentUri(), PATH_RECENTLY_UPDATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri getNewlyAddedUri() {
|
|
||||||
return Uri.withAppendedPath(getContentUri(), PATH_NEWLY_ADDED);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Uri calcSuggestedApksUri() {
|
private static Uri calcSuggestedApksUri() {
|
||||||
return Uri.withAppendedPath(getContentUri(), PATH_CALC_SUGGESTED_APKS);
|
return Uri.withAppendedPath(getContentUri(), PATH_CALC_SUGGESTED_APKS);
|
||||||
}
|
}
|
||||||
@ -666,12 +659,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return new AppQuerySelection(selection);
|
return new AppQuerySelection(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppQuerySelection queryNewlyAdded() {
|
|
||||||
final String selection = getTableName() + "." + Cols.ADDED + " > ?";
|
|
||||||
final String[] args = {Utils.formatDate(Preferences.get().calcMaxHistory(), "")};
|
|
||||||
return new AppQuerySelection(selection, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that for each app metadata row with the same package name, only the one from the repo
|
* Ensures that for each app metadata row with the same package name, only the one from the repo
|
||||||
* with the best priority is represented in the result set. While possible to calculate this
|
* with the best priority is represented in the result set. While possible to calculate this
|
||||||
@ -689,9 +676,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AppQuerySelection queryRecentlyUpdated() {
|
private AppQuerySelection queryRecentlyUpdated() {
|
||||||
final String app = getTableName();
|
final String selection = getTableName() + "." + Cols.LAST_UPDATED + " > ? ";
|
||||||
final String lastUpdated = app + "." + Cols.LAST_UPDATED;
|
|
||||||
final String selection = app + "." + Cols.ADDED + " != " + lastUpdated + " AND " + lastUpdated + " > ?";
|
|
||||||
final String[] args = {Utils.formatDate(Preferences.get().calcMaxHistory(), "")};
|
final String[] args = {Utils.formatDate(Preferences.get().calcMaxHistory(), "")};
|
||||||
return new AppQuerySelection(selection, args);
|
return new AppQuerySelection(selection, args);
|
||||||
}
|
}
|
||||||
@ -810,12 +795,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
includeSwap = false;
|
includeSwap = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NEWLY_ADDED:
|
|
||||||
sortOrder = getTableName() + "." + Cols.ADDED + " DESC";
|
|
||||||
selection = selection.add(queryNewlyAdded());
|
|
||||||
includeSwap = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HIGHEST_PRIORITY:
|
case HIGHEST_PRIORITY:
|
||||||
selection = selection.add(queryPackageName(uri.getLastPathSegment()));
|
selection = selection.add(queryPackageName(uri.getLastPathSegment()));
|
||||||
includeSwap = false;
|
includeSwap = false;
|
||||||
|
@ -0,0 +1,179 @@
|
|||||||
|
package org.fdroid.fdroid.views.categories;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.IdRes;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.ActivityOptionsCompat;
|
||||||
|
import android.support.v4.util.Pair;
|
||||||
|
import android.support.v7.graphics.Palette;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||||
|
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.AppDetails;
|
||||||
|
import org.fdroid.fdroid.Preferences;
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link AppCardController} can bind an app to several different layouts, as long as the layout
|
||||||
|
* contains the following elements:
|
||||||
|
* + {@link R.id#icon} ({@link ImageView}, required)
|
||||||
|
* + {@link R.id#summary} ({@link TextView}, required)
|
||||||
|
* + {@link R.id#featured_image} ({@link ImageView}, optional)
|
||||||
|
* + {@link R.id#status} ({@link TextView}, optional)
|
||||||
|
*/
|
||||||
|
public class AppCardController extends RecyclerView.ViewHolder implements ImageLoadingListener, View.OnClickListener {
|
||||||
|
@NonNull
|
||||||
|
private final ImageView icon;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final TextView summary;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final TextView status;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final ImageView featuredImage;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private App currentApp;
|
||||||
|
|
||||||
|
private final Activity activity;
|
||||||
|
private final int defaultFeaturedImageColour;
|
||||||
|
private final DisplayImageOptions displayImageOptions;
|
||||||
|
|
||||||
|
private final Date recentCuttoffDate;
|
||||||
|
|
||||||
|
public AppCardController(Activity activity, View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
|
||||||
|
this.activity = activity;
|
||||||
|
|
||||||
|
recentCuttoffDate = Preferences.get().calcMaxHistory();
|
||||||
|
|
||||||
|
icon = (ImageView) findViewAndEnsureNonNull(itemView, R.id.icon);
|
||||||
|
summary = (TextView) findViewAndEnsureNonNull(itemView, R.id.summary);
|
||||||
|
|
||||||
|
featuredImage = (ImageView) itemView.findViewById(R.id.featured_image);
|
||||||
|
status = (TextView) itemView.findViewById(R.id.status);
|
||||||
|
|
||||||
|
defaultFeaturedImageColour = activity.getResources().getColor(R.color.cardview_light_background);
|
||||||
|
displayImageOptions = Utils.getImageLoadingOptions().build();
|
||||||
|
|
||||||
|
itemView.setOnClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contract that this controller has is that it will consume any layout resource, given
|
||||||
|
* it has some specific view types (with specific IDs) available. This helper function will
|
||||||
|
* throw an {@link IllegalArgumentException} if the view doesn't exist,
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private View findViewAndEnsureNonNull(View view, @IdRes int res) {
|
||||||
|
View found = view.findViewById(res);
|
||||||
|
if (found == null) {
|
||||||
|
String resName = activity.getResources().getResourceName(res);
|
||||||
|
throw new IllegalArgumentException("Layout for AppCardController requires " + resName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bindApp(@NonNull App app) {
|
||||||
|
currentApp = app;
|
||||||
|
|
||||||
|
summary.setText(Utils.formatAppNameAndSummary(app.name, app.summary));
|
||||||
|
|
||||||
|
if (status != null) {
|
||||||
|
if (app.added != null && app.added.after(recentCuttoffDate) && (app.lastUpdated == null || app.added.equals(app.lastUpdated))) {
|
||||||
|
status.setText(activity.getString(R.string.category_Whats_New));
|
||||||
|
status.setVisibility(View.VISIBLE);
|
||||||
|
} else if (app.lastUpdated != null && app.lastUpdated.after(recentCuttoffDate)) {
|
||||||
|
status.setText(activity.getString(R.string.category_Recently_Updated));
|
||||||
|
status.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
status.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (featuredImage != null) {
|
||||||
|
featuredImage.setBackgroundColor(defaultFeaturedImageColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageLoader.getInstance().displayImage(app.iconUrl, icon, displayImageOptions, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the user clicks/touches an app card, we launch the {@link AppDetails} activity in response.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (currentApp == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(activity, AppDetails.class);
|
||||||
|
intent.putExtra(AppDetails.EXTRA_APPID, currentApp.packageName);
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
Pair<View, String> iconTransitionPair = Pair.create((View) icon, activity.getString(R.string.transition_app_item_icon));
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") // We are passing the right type as the second varargs argument (i.e. a Pair<View, String>).
|
||||||
|
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, iconTransitionPair).toBundle();
|
||||||
|
|
||||||
|
activity.startActivity(intent, bundle);
|
||||||
|
} else {
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================================
|
||||||
|
// Icon loader callbacks
|
||||||
|
//
|
||||||
|
// Most are unused, the main goal is to specify a background colour for the featured image if
|
||||||
|
// no featured image is specified in the apps metadata.
|
||||||
|
// =============================================================================================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||||
|
final ImageView image = featuredImage;
|
||||||
|
if (image != null) {
|
||||||
|
new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() {
|
||||||
|
@Override
|
||||||
|
public void onGenerated(Palette palette) {
|
||||||
|
image.setBackgroundColor(palette.getDominantColor(defaultFeaturedImageColour));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadingStarted(String imageUri, View view) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadingCancelled(String imageUri, View view) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -178,7 +178,9 @@ public class AvailableAppsFragment extends AppListFragment implements
|
|||||||
return AppProvider.getRecentlyUpdatedUri();
|
return AppProvider.getRecentlyUpdatedUri();
|
||||||
}
|
}
|
||||||
if (currentCategory.equals(CategoryProvider.Helper.getCategoryWhatsNew(getActivity()))) {
|
if (currentCategory.equals(CategoryProvider.Helper.getCategoryWhatsNew(getActivity()))) {
|
||||||
return AppProvider.getNewlyAddedUri();
|
// Removed this feature in the new UI. this fragment will be gone soon so not implementing it again.
|
||||||
|
// return AppProvider.getNewlyAddedUri();
|
||||||
|
return AppProvider.getRecentlyUpdatedUri();
|
||||||
}
|
}
|
||||||
return AppProvider.getCategoryUri(currentCategory);
|
return AppProvider.getCategoryUri(currentCategory);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,11 @@ class MainViewController extends RecyclerView.ViewHolder {
|
|||||||
frame.removeAllViews();
|
frame.removeAllViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see WhatsNewViewBinder
|
||||||
|
*/
|
||||||
public void bindWhatsNewView() {
|
public void bindWhatsNewView() {
|
||||||
|
new WhatsNewViewBinder(activity, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bindMyApps() {
|
public void bindMyApps() {
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
package org.fdroid.fdroid.views.main;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.GridLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
|
import org.fdroid.fdroid.data.Schema;
|
||||||
|
import org.fdroid.fdroid.views.whatsnew.WhatsNewAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a list of newly added or recently updated apps and displays them to the user.
|
||||||
|
*/
|
||||||
|
class WhatsNewViewBinder implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
|
private static final int LOADER_ID = 978015789;
|
||||||
|
|
||||||
|
private final WhatsNewAdapter whatsNewAdapter;
|
||||||
|
private final AppCompatActivity activity;
|
||||||
|
|
||||||
|
private static RecyclerView.ItemDecoration appListDecorator;
|
||||||
|
|
||||||
|
WhatsNewViewBinder(AppCompatActivity activity, FrameLayout parent) {
|
||||||
|
this.activity = activity;
|
||||||
|
|
||||||
|
View whatsNewView = activity.getLayoutInflater().inflate(R.layout.main_tab_whats_new, parent, true);
|
||||||
|
|
||||||
|
whatsNewAdapter = new WhatsNewAdapter(activity);
|
||||||
|
|
||||||
|
GridLayoutManager layoutManager = new GridLayoutManager(activity, 2);
|
||||||
|
layoutManager.setSpanSizeLookup(new WhatsNewAdapter.SpanSizeLookup());
|
||||||
|
|
||||||
|
RecyclerView appList = (RecyclerView) whatsNewView.findViewById(R.id.app_list);
|
||||||
|
appList.setHasFixedSize(true);
|
||||||
|
appList.setLayoutManager(layoutManager);
|
||||||
|
appList.setAdapter(whatsNewAdapter);
|
||||||
|
|
||||||
|
// This is a bit hacky, but for some reason even though we are inflating the main_tab_whats_new
|
||||||
|
// layout above, the app_list RecyclerView seems to remember that it has decorations from before.
|
||||||
|
// If we blindly call addItemDecoration here without first removing the existing one, it will
|
||||||
|
// double up on all of the paddings the second time we view it. The third time it will triple up
|
||||||
|
// on the paddings, etc. In addition, the API doesn't allow us to "clearAllDecorators()". Instead
|
||||||
|
// we need to hold onto the reference to the one we added in order to remove it.
|
||||||
|
if (appListDecorator == null) {
|
||||||
|
appListDecorator = new WhatsNewAdapter.ItemDecorator(activity);
|
||||||
|
} else {
|
||||||
|
appList.removeItemDecoration(appListDecorator);
|
||||||
|
}
|
||||||
|
|
||||||
|
appList.addItemDecoration(appListDecorator);
|
||||||
|
|
||||||
|
activity.getSupportLoaderManager().initLoader(LOADER_ID, null, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
if (id != LOADER_ID) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CursorLoader(
|
||||||
|
activity,
|
||||||
|
AppProvider.getRecentlyUpdatedUri(),
|
||||||
|
Schema.AppMetadataTable.Cols.ALL,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||||
|
if (loader.getId() != LOADER_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
whatsNewAdapter.setAppsCursor(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
|
if (loader.getId() != LOADER_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
whatsNewAdapter.setAppsCursor(null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
package org.fdroid.fdroid.views.whatsnew;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.support.v4.view.ViewCompat;
|
||||||
|
import android.support.v7.widget.GridLayoutManager;
|
||||||
|
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.views.categories.AppCardController;
|
||||||
|
|
||||||
|
public class WhatsNewAdapter extends RecyclerView.Adapter<AppCardController> {
|
||||||
|
|
||||||
|
private Cursor cursor;
|
||||||
|
private final Activity activity;
|
||||||
|
|
||||||
|
public WhatsNewAdapter(Activity activity) {
|
||||||
|
this.activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AppCardController onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
int layout;
|
||||||
|
if (viewType == R.id.whats_new_feature) {
|
||||||
|
layout = R.layout.app_card_featured;
|
||||||
|
} else if (viewType == R.id.whats_new_large_tile) {
|
||||||
|
layout = R.layout.app_card_large;
|
||||||
|
} else if (viewType == R.id.whats_new_small_tile) {
|
||||||
|
layout = R.layout.app_card_horizontal;
|
||||||
|
} else if (viewType == R.id.whats_new_regular_list) {
|
||||||
|
layout = R.layout.app_card_list_item;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown view type when rendering \"Whats New\": " + viewType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AppCardController(activity, activity.getLayoutInflater().inflate(layout, parent, false));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (position == 0) {
|
||||||
|
return R.id.whats_new_feature;
|
||||||
|
} else if (position <= 2) {
|
||||||
|
return R.id.whats_new_large_tile;
|
||||||
|
} else if (position <= 4) {
|
||||||
|
return R.id.whats_new_small_tile;
|
||||||
|
} else {
|
||||||
|
return R.id.whats_new_regular_list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(AppCardController holder, int position) {
|
||||||
|
cursor.moveToPosition(position);
|
||||||
|
holder.bindApp(new App(cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return cursor == null ? 0 : cursor.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppsCursor(Cursor cursor) {
|
||||||
|
this.cursor = cursor;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Replace with https://github.com/lucasr/twoway-view which looks really really cool, but
|
||||||
|
// no longer under active development (despite heaps of forks/stars on github).
|
||||||
|
public static class SpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
|
||||||
|
@Override
|
||||||
|
public int getSpanSize(int position) {
|
||||||
|
if (position == 0) {
|
||||||
|
return 2;
|
||||||
|
} else if (position <= 4) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies padding to items, ensuring that the spacing on the left, centre, and right all match.
|
||||||
|
* The vertical padding is slightly shorter than the horizontal padding also.
|
||||||
|
* @see org.fdroid.fdroid.R.dimen#whats_new__padding__app_card__horizontal
|
||||||
|
* @see org.fdroid.fdroid.R.dimen#whats_new__padding__app_card__vertical
|
||||||
|
*/
|
||||||
|
public static class ItemDecorator extends RecyclerView.ItemDecoration {
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public ItemDecorator(Context context) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||||
|
int position = parent.getChildAdapterPosition(view);
|
||||||
|
int horizontalPadding = (int) context.getResources().getDimension(R.dimen.whats_new__padding__app_card__horizontal);
|
||||||
|
int verticalPadding = (int) context.getResources().getDimension(R.dimen.whats_new__padding__app_card__vertical);
|
||||||
|
|
||||||
|
if (position == 0) {
|
||||||
|
// Don't set any padding for the first item as the FeatureImage behind it needs to butt right
|
||||||
|
// up against the left/top/right of the screen.
|
||||||
|
outRect.set(0, 0, 0, verticalPadding);
|
||||||
|
} else if (position <= 4) {
|
||||||
|
// Odd items are on the left, even on the right.
|
||||||
|
// The item on the left will have both left and right padding. The item on the right
|
||||||
|
// will only have padding on the right. This will allow the same amount of padding
|
||||||
|
// on the left, centre, and right of the grid, rather than double the padding in the
|
||||||
|
// middle (which would happen if both left+right paddings were set for both items).
|
||||||
|
boolean isLtr = ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_LTR;
|
||||||
|
boolean isAtStart = (position % 2) == 1;
|
||||||
|
int paddingStart = isAtStart ? horizontalPadding : 0;
|
||||||
|
int paddingLeft = isLtr ? paddingStart : horizontalPadding;
|
||||||
|
int paddingRight = isLtr ? horizontalPadding : paddingStart;
|
||||||
|
outRect.set(paddingLeft, 0, paddingRight, verticalPadding);
|
||||||
|
} else {
|
||||||
|
outRect.set(horizontalPadding, 0, horizontalPadding, verticalPadding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
app/src/main/res/layout/app_card_featured.xml
Normal file
86
app/src/main/res/layout/app_card_featured.xml
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="2dp"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/featured_image"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
tools:src="@color/fdroid_green"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<android.support.constraint.Guideline
|
||||||
|
android:id="@+id/header_height"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintGuide_begin="80dp" />
|
||||||
|
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/whats_new__padding__app_card__horizontal"
|
||||||
|
android:layout_marginStart="@dimen/whats_new__padding__app_card__horizontal"
|
||||||
|
android:layout_marginRight="@dimen/whats_new__padding__app_card__horizontal"
|
||||||
|
android:layout_marginEnd="@dimen/whats_new__padding__app_card__horizontal"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/header_height"
|
||||||
|
>
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="12dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:contentDescription="@string/app_icon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
tools:src="@drawable/ic_launcher"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
tools:text="F-Droid An application summary which takes up too much space and must ellipsize, perhaps after wrapping to a new line"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/summary"
|
||||||
|
android:lines="2"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
tools:text="Recently added"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:lines="1"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/summary"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:layout_marginTop="8dp" />
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
52
app/src/main/res/layout/app_card_horizontal.xml
Normal file
52
app/src/main/res/layout/app_card_horizontal.xml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:contentDescription="@string/app_icon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
tools:src="@drawable/ic_launcher"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
tools:text="F-Droid An application summary which takes up too much space and must ellipsize"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/summary"
|
||||||
|
android:lines="4"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
tools:text="Recently added"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:lines="1"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/summary"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:layout_marginTop="4dp" />
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
58
app/src/main/res/layout/app_card_large.xml
Normal file
58
app/src/main/res/layout/app_card_large.xml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="220dp">
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:contentDescription="@string/app_icon"
|
||||||
|
android:layout_width="96dip"
|
||||||
|
android:layout_height="96dip"
|
||||||
|
tools:src="@drawable/ic_launcher"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
tools:text="F-Droid An application summary which takes up too much space and must ellipsize"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/summary"
|
||||||
|
android:maxLines="4"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/icon"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
android:layout_marginTop="8dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
tools:text="Recently added"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:lines="1"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:layout_marginTop="8dp" />
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
55
app/src/main/res/layout/app_card_list_item.xml
Normal file
55
app/src/main/res/layout/app_card_list_item.xml
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:contentDescription="@string/app_icon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
tools:src="@drawable/ic_launcher"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
tools:text="F-Droid An application summary which takes up too much space and must ellipsize"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/summary"
|
||||||
|
android:lines="2"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:layout_editor_absoluteY="8dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
tools:text="Recently added"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:lines="1"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/summary"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:layout_marginTop="8dp" />
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
19
app/src/main/res/layout/main_tab_whats_new.xml
Normal file
19
app/src/main/res/layout/main_tab_whats_new.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/app_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:listitem="@layout/app_card_normal"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -18,4 +18,6 @@
|
|||||||
<!-- The selected item stands out from the background by this elevation -->
|
<!-- The selected item stands out from the background by this elevation -->
|
||||||
<dimen name="details_screenshot_selected_elevation">3dp</dimen>
|
<dimen name="details_screenshot_selected_elevation">3dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="whats_new__padding__app_card__horizontal">12dp</dimen>
|
||||||
|
<dimen name="whats_new__padding__app_card__vertical">10dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -2,4 +2,9 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<item type="id" name="category_spinner" />
|
<item type="id" name="category_spinner" />
|
||||||
<item type="id" name="appDetailsSummaryHeader" />
|
<item type="id" name="appDetailsSummaryHeader" />
|
||||||
|
|
||||||
|
<item type="id" name="whats_new_feature" />
|
||||||
|
<item type="id" name="whats_new_large_tile" />
|
||||||
|
<item type="id" name="whats_new_small_tile" />
|
||||||
|
<item type="id" name="whats_new_regular_list" />
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user