diff --git a/res/layout/fdroid.xml b/res/layout/fdroid.xml
index 508ec029b..5f84b230b 100644
--- a/res/layout/fdroid.xml
+++ b/res/layout/fdroid.xml
@@ -1,22 +1,15 @@
-
+
-
-
-
-
-
-
-
+
diff --git a/src/org/fdroid/fdroid/AppListManager.java b/src/org/fdroid/fdroid/AppListManager.java
new file mode 100644
index 000000000..59f66f161
--- /dev/null
+++ b/src/org/fdroid/fdroid/AppListManager.java
@@ -0,0 +1,234 @@
+package org.fdroid.fdroid;
+
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.widget.ArrayAdapter;
+
+import java.util.*;
+
+/**
+ * Should be owned by the FDroid Activity, but used by the AppListFragments.
+ * The idea is that it takes a non-trivial amount of time to work this stuff
+ * out, and it is quicker if we only do it once for each view, rather than
+ * each fragment figuring out their own list independently.
+ */
+public class AppListManager {
+
+ private Vector allApps = null;
+
+ private FDroid fdroidActivity;
+
+ private AppListAdapter availableApps;
+ private AppListAdapter installedApps;
+ private AppListAdapter canUpgradeApps;
+ private ArrayAdapter categories;
+
+ private String currentCategory = null;
+ private String categoryAll = null;
+ private String categoryWhatsNew = null;
+ private String categoryRecentlyUpdated = null;
+
+ public AppListAdapter getAvailableAdapter() {
+ return availableApps;
+ }
+
+ public AppListAdapter getInstalledAdapter() {
+ return installedApps;
+ }
+
+ public AppListAdapter getCanUpdateAdapter() {
+ return canUpgradeApps;
+ }
+
+ public ArrayAdapter getCategoriesAdapter() {
+ return categories;
+ }
+
+ public AppListManager(FDroid activity) {
+ this.fdroidActivity = activity;
+
+ availableApps = new AppListAdapter(fdroidActivity);
+ installedApps = new AppListAdapter(fdroidActivity);
+ canUpgradeApps = new AppListAdapter(fdroidActivity);
+
+ // Needs to be created before createViews(), because that will use the
+ // getCategoriesAdapter() accessor which expects this object...
+ categories = new ArrayAdapter(activity,
+ android.R.layout.simple_spinner_item, new Vector());
+ categories
+ .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ }
+
+ private void clear() {
+ installedApps.clear();
+ availableApps.clear();
+ canUpgradeApps.clear();
+ categories.clear();
+ }
+
+ private void notifyLists() {
+ // Tell the lists that the data behind the adapter has changed, so
+ // they can refresh...
+ availableApps.notifyDataSetChanged();
+ installedApps.notifyDataSetChanged();
+ canUpgradeApps.notifyDataSetChanged();
+ categories.notifyDataSetChanged();
+ }
+
+ private void updateCategories() {
+ try {
+ DB db = DB.getDB();
+
+ // Populate the category list with the real categories, and the
+ // locally generated meta-categories for "All", "What's New" and
+ // "Recently Updated"...
+ categoryAll = fdroidActivity.getString(R.string.category_all);
+ categoryWhatsNew = fdroidActivity.getString(R.string.category_whatsnew);
+ categoryRecentlyUpdated = fdroidActivity.getString(R.string.category_recentlyupdated);
+
+ categories.add(categoryWhatsNew);
+ categories.add(categoryRecentlyUpdated);
+ categories.add(categoryAll);
+
+ for (String s : db.getCategories()) {
+ categories.add(s);
+ }
+
+ if (currentCategory == null)
+ currentCategory = categoryWhatsNew;
+
+ } finally {
+ DB.releaseDB();
+ }
+ }
+
+ // Tell the FDroid activity to update its "Update (x)" tab to correctly
+ // reflect the number of updates available.
+ private void notifyActivity() {
+ fdroidActivity.refreshUpdateTabLabel();
+ }
+
+ public void repopulateLists() {
+
+ long startTime = System.currentTimeMillis();
+
+ clear();
+
+ updateCategories();
+ updateApps();
+ notifyLists();
+ notifyActivity();
+
+ Log.d("FDroid", "Updated lists - " + allApps.size() + " allApps in total"
+ + " (update took " + (System.currentTimeMillis() - startTime)
+ + " ms)");
+ }
+
+ // Calculate the cutoff date we'll use for What's New and Recently
+ // Updated...
+ private Date calcMaxHistory() {
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(fdroidActivity.getBaseContext());
+ String daysPreference = prefs.getString("updateHistoryDays", "14");
+ int maxHistoryDays = Integer.parseInt(daysPreference);
+ Calendar recent = Calendar.getInstance();
+ recent.add(Calendar.DAY_OF_YEAR, -maxHistoryDays);
+ return recent.getTime();
+ }
+
+ // recentDate could really be calculated here, but this is just a hack so
+ // it doesn't need to be caluculated for every single app. The reason it
+ // isn't an instance variable is because the preferences may change, and
+ // we wouldn't know.
+ private boolean isInCategory(DB.App app, String category, Date recentDate) {
+ boolean isInCategory;
+ if (category.equals(categoryAll)) {
+ isInCategory = true;
+ } else if (category.equals(categoryWhatsNew)) {
+ if (app.added == null)
+ isInCategory = false;
+ else if (app.added.compareTo(recentDate) < 0)
+ isInCategory = false;
+ else
+ isInCategory = true;
+ } else if (category.equals(categoryRecentlyUpdated)) {
+ if (app.lastUpdated == null)
+ isInCategory = false;
+ // Don't include in the recently updated category if the
+ // 'update' was actually it being added.
+ else if (app.lastUpdated.compareTo(app.added) == 0)
+ isInCategory = false;
+ else if (app.lastUpdated.compareTo(recentDate) < 0)
+ isInCategory = false;
+ else
+ isInCategory = true;
+ } else {
+ isInCategory = category.equals(app.category);
+ }
+ return isInCategory;
+ }
+
+ // Returns false if the app list is empty and the fdroid activity decided
+ // to attempt updating it.
+ private boolean updateApps() {
+
+ allApps = ((FDroidApp)fdroidActivity.getApplication()).getApps();
+
+ if (allApps.isEmpty()) {
+ // If its the first time we've run the app, this should update
+ // the repos. If not, it will do nothing, presuming that the repos
+ // are invalid, the internet is stuffed, the sky has fallen, etc...
+ return fdroidActivity.updateEmptyRepos();
+ }
+
+ Date recentDate = calcMaxHistory();
+ AppFilter appFilter = new AppFilter(fdroidActivity);
+
+ Vector availApps = new Vector();
+ for (DB.App app : allApps) {
+
+ boolean isInCategory = isInCategory(app, currentCategory, recentDate);
+ boolean isFiltered = appFilter.filter(app);
+
+ // Add it to the list(s). Always to installed and updates, but
+ // only to available if it's not filtered.
+ if (!isFiltered && isInCategory)
+ availApps.add(app);
+ if (app.installedVersion != null) {
+ installedApps.addItem(app);
+ if (app.hasUpdates)
+ canUpgradeApps.addItem(app);
+ }
+ }
+
+ if (currentCategory.equals(categoryWhatsNew)) {
+ Collections.sort(availApps, new WhatsNewComparator());
+ } else if (currentCategory.equals(categoryRecentlyUpdated)) {
+ Collections.sort(availApps, new RecentlyUpdatedComparator());
+ }
+
+ for (DB.App app : availApps)
+ availableApps.addItem(app);
+
+ return true;
+ }
+
+ public void setCurrentCategory(String currentCategory) {
+ this.currentCategory = currentCategory;
+ }
+
+ static class WhatsNewComparator implements Comparator {
+ @Override
+ public int compare(DB.App lhs, DB.App rhs) {
+ return rhs.added.compareTo(lhs.added);
+ }
+ }
+
+ static class RecentlyUpdatedComparator implements Comparator {
+ @Override
+ public int compare(DB.App lhs, DB.App rhs) {
+ return rhs.lastUpdated.compareTo(lhs.lastUpdated);
+ }
+ }
+}
diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java
index 712362dfc..4ee964b8b 100644
--- a/src/org/fdroid/fdroid/FDroid.java
+++ b/src/org/fdroid/fdroid/FDroid.java
@@ -19,6 +19,7 @@
package org.fdroid.fdroid;
+import android.app.ActionBar;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
@@ -31,43 +32,34 @@ import org.fdroid.fdroid.R;
import android.R.drawable;
import android.app.AlertDialog;
-import android.app.ProgressDialog;
-import android.app.TabActivity;
import android.app.AlertDialog.Builder;
+import android.app.FragmentTransaction;
+import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.SharedPreferences;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
-import android.preference.PreferenceManager;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.Spinner;
-import android.widget.TabHost;
-import android.widget.TextView;
-import android.widget.Toast;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.*;
import android.widget.TabHost.TabSpec;
+import org.fdroid.fdroid.views.AppListFragmentPageAdapter;
-public class FDroid extends TabActivity implements OnItemClickListener,
- OnItemSelectedListener {
+public class FDroid extends FragmentActivity {
- private static final int REQUEST_APPDETAILS = 0;
- private static final int REQUEST_MANAGEREPOS = 1;
- private static final int REQUEST_PREFS = 2;
+ public static final int REQUEST_APPDETAILS = 0;
+ public static final int REQUEST_MANAGEREPOS = 1;
+ public static final int REQUEST_PREFS = 2;
public static final String EXTRA_TAB_UPDATE = "extraTab";
@@ -77,70 +69,53 @@ public class FDroid extends TabActivity implements OnItemClickListener,
private static final int ABOUT = Menu.FIRST + 3;
private static final int SEARCH = Menu.FIRST + 4;
- // Apps that are available to be installed
- private AppListAdapter apps_av = new AppListAdapter(this);
-
- // Apps that are installed
- private AppListAdapter apps_in = new AppListAdapter(this);
-
- // Apps that can be upgraded
- private AppListAdapter apps_up = new AppListAdapter(this);
-
- // Category list
- private ArrayAdapter categories;
- private String currentCategory = null;
-
private ProgressDialog pd;
- // Tags for the tabs
- private static final String TAB_Installed = "I";
- private static final String TAB_Available = "A";
- private static final String TAB_Updates = "U";
+ private ViewPager viewPager;
+ private AppListManager manager = null;
+
+ // Used by pre 3.0 devices which don't have an ActionBar...
private TabHost tabHost;
- private TabSpec ts;
- private TabSpec ts1;
- private TabSpec tsUp;
- private boolean triedEmptyUpdate;
-
- // List of apps.
- private Vector apps = null;
+ public AppListManager getManager() {
+ return manager;
+ }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
+ manager = new AppListManager(this);
setContentView(R.layout.fdroid);
-
- categories = new ArrayAdapter(this,
- android.R.layout.simple_spinner_item, new Vector());
- categories
- .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-
- tabHost = getTabHost();
+ createViews();
createTabs();
+ // Must be done *after* createViews, because it will involve a
+ // callback to update the tab label for the "update" tab. This
+ // will fail unless the tabs have actually been created.
+ repopulateViews();
+
Intent i = getIntent();
if (i.hasExtra("uri")) {
Intent call = new Intent(this, ManageRepo.class);
call.putExtra("uri", i.getStringExtra("uri"));
startActivityForResult(call, REQUEST_MANAGEREPOS);
} else if (i.hasExtra(EXTRA_TAB_UPDATE)) {
- boolean updateTab = i.getBooleanExtra(EXTRA_TAB_UPDATE, false);
- if (updateTab) {
- tabHost.setCurrentTab(2);
+ boolean showUpdateTab = i.getBooleanExtra(EXTRA_TAB_UPDATE, false);
+ if (showUpdateTab) {
+ selectTab(2);
}
}
-
- triedEmptyUpdate = false;
}
@Override
protected void onStart() {
super.onStart();
- populateLists();
+ }
+
+ protected void repopulateViews() {
+ manager.repopulateLists();
}
@Override
@@ -226,7 +201,6 @@ public class FDroid extends TabActivity implements OnItemClickListener,
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- triedEmptyUpdate = true;
switch (requestCode) {
case REQUEST_APPDETAILS:
break;
@@ -263,219 +237,142 @@ public class FDroid extends TabActivity implements OnItemClickListener,
&& (data.hasExtra("reset") || data.hasExtra("update"))) {
updateRepos();
} else {
- populateLists();
+ repopulateViews();
}
break;
}
}
- private void createTabs() {
- tabHost.clearAllTabs();
+ private void createViews() {
+ viewPager = (ViewPager)findViewById(R.id.main_pager);
+ AppListFragmentPageAdapter viewPageAdapter = new AppListFragmentPageAdapter(this);
+ viewPager.setAdapter(viewPageAdapter);
+ viewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() {
+ public void onPageSelected(int position) {
+ selectTab(position);
+ }
+ });
+ }
- // TabContentFactory that can generate the appropriate list for each
- // tab...
- TabHost.TabContentFactory tf = new TabHost.TabContentFactory() {
+ private void createTabs() {
+ if (Build.VERSION.SDK_INT >= 11) {
+ createActionBarTabs();
+ } else {
+ createOldTabs();
+ }
+ }
+
+ private void selectTab(int index) {
+ if (Build.VERSION.SDK_INT >= 11) {
+ getActionBar().setSelectedNavigationItem(index);
+ } else {
+ tabHost.setCurrentTab(index);
+ }
+ }
+
+ public void refreshUpdateTabLabel() {
+ final int INDEX = 2;
+ CharSequence text = viewPager.getAdapter().getPageTitle(INDEX);
+ if ( Build.VERSION.SDK_INT >= 11) {
+ getActionBar().getTabAt(INDEX).setText(text);
+ } else {
+ // Update the count on the 'Updates' tab to show the number available.
+ // This is quite unpleasant, but seems to be the only way to do it.
+ TextView textView = (TextView) tabHost.getTabWidget().getChildAt(2)
+ .findViewById(android.R.id.title);
+ textView.setText(text);
+ }
+ }
+
+ private void createActionBarTabs() {
+ final ActionBar actionBar = getActionBar();
+ final ViewPager pager = viewPager;
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ for (int i = 0; i < viewPager.getAdapter().getCount(); i ++) {
+ CharSequence label = viewPager.getAdapter().getPageTitle(i);
+ actionBar.addTab(
+ actionBar.newTab()
+ .setText(label)
+ .setTabListener(new ActionBar.TabListener() {
+ public void onTabSelected(ActionBar.Tab tab,
+ FragmentTransaction ft) {
+ pager.setCurrentItem(tab.getPosition());
+ }
+
+ @Override
+ public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
+ }
+
+ @Override
+ public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
+ }
+ }));
+ }
+ }
+
+ /**
+ * There is a bit of boiler-plate code required to get a TabWidget showing,
+ * which includes creating a TabHost, populating it with the TabWidget,
+ * and giving it a FrameLayout as a child. This will make the tabs have
+ * dummy empty contents and then hook them up to our ViewPager.
+ */
+ private void createOldTabs() {
+ tabHost = new TabHost(this);
+ tabHost.setLayoutParams(new TabHost.LayoutParams(
+ TabHost.LayoutParams.MATCH_PARENT, TabHost.LayoutParams.WRAP_CONTENT));
+
+ TabWidget tabWidget = new TabWidget(this);
+ tabWidget.setId(android.R.id.tabs);
+ tabHost.setLayoutParams(new TabHost.LayoutParams(
+ TabWidget.LayoutParams.MATCH_PARENT, TabWidget.LayoutParams.WRAP_CONTENT));
+
+ FrameLayout layout = new FrameLayout(this);
+ layout.setId(android.R.id.tabcontent);
+ layout.setLayoutParams(new TabWidget.LayoutParams(0, 0));
+
+ tabHost.addView(tabWidget);
+ tabHost.addView(layout);
+ tabHost.setup();
+
+ TabHost.TabContentFactory factory = new TabHost.TabContentFactory() {
@Override
public View createTabContent(String tag) {
-
- AppListAdapter ad;
- if (tag.equals(TAB_Installed))
- ad = apps_in;
- else if (tag.equals(TAB_Updates))
- ad = apps_up;
- else
- ad = apps_av;
-
- ListView lst = new ListView(FDroid.this);
- lst.setFastScrollEnabled(true);
- lst.setOnItemClickListener(FDroid.this);
- lst.setAdapter(ad);
-
- if (!tag.equals(TAB_Available))
- return lst;
-
- LinearLayout v = new LinearLayout(FDroid.this);
- v.setOrientation(LinearLayout.VERTICAL);
- Spinner cats = new Spinner(FDroid.this);
- // Giving it an ID lets the default save/restore state
- // functionality do its stuff.
- cats.setId(R.id.categorySpinner);
- cats.setAdapter(categories);
- cats.setOnItemSelectedListener(FDroid.this);
- v.addView(cats, new LayoutParams(
- LinearLayout.LayoutParams.FILL_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT));
- v.addView(lst, new LayoutParams(
- LinearLayout.LayoutParams.FILL_PARENT,
- LinearLayout.LayoutParams.FILL_PARENT));
- return v;
+ return new View(FDroid.this);
}
};
- // Create the tab of installed apps...
- ts = tabHost.newTabSpec(TAB_Installed);
- ts.setIndicator(getString(R.string.tab_installed), getResources()
- .getDrawable(drawable.star_off));
- ts.setContent(tf);
+ TabSpec availableTabSpec = tabHost.newTabSpec("available")
+ .setIndicator(
+ getString(R.string.tab_noninstalled),
+ getResources().getDrawable(android.R.drawable.ic_input_add))
+ .setContent(factory);
- // Create the tab of apps with updates...
- tsUp = tabHost.newTabSpec(TAB_Updates);
- tsUp.setIndicator(getString(R.string.tab_updates), getResources()
- .getDrawable(drawable.star_on));
- tsUp.setContent(tf);
+ TabSpec installedTabSpec = tabHost.newTabSpec("installed")
+ .setIndicator(
+ getString(R.string.tab_installed),
+ getResources().getDrawable(android.R.drawable.star_off))
+ .setContent(factory);
- // Create the tab of available apps...
- ts1 = tabHost.newTabSpec(TAB_Available);
- ts1.setIndicator(getString(R.string.tab_noninstalled), getResources()
- .getDrawable(drawable.ic_input_add));
- ts1.setContent(tf);
+ TabSpec canUpdateTabSpec = tabHost.newTabSpec("canUpdate")
+ .setIndicator(
+ getString(R.string.tab_updates),
+ getResources().getDrawable(android.R.drawable.star_on))
+ .setContent(factory);
- tabHost.addTab(ts1);
- tabHost.addTab(ts);
- tabHost.addTab(tsUp);
+ tabHost.addTab(availableTabSpec);
+ tabHost.addTab(installedTabSpec);
+ tabHost.addTab(canUpdateTabSpec);
- }
+ LinearLayout contentView = (LinearLayout)findViewById(R.id.fdroid_layout);
+ contentView.addView(tabHost, 0);
- // Populate the lists.
- private void populateLists() {
-
- apps_in.clear();
- apps_av.clear();
- apps_up.clear();
- categories.clear();
-
- long startTime = System.currentTimeMillis();
-
- DB db;
- String cat_all, cat_whatsnew, cat_recentlyupdated;
- try {
- db = DB.getDB();
-
- // Populate the category list with the real categories, and the
- // locally generated meta-categories for "All", "What's New" and
- // "Recently Updated"...
- cat_all = getString(R.string.category_all);
- cat_whatsnew = getString(R.string.category_whatsnew);
- cat_recentlyupdated = getString(R.string.category_recentlyupdated);
- categories.add(cat_whatsnew);
- categories.add(cat_recentlyupdated);
- categories.add(cat_all);
- for (String s : db.getCategories()) {
- categories.add(s);
+ tabHost.setOnTabChangedListener( new TabHost.OnTabChangeListener() {
+ @Override
+ public void onTabChanged(String tabId) {
+ viewPager.setCurrentItem(tabHost.getCurrentTab());
}
- if (currentCategory == null)
- currentCategory = cat_whatsnew;
-
- } finally {
- DB.releaseDB();
- }
-
- apps = ((FDroidApp) getApplication()).getApps();
-
- if (apps.isEmpty()) {
- // Don't attempt this more than once - we may have invalid
- // repositories.
- if (triedEmptyUpdate)
- return;
- // If there are no apps, update from the repos - it must be a
- // new installation.
- Log.d("FDroid", "Empty app list forces repo update");
- updateRepos();
- triedEmptyUpdate = true;
- return;
- }
-
- // Calculate the cutoff date we'll use for What's New and Recently
- // Updated...
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(getBaseContext());
- String sint = prefs.getString("updateHistoryDays", "14");
- int history_days = Integer.parseInt(sint);
-
- Calendar recent = Calendar.getInstance();
- recent.add(Calendar.DAY_OF_YEAR, -history_days);
- Date recentDate = recent.getTime();
-
- AppFilter appfilter = new AppFilter(this);
-
- boolean incat;
- Vector availapps = new Vector();
- for (DB.App app : apps) {
- if (currentCategory.equals(cat_all)) {
- incat = true;
- } else if (currentCategory.equals(cat_whatsnew)) {
- if (app.added == null)
- incat = false;
- else if (app.added.compareTo(recentDate) < 0)
- incat = false;
- else
- incat = true;
- } else if (currentCategory.equals(cat_recentlyupdated)) {
- if (app.lastUpdated == null)
- incat = false;
- // Don't include in the recently updated category if the
- // 'update' was actually it being added.
- else if (app.lastUpdated.compareTo(app.added) == 0)
- incat = false;
- else if (app.lastUpdated.compareTo(recentDate) < 0)
- incat = false;
- else
- incat = true;
- } else {
- incat = currentCategory.equals(app.category);
- }
-
- boolean filtered = appfilter.filter(app);
-
- // Add it to the list(s). Always to installed and updates, but
- // only to available if it's not filtered.
- if (!filtered && incat)
- availapps.add(app);
- if (app.installedVersion != null) {
- apps_in.addItem(app);
- if (app.hasUpdates)
- apps_up.addItem(app);
- }
- }
-
- if (currentCategory.equals(cat_whatsnew)) {
- class WhatsNewComparator implements Comparator {
- @Override
- public int compare(App lhs, App rhs) {
- return rhs.added.compareTo(lhs.added);
- }
- }
- Collections.sort(availapps, new WhatsNewComparator());
- } else if (currentCategory.equals(cat_recentlyupdated)) {
- class UpdatedComparator implements Comparator {
- @Override
- public int compare(App lhs, App rhs) {
- return rhs.lastUpdated.compareTo(lhs.lastUpdated);
- }
- }
- Collections.sort(availapps, new UpdatedComparator());
- }
- for (DB.App app : availapps)
- apps_av.addItem(app);
-
- // Update the count on the 'Updates' tab to show the number available.
- // This is quite unpleasant, but seems to be the only way to do it.
- TextView uptext = (TextView) tabHost.getTabWidget().getChildAt(2)
- .findViewById(android.R.id.title);
- uptext.setText(getString(R.string.tab_updates) + " ("
- + Integer.toString(apps_up.getCount()) + ")");
-
- // Tell the lists that the data behind the adapter has changed, so
- // they can refresh...
- apps_av.notifyDataSetChanged();
- apps_in.notifyDataSetChanged();
- apps_up.notifyDataSetChanged();
- categories.notifyDataSetChanged();
-
- Log.d("FDroid", "Updated lists - " + apps.size() + " apps in total"
- + " (update took " + (System.currentTimeMillis() - startTime)
- + " ms)");
+ });
}
// For receiving results from the UpdateService when we've told it to
@@ -491,7 +388,7 @@ public class FDroid extends TabActivity implements OnItemClickListener,
Toast.makeText(FDroid.this, resultData.getString("errmsg"),
Toast.LENGTH_LONG).show();
} else {
- populateLists();
+ repopulateViews();
}
if (pd.isShowing())
pd.dismiss();
@@ -500,10 +397,32 @@ public class FDroid extends TabActivity implements OnItemClickListener,
private UpdateReceiver mUpdateReceiver;
+ /**
+ * The first time the app is run, we will have an empty app list.
+ * If this is the case, we will attempt to update with the default repo.
+ * However, if we have tried this at least once, then don't try to do
+ * it automatically again, because the repos or internet connection may
+ * be bad.
+ */
+ public boolean updateEmptyRepos() {
+ final String TRIED_EMPTY_UPDATE = "triedEmptyUpdate";
+ boolean hasTriedEmptyUpdate = getPreferences(MODE_PRIVATE).getBoolean(TRIED_EMPTY_UPDATE, false);
+ if (!hasTriedEmptyUpdate) {
+ Log.d("FDroid", "Empty app list, and we haven't done an update yet. Forcing repo update.");
+ getPreferences(MODE_PRIVATE).edit().putBoolean(TRIED_EMPTY_UPDATE, true).apply();
+ updateRepos();
+ return true;
+ } else {
+ Log.d("FDroid", "Empty app list, but it looks like we've had an update previously. Will not force repo update.");
+ return false;
+ }
+ }
+
// Force a repo update now. A progress dialog is shown and the UpdateService
// is told to do the update, which will result in the database changing. The
// UpdateReceiver class should get told when this is finished.
- private void updateRepos() {
+ public void updateRepos() {
+
pd = ProgressDialog.show(this, getString(R.string.process_wait_title),
getString(R.string.process_update_msg), true, true);
pd.setIcon(android.R.drawable.ic_dialog_info);
@@ -515,36 +434,4 @@ public class FDroid extends TabActivity implements OnItemClickListener,
startService(intent);
}
- public void onItemSelected(AdapterView> parent, View view, int pos,
- long id) {
- currentCategory = parent.getItemAtPosition(pos).toString();
- populateLists();
- }
-
- public void onNothingSelected(AdapterView> parent) {
- // We always have at least "All"
- }
-
- // Handler for a click on one of the items in an application list. Pops
- // up a dialog that shows the details of the application and all its
- // available versions, with buttons to allow installation etc.
- public void onItemClick(AdapterView> arg0, View arg1, final int arg2,
- long arg3) {
-
- final DB.App app;
- String curtab = tabHost.getCurrentTabTag();
- if (curtab.equalsIgnoreCase(TAB_Installed)) {
- app = (DB.App) apps_in.getItem(arg2);
- } else if (curtab.equalsIgnoreCase(TAB_Updates)) {
- app = (DB.App) apps_up.getItem(arg2);
- } else {
- app = (DB.App) apps_av.getItem(arg2);
- }
-
- Intent intent = new Intent(this, AppDetails.class);
- intent.putExtra("appid", app.id);
- startActivityForResult(intent, REQUEST_APPDETAILS);
-
- }
-
}
diff --git a/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java b/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java
new file mode 100644
index 000000000..e817ebd29
--- /dev/null
+++ b/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java
@@ -0,0 +1,64 @@
+package org.fdroid.fdroid.views;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import org.fdroid.fdroid.AppListManager;
+import org.fdroid.fdroid.FDroid;
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.views.fragments.AvailableAppsFragment;
+import org.fdroid.fdroid.views.fragments.CanUpdateAppsFragment;
+import org.fdroid.fdroid.views.fragments.InstalledAppsFragment;
+
+/**
+ * Used by the FDroid activity in conjunction with its ViewPager to support
+ * swiping of tabs for both old devices (< 3.0) and new devices.
+ */
+public class AppListFragmentPageAdapter extends FragmentPagerAdapter {
+
+ private FDroid parent = null;
+
+ public AppListFragmentPageAdapter(FDroid parent) {
+ super(parent.getSupportFragmentManager());
+ this.parent = parent;
+ }
+
+ @Override
+ public Fragment getItem(int i) {
+ Fragment fragment = null;
+ if ( i == 0 ) {
+ fragment = new AvailableAppsFragment();
+ } else if ( i == 1 ) {
+ fragment = new InstalledAppsFragment();
+ } else if ( i == 2 ) {
+ fragment = new CanUpdateAppsFragment();
+ }
+ return fragment;
+ }
+
+ @Override
+ public int getCount() {
+ return 3;
+ }
+
+ public String getPageTitle(int i) {
+ switch(i) {
+ case 0:
+ return parent.getString(R.string.tab_noninstalled);
+ case 1:
+ return parent.getString(R.string.tab_installed);
+ case 2:
+ String updates = parent.getString(R.string.tab_updates);
+ updates += " (" + parent.getManager().getCanUpdateAdapter().getCount() + ")";
+ return updates;
+ default:
+ return "";
+ }
+ }
+
+}
diff --git a/src/org/fdroid/fdroid/views/AppListView.java b/src/org/fdroid/fdroid/views/AppListView.java
new file mode 100644
index 000000000..4fbe716ed
--- /dev/null
+++ b/src/org/fdroid/fdroid/views/AppListView.java
@@ -0,0 +1,41 @@
+package org.fdroid.fdroid.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+/**
+ * There are three main app-lists in the UI:
+ * - Available
+ * - Installed
+ * - Apps which can be updated
+ * This class provides a View which knows about these app lists, but can have
+ * different contents (e.g. a drop down list of categories). It allows us to
+ * get a reference to the selected item in the FDroid Activity, without having
+ * to know which list we are actually looking at.
+ */
+public class AppListView extends LinearLayout {
+
+ private ListView appList;
+
+ public AppListView(Context context) {
+ super(context);
+ }
+
+ public AppListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AppListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setAppList(ListView appList) {
+ this.appList = appList;
+ }
+
+ public ListView getAppList() {
+ return appList;
+ }
+}
diff --git a/src/org/fdroid/fdroid/views/fragments/AppListFragment.java b/src/org/fdroid/fdroid/views/fragments/AppListFragment.java
new file mode 100644
index 000000000..6ad8f9588
--- /dev/null
+++ b/src/org/fdroid/fdroid/views/fragments/AppListFragment.java
@@ -0,0 +1,66 @@
+package org.fdroid.fdroid.views.fragments;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import org.fdroid.fdroid.*;
+import org.fdroid.fdroid.views.AppListView;
+
+abstract class AppListFragment extends Fragment implements AdapterView.OnItemClickListener {
+
+ private AppListManager appListManager;
+ private FDroid parent;
+
+ protected abstract AppListAdapter getAppListAdapter();
+
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ try {
+ parent = (FDroid)activity;
+ } catch (ClassCastException e) {
+ // I know fragments are meant to be activity agnostic, but I can't
+ // think of a better way to share the one application list between
+ // all three app list fragments.
+ throw new RuntimeException(
+ "AppListFragment can only be attached to FDroid activity. " +
+ "Here it was attached to a " + activity.getClass() );
+ }
+ }
+
+ public AppListManager getAppListManager() {
+ return parent.getManager();
+ }
+
+ protected AppListView createPlainAppList() {
+ AppListView view = new AppListView(getActivity());
+ ListView list = createAppListView();
+ view.addView(
+ list,
+ new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ view.setAppList(list);
+ return view;
+ }
+
+ protected ListView createAppListView() {
+ ListView list = new ListView(getActivity());
+ list.setFastScrollEnabled(true);
+ list.setOnItemClickListener(this);
+ list.setAdapter(getAppListAdapter());
+ return list;
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ final DB.App app = (DB.App)getAppListAdapter().getItem(position);
+ Intent intent = new Intent(getActivity(), AppDetails.class);
+ intent.putExtra("appid", app.id);
+ startActivityForResult(intent, FDroid.REQUEST_APPDETAILS);
+ }
+}
diff --git a/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java
new file mode 100644
index 000000000..517476e79
--- /dev/null
+++ b/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java
@@ -0,0 +1,67 @@
+package org.fdroid.fdroid.views.fragments;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.*;
+import org.fdroid.fdroid.AppListAdapter;
+import org.fdroid.fdroid.AppListManager;
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.views.AppListView;
+
+public class AvailableAppsFragment extends AppListFragment implements AdapterView.OnItemSelectedListener {
+
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ AppListView view = new AppListView(getActivity());
+ view.setOrientation(LinearLayout.VERTICAL);
+
+ Spinner spinner = new Spinner(getActivity());
+ // Giving it an ID lets the default save/restore state
+ // functionality do its stuff.
+ spinner.setId(R.id.categorySpinner);
+ spinner.setAdapter(getAppListManager().getCategoriesAdapter());
+ spinner.setOnItemSelectedListener(this);
+
+ view.addView(
+ spinner,
+ new ViewGroup.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+
+ ListView list = createAppListView();
+ view.setAppList(list);
+ view.addView(
+ list,
+ new ViewGroup.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT));
+
+ return view;
+ }
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ }
+
+ public void onItemSelected(AdapterView> parent, View view, int pos,
+ long id) {
+ getAppListManager().setCurrentCategory(parent.getItemAtPosition(pos).toString());
+ getAppListManager().repopulateLists();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+
+ }
+
+ @Override
+ protected AppListAdapter getAppListAdapter() {
+ return getAppListManager().getAvailableAdapter();
+ }
+}
diff --git a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java
new file mode 100644
index 000000000..2b864aadd
--- /dev/null
+++ b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java
@@ -0,0 +1,20 @@
+package org.fdroid.fdroid.views.fragments;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import org.fdroid.fdroid.AppListAdapter;
+
+public class CanUpdateAppsFragment extends AppListFragment {
+
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return createPlainAppList();
+ }
+
+ @Override
+ protected AppListAdapter getAppListAdapter() {
+ return getAppListManager().getCanUpdateAdapter();
+ }
+}
diff --git a/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java
new file mode 100644
index 000000000..e6b48f35f
--- /dev/null
+++ b/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java
@@ -0,0 +1,20 @@
+package org.fdroid.fdroid.views.fragments;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import org.fdroid.fdroid.AppListAdapter;
+
+public class InstalledAppsFragment extends AppListFragment {
+
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return createPlainAppList();
+ }
+
+ @Override
+ protected AppListAdapter getAppListAdapter() {
+ return getAppListManager().getInstalledAdapter();
+ }
+}