diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a86940e47..261ba4ae3 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -84,8 +84,8 @@ android:value="FDroid" /> + android:paddingLeft="@dimen/applist_summary_padding" /> diff --git a/res/values/dimen.xml b/res/values/dimen.xml new file mode 100644 index 000000000..8ab611650 --- /dev/null +++ b/res/values/dimen.xml @@ -0,0 +1,4 @@ + + + 6dp + \ No newline at end of file diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java index 84276c72e..8dc3bb667 100644 --- a/src/org/fdroid/fdroid/FDroid.java +++ b/src/org/fdroid/fdroid/FDroid.java @@ -22,7 +22,6 @@ package org.fdroid.fdroid; import android.content.*; import android.content.res.Configuration; import android.support.v4.view.MenuItemCompat; -import org.fdroid.fdroid.R; import android.app.AlertDialog; import android.app.AlertDialog.Builder; @@ -149,7 +148,7 @@ public class FDroid extends FragmentActivity { return true; case PREFERENCES: - Intent prefs = new Intent(getBaseContext(), Preferences.class); + Intent prefs = new Intent(getBaseContext(), PreferencesActivity.class); startActivityForResult(prefs, REQUEST_PREFS); return true; diff --git a/src/org/fdroid/fdroid/FDroidApp.java b/src/org/fdroid/fdroid/FDroidApp.java index 6f2de086d..98c34d1d5 100644 --- a/src/org/fdroid/fdroid/FDroidApp.java +++ b/src/org/fdroid/fdroid/FDroidApp.java @@ -36,6 +36,11 @@ public class FDroidApp extends Application { public void onCreate() { super.onCreate(); + // Needs to be setup before anything else tries to access it. + // Perhaps the constructor is a better place, but then again, + // it is more deterministic as to when this gets called... + Preferences.setup(this); + // Clear cached apk files. We used to just remove them after they'd // been installed, but this causes problems for proprietary gapps // users since the introduction of verification (on pre-4.2 Android), diff --git a/src/org/fdroid/fdroid/Preferences.java b/src/org/fdroid/fdroid/Preferences.java index dbbb26046..363bf2bee 100644 --- a/src/org/fdroid/fdroid/Preferences.java +++ b/src/org/fdroid/fdroid/Preferences.java @@ -1,59 +1,104 @@ -/* - * Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 3 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - package org.fdroid.fdroid; -import java.io.File; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; -import android.app.ActionBar; -import android.content.Intent; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceActivity; -import android.preference.Preference.OnPreferenceClickListener; -import android.widget.Toast; -import org.fdroid.fdroid.compat.ActionBarCompat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.prefs.PreferenceChangeListener; -public class Preferences extends PreferenceActivity implements - OnPreferenceClickListener { +/** + * Handles shared preferences for FDroid, looking after the names of + * preferences, default values and caching. Needs to be setup in the FDroidApp + * (using {@link Preferences#setup(android.content.Context)} before it gets + * accessed via the {@link org.fdroid.fdroid.Preferences#get()} + * singleton method. + */ +public class Preferences implements SharedPreferences.OnSharedPreferenceChangeListener { + + private final SharedPreferences preferences; + + private Preferences(Context context) { + preferences = PreferenceManager.getDefaultSharedPreferences(context); + preferences.registerOnSharedPreferenceChangeListener(this); + } + + private static final String PREF_COMPACT_LAYOUT = "compactlayout"; + private static final boolean DEFAULT_COMPACT_LAYOUT = false; + + private boolean compactLayout = DEFAULT_COMPACT_LAYOUT; + + private Map initialized = new HashMap(); + private List compactLayoutListeners = new ArrayList(); + + private boolean isInitialized(String key) { + return initialized.containsKey(key) && initialized.get(key); + } + + private void initialize(String key) { + initialized.put(key, true); + } + + private void uninitialize(String key) { + initialized.put(key, false); + } + + public boolean hasCompactLayout() { + if (!isInitialized(PREF_COMPACT_LAYOUT)) { + initialize(PREF_COMPACT_LAYOUT); + compactLayout = preferences.getBoolean(PREF_COMPACT_LAYOUT, DEFAULT_COMPACT_LAYOUT); + } + return compactLayout; + } + + public void registerCompactLayoutChangeListener(ChangeListener listener) { + compactLayoutListeners.add(listener); + } + + public void unregisterCompactLayoutChangeListener(ChangeListener listener) { + compactLayoutListeners.remove(listener); + } @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true); - addPreferencesFromResource(R.xml.preferences); - for (String prefkey : new String[] { "ignoreTouchscreen", - "showIncompatible" }) { - Preference pref = findPreference(prefkey); - pref.setOnPreferenceClickListener(this); + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + Log.d("FDroid", "Invalidating preference '" + key + "'."); + uninitialize(key); + + if (key.equals(PREF_COMPACT_LAYOUT)) { + for ( ChangeListener listener : compactLayoutListeners ) { + listener.onPreferenceChange(); + } } } - @Override - public boolean onPreferenceClick(Preference preference) { - // Currently only one action is returned. - //String key = preference.getKey(); - //if (key.equals("ignoreTouchscreen") || key.equals("showIncompatible")) { - Intent ret = new Intent(); - ret.putExtra("update", true); - setResult(RESULT_OK, ret); - return true; - //} + private static Preferences instance; + + public static void setup(Context context) { + if (instance != null) { + String error = "Attempted to reinitialize preferences after it " + + "has already been initialized in FDroidApp"; + Log.e("FDroid", error); + throw new RuntimeException(error); + } + instance = new Preferences(context); + } + + public static Preferences get() { + if (instance == null) { + String error = "Attempted to access preferences before it " + + "has been initialized in FDroidApp"; + Log.e("FDroid", error); + throw new RuntimeException(error); + } + return instance; + } + + public static interface ChangeListener { + public void onPreferenceChange(); } } diff --git a/src/org/fdroid/fdroid/PreferencesActivity.java b/src/org/fdroid/fdroid/PreferencesActivity.java new file mode 100644 index 000000000..c5f71766a --- /dev/null +++ b/src/org/fdroid/fdroid/PreferencesActivity.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.fdroid.fdroid; + +import java.io.File; + +import android.app.ActionBar; +import android.content.Intent; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.Preference.OnPreferenceClickListener; +import android.widget.Toast; +import org.fdroid.fdroid.compat.ActionBarCompat; + +public class PreferencesActivity extends PreferenceActivity implements + OnPreferenceClickListener { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true); + addPreferencesFromResource(R.xml.preferences); + for (String prefkey : new String[] { "ignoreTouchscreen", + "showIncompatible" }) { + Preference pref = findPreference(prefkey); + pref.setOnPreferenceClickListener(this); + } + } + + @Override + public boolean onPreferenceClick(Preference preference) { + // Currently only one action is returned. + //String key = preference.getKey(); + //if (key.equals("ignoreTouchscreen") || key.equals("showIncompatible")) { + Intent ret = new Intent(); + ret.putExtra("update", true); + setResult(RESULT_OK, ret); + return true; + //} + } + +} diff --git a/src/org/fdroid/fdroid/views/AppListAdapter.java b/src/org/fdroid/fdroid/views/AppListAdapter.java index 32fdba919..640493a46 100644 --- a/src/org/fdroid/fdroid/views/AppListAdapter.java +++ b/src/org/fdroid/fdroid/views/AppListAdapter.java @@ -13,6 +13,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.*; import org.fdroid.fdroid.DB; +import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; import org.fdroid.fdroid.compat.LayoutCompat; @@ -21,9 +22,6 @@ abstract public class AppListAdapter extends BaseAdapter { private List items = new ArrayList(); private Context mContext; - private boolean prefCompactLayoutInitialized = false; - private boolean prefCompactLayout = false; - public AppListAdapter(Context context) { mContext = context; } @@ -55,73 +53,48 @@ abstract public class AppListAdapter extends BaseAdapter { return position; } - protected boolean hasCompactLayout() { - if (!prefCompactLayoutInitialized) { - prefCompactLayoutInitialized = true; - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); - prefCompactLayout = prefs.getBoolean("compactlayout", false); - } - return prefCompactLayout; - } - @Override public View getView(int position, View convertView, ViewGroup parent) { - boolean init = false; + + boolean compact = Preferences.get().hasCompactLayout(); + DB.App app = items.get(position); if (convertView == null) { convertView = ((LayoutInflater) mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.applistitem, null); - init = true; } TextView name = (TextView) convertView.findViewById(R.id.name); TextView summary = (TextView) convertView.findViewById(R.id.summary); TextView status = (TextView) convertView.findViewById(R.id.status); TextView license = (TextView) convertView.findViewById(R.id.license); - - DB.App app = items.get(position); - - status.setText(getVersionInfo(app)); - license.setText(app.license); + ImageView icon = (ImageView) convertView.findViewById(R.id.icon); + LinearLayout iconContainer = (LinearLayout)convertView.findViewById(R.id.status_icons); + ImageView iconInstalled = (ImageView) convertView.findViewById(R.id.icon_status_installed); + ImageView iconUpdates = (ImageView) convertView.findViewById(R.id.icon_status_has_updates); name.setText(app.name); summary.setText(app.summary); - ImageView icon = (ImageView) convertView.findViewById(R.id.icon); - File icn = new File(DB.getIconsPath(mContext), app.icon); - if (icn.exists() && icn.length() > 0) { - new Uri.Builder().build(); - icon.setImageURI(Uri.parse(icn.getPath())); + layoutSummary(summary); + layoutIcon(icon, app); + + int visibleOnCompact = compact ? View.VISIBLE : View.GONE; + int notVisibleOnCompact = compact ? View.GONE : View.VISIBLE; + + iconContainer.setVisibility(visibleOnCompact); + status.setVisibility(notVisibleOnCompact); + license.setVisibility(notVisibleOnCompact); + + if (!compact) { + status.setText(getVersionInfo(app)); + license.setText(app.license); } else { - icon.setImageResource(android.R.drawable.sym_def_app_icon); - } + status.setText(""); + license.setText(""); - ImageView iconInstalled = (ImageView) convertView.findViewById(R.id.icon_status_installed); - ImageView iconUpdates = (ImageView) convertView.findViewById(R.id.icon_status_has_updates); - - if (init) { - - if (hasCompactLayout()) { - - iconInstalled.setImageResource(R.drawable.ic_cab_done_holo_dark); - iconUpdates.setImageResource(R.drawable.ic_menu_refresh); - - status.setVisibility(View.GONE); - license.setVisibility(View.GONE); - - RelativeLayout.LayoutParams summaryLayout = - new RelativeLayout.LayoutParams( - RelativeLayout.LayoutParams.WRAP_CONTENT, - RelativeLayout.LayoutParams.WRAP_CONTENT); - summaryLayout.addRule(RelativeLayout.BELOW, R.id.name); - summaryLayout.addRule(LayoutCompat.RelativeLayout.END_OF, R.id.icon); - summary.setLayoutParams(summaryLayout); - summary.setPadding(0,0,0,0); - - } - } - - if (hasCompactLayout()) { + iconInstalled.setImageResource(R.drawable.ic_cab_done_holo_dark); + iconUpdates.setImageResource(R.drawable.ic_menu_refresh); if (app.hasUpdates && showStatusUpdate()) { iconUpdates.setVisibility(View.VISIBLE); @@ -145,6 +118,54 @@ abstract public class AppListAdapter extends BaseAdapter { return convertView; } + /** + * If an icon exists on disc, we'll use that, otherwise default to the + * plain android app icon. + */ + private void layoutIcon(ImageView iconView, DB.App app) { + + File icn = new File(DB.getIconsPath(mContext), app.icon); + if (icn.exists() && icn.length() > 0) { + new Uri.Builder().build(); + iconView.setImageURI(Uri.parse(icn.getPath())); + } else { + iconView.setImageResource(android.R.drawable.sym_def_app_icon); + } + + } + + /** + * In compact view, the summary sites next to the icon, below the name. + * In non-compact view, it sits under the icon, with some padding pushing + * it away from the left margin. + */ + private void layoutSummary(TextView summaryView) { + + if (Preferences.get().hasCompactLayout()) { + + RelativeLayout.LayoutParams summaryLayout = + new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT); + summaryLayout.addRule(RelativeLayout.BELOW, R.id.name); + summaryLayout.addRule(LayoutCompat.RelativeLayout.END_OF, R.id.icon); + summaryView.setLayoutParams(summaryLayout); + summaryView.setPadding(0,0,0,0); + + } else { + + RelativeLayout.LayoutParams summaryLayout = + new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.WRAP_CONTENT); + summaryLayout.addRule(RelativeLayout.BELOW, R.id.icon); + summaryView.setLayoutParams(summaryLayout); + float padding = mContext.getResources().getDimension(R.dimen.applist_summary_padding); + summaryView.setPadding((int)padding, 0, 0, 0); + + } + } + private String getVersionInfo(DB.App app) { StringBuilder version = new StringBuilder(); if (app.installedVersion != null) { diff --git a/src/org/fdroid/fdroid/views/fragments/AppListFragment.java b/src/org/fdroid/fdroid/views/fragments/AppListFragment.java index 249b2f438..46e3a5d2f 100644 --- a/src/org/fdroid/fdroid/views/fragments/AppListFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/AppListFragment.java @@ -2,6 +2,7 @@ package org.fdroid.fdroid.views.fragments; import android.app.Activity; import android.content.Intent; +import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.View; import android.view.ViewGroup; @@ -11,12 +12,24 @@ import org.fdroid.fdroid.*; import org.fdroid.fdroid.views.AppListAdapter; import org.fdroid.fdroid.views.AppListView; -abstract class AppListFragment extends Fragment implements AdapterView.OnItemClickListener { +abstract class AppListFragment extends Fragment implements AdapterView.OnItemClickListener, Preferences.ChangeListener { private FDroid parent; protected abstract AppListAdapter getAppListAdapter(); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Preferences.get().registerCompactLayoutChangeListener(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Preferences.get().unregisterCompactLayoutChangeListener(this); + } + public void onAttach(Activity activity) { super.onAttach(activity); try { @@ -62,4 +75,9 @@ abstract class AppListFragment extends Fragment implements AdapterView.OnItemCli intent.putExtra("appid", app.id); startActivityForResult(intent, FDroid.REQUEST_APPDETAILS); } + + @Override + public void onPreferenceChange() { + getAppListAdapter().notifyDataSetChanged(); + } } diff --git a/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java index 541267ae3..f7cd4263d 100644 --- a/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java @@ -40,14 +40,6 @@ public class AvailableAppsFragment extends AppListFragment implements AdapterVie 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) { String category = parent.getItemAtPosition(pos).toString();