Categories: Show list of all categories in the main view.
This is different to the old categories drop down, because that also included meta-categories of "Whats New" and "Recently Updated". Given we now show them on the first page, this categories screen can do away with them. Each category entry loads a few apps to show to the user. Note: The "View all" button next to each category doesn't currently go anywhere. It will soon be hooked up to an app list that is filtered to the selected category.
This commit is contained in:
parent
53df5473f5
commit
f5e6d73999
@ -0,0 +1,40 @@
|
||||
package org.fdroid.fdroid.views.categories;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.database.Cursor;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
|
||||
class AppPreviewAdapter extends RecyclerView.Adapter<AppCardController> {
|
||||
|
||||
private Cursor cursor;
|
||||
private final Activity activity;
|
||||
|
||||
AppPreviewAdapter(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppCardController onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new AppCardController(activity, activity.getLayoutInflater().inflate(R.layout.app_card_normal, parent, false));
|
||||
}
|
||||
|
||||
@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 setAppCursor(Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package org.fdroid.fdroid.views.categories;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.Schema;
|
||||
|
||||
public class CategoryAdapter extends RecyclerView.Adapter<CategoryController> {
|
||||
|
||||
private Cursor cursor;
|
||||
private final Activity activity;
|
||||
private final LoaderManager loaderManager;
|
||||
|
||||
public CategoryAdapter(Activity activity, LoaderManager loaderManager) {
|
||||
this.activity = activity;
|
||||
this.loaderManager = loaderManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CategoryController onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new CategoryController(activity, loaderManager, activity.getLayoutInflater().inflate(R.layout.category_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(CategoryController holder, int position) {
|
||||
cursor.moveToPosition(position);
|
||||
holder.bindModel(cursor.getString(cursor.getColumnIndex(Schema.CategoryTable.Cols.NAME)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return cursor == null ? 0 : cursor.getCount();
|
||||
}
|
||||
|
||||
public void setCategoriesCursor(Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
package org.fdroid.fdroid.views.categories;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.Schema;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class CategoryController extends RecyclerView.ViewHolder implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
private final Button viewAll;
|
||||
private final TextView heading;
|
||||
private final AppPreviewAdapter appCardsAdapter;
|
||||
private final FrameLayout background;
|
||||
|
||||
private final Activity activity;
|
||||
private final LoaderManager loaderManager;
|
||||
|
||||
private String currentCategory;
|
||||
|
||||
CategoryController(final Activity activity, LoaderManager loaderManager, View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.activity = activity;
|
||||
this.loaderManager = loaderManager;
|
||||
|
||||
appCardsAdapter = new AppPreviewAdapter(activity);
|
||||
|
||||
viewAll = (Button) itemView.findViewById(R.id.button);
|
||||
viewAll.setOnClickListener(onViewAll);
|
||||
|
||||
heading = (TextView) itemView.findViewById(R.id.name);
|
||||
|
||||
background = (FrameLayout) itemView.findViewById(R.id.category_background);
|
||||
|
||||
RecyclerView appCards = (RecyclerView) itemView.findViewById(R.id.app_cards);
|
||||
appCards.setAdapter(appCardsAdapter);
|
||||
appCards.addItemDecoration(new ItemDecorator(activity));
|
||||
}
|
||||
|
||||
void bindModel(@NonNull String categoryName) {
|
||||
currentCategory = categoryName;
|
||||
heading.setText(categoryName);
|
||||
viewAll.setVisibility(View.INVISIBLE);
|
||||
loaderManager.initLoader(currentCategory.hashCode(), null, this);
|
||||
loaderManager.initLoader(currentCategory.hashCode() + 1, null, this);
|
||||
|
||||
background.setBackgroundColor(getBackgroundColour(categoryName));
|
||||
}
|
||||
|
||||
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
|
||||
// category it will look the same for each different user, and each different session.
|
||||
Random random = new Random(categoryName.toLowerCase().hashCode());
|
||||
|
||||
float[] hsv = new float[3];
|
||||
hsv[0] = random.nextFloat() * 360;
|
||||
hsv[1] = 0.4f;
|
||||
hsv[2] = 0.5f;
|
||||
return Color.HSVToColor(hsv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
if (id == currentCategory.hashCode() + 1) {
|
||||
return new CursorLoader(
|
||||
activity,
|
||||
AppProvider.getCategoryUri(currentCategory),
|
||||
new String[]{Schema.AppMetadataTable.Cols._COUNT},
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
} else {
|
||||
return new CursorLoader(
|
||||
activity,
|
||||
AppProvider.getTopFromCategoryUri(currentCategory, 20),
|
||||
new String[]{
|
||||
Schema.AppMetadataTable.Cols.NAME,
|
||||
Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME,
|
||||
Schema.AppMetadataTable.Cols.SUMMARY,
|
||||
Schema.AppMetadataTable.Cols.ICON_URL,
|
||||
},
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
int topAppsId = currentCategory.hashCode();
|
||||
int countAllAppsId = topAppsId + 1;
|
||||
|
||||
// Anything other than these IDs indicates that the loader which just finished finished
|
||||
// is no longer the one this view holder is interested in, due to the user having
|
||||
// scrolled away already during the asynchronous query being run.
|
||||
if (loader.getId() == topAppsId) {
|
||||
appCardsAdapter.setAppCursor(cursor);
|
||||
} else if (loader.getId() == countAllAppsId) {
|
||||
cursor.moveToFirst();
|
||||
int numAppsInCategory = cursor.getInt(0);
|
||||
viewAll.setVisibility(View.VISIBLE);
|
||||
viewAll.setText(activity.getResources().getQuantityString(R.plurals.button_view_all_apps_in_category, numAppsInCategory, numAppsInCategory));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
appCardsAdapter.setAppCursor(null);
|
||||
}
|
||||
|
||||
private final View.OnClickListener onViewAll = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies excessive padding to the start of the first item. This is so that the category artwork
|
||||
* can peek out and make itself visible. This is RTL friendly.
|
||||
* @see org.fdroid.fdroid.R.dimen#category_preview__app_list__padding__horizontal
|
||||
* @see org.fdroid.fdroid.R.dimen#category_preview__app_list__padding__horizontal__first
|
||||
*/
|
||||
private static class ItemDecorator extends RecyclerView.ItemDecoration {
|
||||
private final Context context;
|
||||
|
||||
ItemDecorator(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
int horizontalPadding = (int) context.getResources().getDimension(R.dimen.category_preview__app_list__padding__horizontal);
|
||||
int horizontalPaddingFirst = (int) context.getResources().getDimension(R.dimen.category_preview__app_list__padding__horizontal__first);
|
||||
boolean isLtr = ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_LTR;
|
||||
int itemPosition = parent.getChildLayoutPosition(view);
|
||||
boolean first = itemPosition == 0;
|
||||
|
||||
// Leave this "paddingEnd" local variable here for clarity when converting from
|
||||
// left/right to start/end for RTL friendly layout.
|
||||
// noinspection UnnecessaryLocalVariable
|
||||
int paddingEnd = horizontalPadding;
|
||||
int paddingStart = first ? horizontalPaddingFirst : horizontalPadding;
|
||||
|
||||
int paddingLeft = isLtr ? paddingStart : paddingEnd;
|
||||
int paddingRight = isLtr ? paddingEnd : paddingStart;
|
||||
outRect.set(paddingLeft, 0, paddingRight, 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
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.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.CategoryProvider;
|
||||
import org.fdroid.fdroid.data.Schema;
|
||||
import org.fdroid.fdroid.views.categories.CategoryAdapter;
|
||||
|
||||
/**
|
||||
* Responsible for ensuring that the categories view is inflated and then populated correctly.
|
||||
* Will start a loader to get the list of categories from the database and populate a recycler
|
||||
* view with relevant info about each.
|
||||
*/
|
||||
class CategoriesViewBinder implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private static final int LOADER_ID = 429820532;
|
||||
|
||||
private final CategoryAdapter categoryAdapter;
|
||||
private final AppCompatActivity activity;
|
||||
|
||||
CategoriesViewBinder(AppCompatActivity activity, FrameLayout parent) {
|
||||
this.activity = activity;
|
||||
|
||||
View categoriesView = activity.getLayoutInflater().inflate(R.layout.main_tab_categories, parent, true);
|
||||
|
||||
categoryAdapter = new CategoryAdapter(activity, activity.getSupportLoaderManager());
|
||||
|
||||
RecyclerView categoriesList = (RecyclerView) categoriesView.findViewById(R.id.category_list);
|
||||
categoriesList.setHasFixedSize(true);
|
||||
categoriesList.setLayoutManager(new LinearLayoutManager(activity));
|
||||
categoriesList.setAdapter(categoryAdapter);
|
||||
|
||||
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,
|
||||
CategoryProvider.getAllCategories(),
|
||||
Schema.CategoryTable.Cols.ALL,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
if (loader.getId() != LOADER_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
categoryAdapter.setCategoriesCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
if (loader.getId() != LOADER_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
categoryAdapter.setCategoriesCursor(null);
|
||||
}
|
||||
|
||||
}
|
@ -46,7 +46,11 @@ class MainViewController extends RecyclerView.ViewHolder {
|
||||
new MyAppsViewBinder(activity, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CategoriesViewBinder
|
||||
*/
|
||||
public void bindCategoriesView() {
|
||||
new CategoriesViewBinder(activity, frame);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Visually different from other app cards because it doesn't have a drop shadow, and has a larger
|
||||
corner radius.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="4dp" />
|
||||
<solid android:color="#faf8ef" />
|
||||
</shape>
|
41
app/src/main/res/layout/app_card_normal.xml
Normal file
41
app/src/main/res/layout/app_card_normal.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?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="100dp"
|
||||
android:layout_height="130dp"
|
||||
android:background="@drawable/category_preview_app_card_background"
|
||||
android:padding="8dp">
|
||||
|
||||
<!-- Ignore ContentDescription because it is kind of meaningless to have TTS read out "App icon"
|
||||
when it will inevitably read out the name of the app straight after. -->
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="48dip"
|
||||
android:layout_height="48dip"
|
||||
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"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<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="3"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintTop_toBottomOf="@+id/icon"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
80
app/src/main/res/layout/category_item.xml
Normal file
80
app/src/main/res/layout/category_item.xml
Normal file
@ -0,0 +1,80 @@
|
||||
<?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:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
tools:text="Business"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/button"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
|
||||
android:textSize="18sp"
|
||||
android:textColor="#4a4a4a"
|
||||
android:paddingLeft="18dp"
|
||||
android:paddingStart="18dp"
|
||||
android:paddingRight="18dp"
|
||||
android:paddingEnd="18dp"
|
||||
tools:layout_editor_absoluteX="0dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
tools:text="View all 10"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:background="@android:color/transparent"
|
||||
android:paddingLeft="18dp"
|
||||
android:paddingStart="18dp"
|
||||
android:paddingRight="18dp"
|
||||
android:paddingEnd="18dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/fdroid_blue"
|
||||
|
||||
tools:layout_editor_absoluteX="268dp" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/category_background"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="@+id/app_cards"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/app_cards"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:background="#ffffbbbb"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/category_image"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/button"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/app_cards"
|
||||
tools:listitem="@layout/app_card_normal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/button"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="@dimen/category_preview__app_list__padding__vertical"
|
||||
android:paddingBottom="@dimen/category_preview__app_list__padding__vertical"
|
||||
android:clipToPadding="false"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
19
app/src/main/res/layout/main_tab_categories.xml
Normal file
19
app/src/main/res/layout/main_tab_categories.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/category_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/category_item"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</LinearLayout>
|
@ -20,4 +20,11 @@
|
||||
|
||||
<dimen name="whats_new__padding__app_card__horizontal">12dp</dimen>
|
||||
<dimen name="whats_new__padding__app_card__vertical">10dp</dimen>
|
||||
|
||||
<dimen name="category_preview__app_list__padding__horizontal">4dp</dimen>
|
||||
<dimen name="category_preview__app_list__padding__horizontal__first">72dp</dimen>
|
||||
<dimen name="category_preview__app_list__padding__vertical">18dp</dimen>
|
||||
<dimen name="category_preview__padding__recycler_view__top">12dp</dimen>
|
||||
<dimen name="category_preview__padding__app_card__horizontal">3dp</dimen>
|
||||
<dimen name="category_preview__padding__app_card__vertical">4dp</dimen>
|
||||
</resources>
|
||||
|
@ -277,6 +277,13 @@
|
||||
<string name="category_Time">Time</string>
|
||||
<string name="category_Writing">Writing</string>
|
||||
|
||||
<plurals name="button_view_all_apps_in_category">
|
||||
<!-- Even though these are the same as eachother, Android docs suggest always specifying at
|
||||
least "one" and "other": https://developer.android.com/guide/topics/resources/string-resource.html#Plurals -->
|
||||
<item quantity="one">View all %d</item>
|
||||
<item quantity="other">View all %d</item>
|
||||
</plurals>
|
||||
|
||||
<string name="empty_installed_app_list">No apps installed.\n\nThere are apps on your device, but they are not available from F-Droid. This could be because you need to update your repositories, or the repositories genuinely don\'t have your apps available.</string>
|
||||
<string name="empty_available_app_list">No apps in this category.\n\nTry selecting a different category or updating your repositories to get a fresh list of apps.</string>
|
||||
<string name="empty_can_update_app_list">All apps up to date.\n\nCongratulations! All of your apps are up to date (or your repositories are out of date).</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user