Safer preference managing. Fix "compact layout requires reload".

Previously, everybody had to remember the preference name and the
default value. If it was ever changed, this would have to be updated
everywhere. Now, the Preferences class is responsible for talking to the
SharedPreferences functionality of ANdroid.

I've started with just the compactlayout preference, because
that is what I required for this fix.
This commit is contained in:
Peter Serwylo 2013-07-26 09:10:32 +10:00
parent bc77804eee
commit af2a9ecfb6
10 changed files with 255 additions and 112 deletions

View File

@ -84,8 +84,8 @@
android:value="FDroid" />
</activity>
<activity
android:name="Preferences"
android:label="Preferences"
android:name=".PreferencesActivity"
android:parentActivityName="FDroid" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"

View File

@ -75,6 +75,6 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/icon"
android:paddingLeft="6dp" />
android:paddingLeft="@dimen/applist_summary_padding" />
</RelativeLayout>

4
res/values/dimen.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="applist_summary_padding">6dp</dimen>
</resources>

View File

@ -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;

View File

@ -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),

View File

@ -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<String,Boolean> initialized = new HashMap<String,Boolean>();
private List<ChangeListener> compactLayoutListeners = new ArrayList<ChangeListener>();
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();
}
}

View File

@ -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;
//}
}
}

View File

@ -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<DB.App> items = new ArrayList<DB.App>();
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) {

View File

@ -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();
}
}

View File

@ -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();