mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1064304 - Part 3: Persist group collapsed/expanded state in SharedPreferences. r=rnewman
This commit is contained in:
parent
319ee1a78a
commit
3135852912
@ -8,6 +8,7 @@ package org.mozilla.gecko.home;
|
|||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.RemoteTabsExpandableListAdapter;
|
import org.mozilla.gecko.RemoteTabsExpandableListAdapter;
|
||||||
import org.mozilla.gecko.TabsAccessor;
|
import org.mozilla.gecko.TabsAccessor;
|
||||||
@ -52,6 +53,10 @@ public class RemoteTabsExpandableListFragment extends HomeFragment {
|
|||||||
|
|
||||||
private static final String[] STAGES_TO_SYNC_ON_REFRESH = new String[] { "clients", "tabs" };
|
private static final String[] STAGES_TO_SYNC_ON_REFRESH = new String[] { "clients", "tabs" };
|
||||||
|
|
||||||
|
// Maintain group collapsed and hidden state.
|
||||||
|
// Only accessed from the UI thread.
|
||||||
|
private static RemoteTabsExpandableListState sState;
|
||||||
|
|
||||||
// Adapter for the list of remote tabs.
|
// Adapter for the list of remote tabs.
|
||||||
private RemoteTabsExpandableListAdapter mAdapter;
|
private RemoteTabsExpandableListAdapter mAdapter;
|
||||||
|
|
||||||
@ -117,9 +122,15 @@ public class RemoteTabsExpandableListFragment extends HomeFragment {
|
|||||||
mList.setOnGroupClickListener(new OnGroupClickListener() {
|
mList.setOnGroupClickListener(new OnGroupClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
|
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
|
||||||
// Since we don't indicate the expansion state yet, don't allow
|
final ExpandableListAdapter adapter = parent.getExpandableListAdapter();
|
||||||
// collapsing groups at all.
|
final RemoteClient client = (RemoteClient) adapter.getGroup(groupPosition);
|
||||||
return true;
|
if (client != null) {
|
||||||
|
// After we process this click, the group's expanded state will have flipped.
|
||||||
|
sState.setClientCollapsed(client.guid, mList.isGroupExpanded(groupPosition));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want the system to handle the click, expanding or collapsing as necessary.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -165,6 +176,15 @@ public class RemoteTabsExpandableListFragment extends HomeFragment {
|
|||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
// This races when multiple Fragments are created. That's okay: one
|
||||||
|
// will win, and thereafter, all will be okay. If we create and then
|
||||||
|
// drop an instance the shared SharedPreferences backing all the
|
||||||
|
// instances will maintain the state for us. Since everything happens on
|
||||||
|
// the UI thread, this doesn't even need to be volatile.
|
||||||
|
if (sState == null) {
|
||||||
|
sState = new RemoteTabsExpandableListState(GeckoSharedPrefs.forProfile(getActivity()));
|
||||||
|
}
|
||||||
|
|
||||||
// Intialize adapter
|
// Intialize adapter
|
||||||
mAdapter = new RemoteTabsExpandableListAdapter(R.layout.home_remote_tabs_group, R.layout.home_remote_tabs_child, null);
|
mAdapter = new RemoteTabsExpandableListAdapter(R.layout.home_remote_tabs_group, R.layout.home_remote_tabs_child, null);
|
||||||
mList.setAdapter(mAdapter);
|
mList.setAdapter(mAdapter);
|
||||||
@ -176,9 +196,16 @@ public class RemoteTabsExpandableListFragment extends HomeFragment {
|
|||||||
|
|
||||||
private void updateUiFromClients(List<RemoteClient> clients) {
|
private void updateUiFromClients(List<RemoteClient> clients) {
|
||||||
if (clients != null && !clients.isEmpty()) {
|
if (clients != null && !clients.isEmpty()) {
|
||||||
for (int i = 0; i < mList.getExpandableListAdapter().getGroupCount(); i++) {
|
// No sense crashing if we've made an error.
|
||||||
|
int groupCount = Math.min(mList.getExpandableListAdapter().getGroupCount(), clients.size());
|
||||||
|
for (int i = 0; i < groupCount; i++) {
|
||||||
|
final RemoteClient client = clients.get(i);
|
||||||
|
if (sState.isClientCollapsed(client.guid)) {
|
||||||
|
mList.collapseGroup(i);
|
||||||
|
} else {
|
||||||
mList.expandGroup(i);
|
mList.expandGroup(i);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
139
mobile/android/base/home/RemoteTabsExpandableListState.java
Normal file
139
mobile/android/base/home/RemoteTabsExpandableListState.java
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.gecko.home;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.util.PrefUtils;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.Editor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulate visual state maintained by the Remote Tabs home panel.
|
||||||
|
* <p>
|
||||||
|
* This state should persist across database updates by Sync and the like. This
|
||||||
|
* state could be stored in a separate "clients_metadata" table and served by
|
||||||
|
* the Tabs provider, but that is heavy-weight for what we want to achieve. Such
|
||||||
|
* a scheme would require either an expensive table join, or a tricky
|
||||||
|
* co-ordination between multiple cursors. In contrast, this is easy and cheap
|
||||||
|
* enough to do on the main thread.
|
||||||
|
* <p>
|
||||||
|
* This state is "per SharedPreferences" object. In practice, there should exist
|
||||||
|
* one state object per Gecko Profile; since we can't change profiles without
|
||||||
|
* killing our process, this can be a static singleton.
|
||||||
|
*/
|
||||||
|
public class RemoteTabsExpandableListState {
|
||||||
|
private static final String PREF_COLLAPSED_CLIENT_GUIDS = "remote_tabs_collapsed_client_guids";
|
||||||
|
private static final String PREF_HIDDEN_CLIENT_GUIDS = "remote_tabs_hidden_client_guids";
|
||||||
|
|
||||||
|
protected final SharedPreferences sharedPrefs;
|
||||||
|
|
||||||
|
// Synchronized by the state instance. The default is to expand a clients
|
||||||
|
// tabs, so "not present" means "expanded".
|
||||||
|
// Only accessed from the UI thread.
|
||||||
|
protected final Set<String> collapsedClients;
|
||||||
|
|
||||||
|
// Synchronized by the state instance. The default is to show a client, so
|
||||||
|
// "not present" means "shown".
|
||||||
|
// Only accessed from the UI thread.
|
||||||
|
protected final Set<String> hiddenClients;
|
||||||
|
|
||||||
|
public RemoteTabsExpandableListState(SharedPreferences sharedPrefs) {
|
||||||
|
if (null == sharedPrefs) {
|
||||||
|
throw new IllegalArgumentException("sharedPrefs must not be null");
|
||||||
|
}
|
||||||
|
this.sharedPrefs = sharedPrefs;
|
||||||
|
|
||||||
|
this.collapsedClients = getStringSet(PREF_COLLAPSED_CLIENT_GUIDS);
|
||||||
|
this.hiddenClients = getStringSet(PREF_HIDDEN_CLIENT_GUIDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a string set from shared preferences.
|
||||||
|
* <p>
|
||||||
|
* Nota bene: it is not OK to modify the set returned by {@link SharedPreferences#getStringSet(String, Set)}.
|
||||||
|
*
|
||||||
|
* @param pref to read from.
|
||||||
|
* @returns string set; never null.
|
||||||
|
*/
|
||||||
|
protected Set<String> getStringSet(String pref) {
|
||||||
|
final Set<String> loaded = PrefUtils.getStringSet(sharedPrefs, pref, null);
|
||||||
|
if (loaded != null) {
|
||||||
|
return new HashSet<String>(loaded);
|
||||||
|
} else {
|
||||||
|
return new HashSet<String>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update client membership in a set.
|
||||||
|
*
|
||||||
|
* @param pref
|
||||||
|
* to write updated set to.
|
||||||
|
* @param clients
|
||||||
|
* set to update membership in.
|
||||||
|
* @param clientGuid
|
||||||
|
* to update membership of.
|
||||||
|
* @param isMember
|
||||||
|
* whether the client is a member of the set.
|
||||||
|
* @return true if the set of clients was modified.
|
||||||
|
*/
|
||||||
|
protected boolean updateClientMembership(String pref, Set<String> clients, String clientGuid, boolean isMember) {
|
||||||
|
final boolean modified;
|
||||||
|
if (isMember) {
|
||||||
|
modified = clients.add(clientGuid);
|
||||||
|
} else {
|
||||||
|
modified = clients.remove(clientGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified) {
|
||||||
|
// This starts an asynchronous write. We don't care if we drop the
|
||||||
|
// write, and we don't really care if we race between writes, since
|
||||||
|
// we will return results from our in-memory cache.
|
||||||
|
final Editor editor = sharedPrefs.edit();
|
||||||
|
PrefUtils.putStringSet(editor, pref, clients);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a client as collapsed.
|
||||||
|
*
|
||||||
|
* @param clientGuid
|
||||||
|
* to update.
|
||||||
|
* @param collapsed
|
||||||
|
* whether the client is collapsed.
|
||||||
|
* @return true if the set of collapsed clients was modified.
|
||||||
|
*/
|
||||||
|
protected synchronized boolean setClientCollapsed(String clientGuid, boolean collapsed) {
|
||||||
|
return updateClientMembership(PREF_COLLAPSED_CLIENT_GUIDS, collapsedClients, clientGuid, collapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isClientCollapsed(String clientGuid) {
|
||||||
|
return collapsedClients.contains(clientGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a client as hidden.
|
||||||
|
*
|
||||||
|
* @param clientGuid
|
||||||
|
* to update.
|
||||||
|
* @param hidden
|
||||||
|
* whether the client is hidden.
|
||||||
|
* @return true if the set of hidden clients was modified.
|
||||||
|
*/
|
||||||
|
protected synchronized boolean setClientHidden(String clientGuid, boolean hidden) {
|
||||||
|
return updateClientMembership(PREF_HIDDEN_CLIENT_GUIDS, hiddenClients, clientGuid, hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isClientHidden(String clientGuid) {
|
||||||
|
return hiddenClients.contains(clientGuid);
|
||||||
|
}
|
||||||
|
}
|
@ -294,6 +294,7 @@ gbjar.sources += [
|
|||||||
'home/ReadingListRow.java',
|
'home/ReadingListRow.java',
|
||||||
'home/RecentTabsPanel.java',
|
'home/RecentTabsPanel.java',
|
||||||
'home/RemoteTabsExpandableListFragment.java',
|
'home/RemoteTabsExpandableListFragment.java',
|
||||||
|
'home/RemoteTabsExpandableListState.java',
|
||||||
'home/RemoteTabsPanel.java',
|
'home/RemoteTabsPanel.java',
|
||||||
'home/RemoteTabsStaticFragment.java',
|
'home/RemoteTabsStaticFragment.java',
|
||||||
'home/SearchEngine.java',
|
'home/SearchEngine.java',
|
||||||
|
Loading…
Reference in New Issue
Block a user