OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.bookmark; |
| 6 |
| 7 import static org.chromium.chrome.browser.ChromeBrowserProviderClient.INVALID_BO
OKMARK_ID; |
| 8 |
| 9 import android.content.Context; |
| 10 import android.content.res.Resources; |
| 11 import android.graphics.Typeface; |
| 12 import android.graphics.drawable.BitmapDrawable; |
| 13 import android.os.Bundle; |
| 14 import android.util.Log; |
| 15 import android.view.LayoutInflater; |
| 16 import android.view.View; |
| 17 import android.view.View.OnClickListener; |
| 18 import android.view.ViewGroup; |
| 19 import android.widget.ArrayAdapter; |
| 20 import android.widget.Button; |
| 21 import android.widget.ListView; |
| 22 import android.widget.TextView; |
| 23 |
| 24 import com.google.android.apps.chrome.R; |
| 25 |
| 26 import org.chromium.base.ApiCompatibilityUtils; |
| 27 import org.chromium.base.VisibleForTesting; |
| 28 import org.chromium.chrome.browser.ChromeBrowserProvider.BookmarkNode; |
| 29 import org.chromium.chrome.browser.ChromeBrowserProvider.Type; |
| 30 import org.chromium.chrome.browser.ChromeBrowserProviderClient; |
| 31 import org.chromium.chrome.browser.widget.TintedDrawable; |
| 32 import org.chromium.sync.AndroidSyncSettings; |
| 33 import org.chromium.ui.base.LocalizationUtils; |
| 34 |
| 35 /** |
| 36 * The user interface for selecting a bookmark folder. |
| 37 */ |
| 38 public class SelectBookmarkFolderFragment extends AsyncTaskFragment implements O
nClickListener { |
| 39 /** |
| 40 * Defines the constants used as arguments to this fragment. |
| 41 * @see android.app.Fragment#setArguments(Bundle) |
| 42 */ |
| 43 private static class Arguments { |
| 44 private Arguments() {} |
| 45 |
| 46 public static final String ALLOW_FOLDER_ADDITION = "allowAdd"; |
| 47 public static final String FOLDER_ID_TO_SELECT = "selectedFolder"; |
| 48 public static final String IS_FOLDER = "isFolder"; |
| 49 } |
| 50 |
| 51 private Button mNewFolderButton; |
| 52 |
| 53 private ListView mFoldersList; |
| 54 private FolderListAdapter mFoldersAdapter; |
| 55 private TextView mEmptyFoldersView; |
| 56 |
| 57 private boolean mAllowFolderAddition; |
| 58 private long mFolderIdToSelect; |
| 59 // Used to determine if a bookmark's or a folder's parent is being changed. |
| 60 private boolean mIsFolder; |
| 61 |
| 62 private OnActionListener mActionListener; |
| 63 |
| 64 /** |
| 65 * The maximum depth that will be indented. Folders with a depth greater th
an this will |
| 66 * all appear at this same depth. |
| 67 */ |
| 68 private int mMaximumFolderIndentDepth = 8; |
| 69 |
| 70 /** |
| 71 * Constructs a new fragment in charge of handling bookmark folder selection
. |
| 72 * @param allowFolderAddition Whether this fragment should allow additional
folders to be added |
| 73 * as children. |
| 74 * @param folderIdToSelect The ID of the folder to select when shown initial
ly. |
| 75 * @return The selection fragment. |
| 76 */ |
| 77 public static SelectBookmarkFolderFragment newInstance( |
| 78 boolean allowFolderAddition, long folderIdToSelect, boolean isFolder
) { |
| 79 SelectBookmarkFolderFragment fragment = new SelectBookmarkFolderFragment
(); |
| 80 Bundle arguments = new Bundle(); |
| 81 arguments.putBoolean(Arguments.ALLOW_FOLDER_ADDITION, allowFolderAdditio
n); |
| 82 arguments.putLong(Arguments.FOLDER_ID_TO_SELECT, folderIdToSelect); |
| 83 arguments.putBoolean(Arguments.IS_FOLDER, isFolder); |
| 84 fragment.setArguments(arguments); |
| 85 return fragment; |
| 86 } |
| 87 |
| 88 /** |
| 89 * Retrieves the current action listener for this fragment. |
| 90 * Used by testing to intercept calls as a proxy. |
| 91 */ |
| 92 @VisibleForTesting |
| 93 public OnActionListener getOnActionListenerForTest() { |
| 94 return mActionListener; |
| 95 } |
| 96 |
| 97 /** |
| 98 * Sets the action listener for this fragment. |
| 99 * @param listener The listener to be set. |
| 100 */ |
| 101 public void setOnActionListener(OnActionListener listener) { |
| 102 mActionListener = listener; |
| 103 } |
| 104 |
| 105 @Override |
| 106 public void onCreate(Bundle savedInstanceState) { |
| 107 super.onCreate(savedInstanceState); |
| 108 |
| 109 mAllowFolderAddition = getArguments().getBoolean(Arguments.ALLOW_FOLDER_
ADDITION); |
| 110 mFolderIdToSelect = getArguments().getLong( |
| 111 Arguments.FOLDER_ID_TO_SELECT, INVALID_BOOKMARK_ID); |
| 112 mIsFolder = getArguments().getBoolean(Arguments.IS_FOLDER); |
| 113 } |
| 114 |
| 115 @Override |
| 116 public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| 117 Bundle savedInstanceState) { |
| 118 View contentView = inflater.inflate(R.layout.select_bookmark_folder, con
tainer, false); |
| 119 |
| 120 mNewFolderButton = (Button) contentView.findViewById(R.id.new_folder_btn
); |
| 121 if (mAllowFolderAddition) { |
| 122 mNewFolderButton.setOnClickListener(this); |
| 123 } else { |
| 124 mNewFolderButton.setVisibility(View.GONE); |
| 125 } |
| 126 |
| 127 mFoldersList = (ListView) contentView.findViewById(R.id.bookmark_folder_
list); |
| 128 mEmptyFoldersView = (TextView) contentView.findViewById(R.id.empty_folde
rs); |
| 129 mFoldersList.setEmptyView(mEmptyFoldersView); |
| 130 |
| 131 if (mFoldersAdapter != null) mFoldersList.setAdapter(mFoldersAdapter); |
| 132 return contentView; |
| 133 } |
| 134 |
| 135 @Override |
| 136 public void onActivityCreated(Bundle savedInstanceState) { |
| 137 super.onActivityCreated(savedInstanceState); |
| 138 |
| 139 if (mFoldersAdapter == null) { |
| 140 mFoldersAdapter = new FolderListAdapter(getActivity().getApplication
Context()); |
| 141 mFoldersList.setAdapter(mFoldersAdapter); |
| 142 |
| 143 long selectedFolder = INVALID_BOOKMARK_ID; |
| 144 if (savedInstanceState == null) { |
| 145 // Only use the folder ID passed in from the intent if the activ
ity is not being |
| 146 // restored. During restoration, the ListView will handle resel
ecting the |
| 147 // previously selected entity and we do not want to override tha
t with the initial |
| 148 // value. |
| 149 selectedFolder = mFolderIdToSelect; |
| 150 } |
| 151 |
| 152 loadAllFolders(selectedFolder); |
| 153 } else { |
| 154 if (!areFoldersLoaded()) { |
| 155 loadAllFolders(mFolderIdToSelect); |
| 156 } |
| 157 } |
| 158 |
| 159 mMaximumFolderIndentDepth = |
| 160 getResources().getInteger(R.integer.select_bookmark_folder_max_d
epth_indent); |
| 161 } |
| 162 |
| 163 @Override |
| 164 public void onClick(View v) { |
| 165 if (mActionListener == null) { |
| 166 Log.d(getClass().getName(), "No OnResultListener specified -- onClic
k == NoOp"); |
| 167 return; |
| 168 } |
| 169 if (v == mNewFolderButton) { |
| 170 long parentId = INVALID_BOOKMARK_ID; |
| 171 String parentName = null; |
| 172 for (int i = 0; i < mFoldersAdapter.getCount(); i++) { |
| 173 FolderListEntry selectedFolder = mFoldersAdapter.getItem(i); |
| 174 if (selectedFolder.mFolder.id() == mFolderIdToSelect) { |
| 175 parentId = selectedFolder.mFolder.id(); |
| 176 parentName = selectedFolder.mFolder.name(); |
| 177 } |
| 178 } |
| 179 mActionListener.triggerNewFolderCreation(parentId, parentName); |
| 180 } |
| 181 } |
| 182 |
| 183 /** |
| 184 * @return Whether or not the bookmark folders have been loaded asynchronous
ly yet. |
| 185 */ |
| 186 @VisibleForTesting |
| 187 public boolean areFoldersLoaded() { |
| 188 return mFoldersAdapter.getCount() > 0; |
| 189 } |
| 190 |
| 191 private void loadAllFolders(long folderId) { |
| 192 if (isFragmentAsyncTaskRunning()) return; |
| 193 runFragmentAsyncTask(new LoadAllFoldersTask(folderId), |
| 194 getActivity().getString(R.string.loading_bookmark)); |
| 195 } |
| 196 |
| 197 private void handleLoadAllFolders(BookmarkNode result, long selectedFolderId
, |
| 198 boolean syncEnabled) { |
| 199 if (getActivity() == null || getActivity().isFinishing()) return; |
| 200 |
| 201 mFoldersAdapter.clear(); |
| 202 if (result == null) { |
| 203 mEmptyFoldersView.setText(R.string.bookmark_folder_tree_error); |
| 204 } else { |
| 205 mEmptyFoldersView.setText(R.string.no_bookmark_folders); |
| 206 |
| 207 // The root node is just a placeholder, so directly add it's childre
n. |
| 208 for (BookmarkNode child : result.children()) { |
| 209 if (!syncEnabled) { |
| 210 Type type = child.type(); |
| 211 if (type == Type.BOOKMARK_BAR || type == Type.OTHER_NODE) { |
| 212 continue; |
| 213 } |
| 214 } |
| 215 addFolderItem(child, 0, selectedFolderId); |
| 216 } |
| 217 } |
| 218 } |
| 219 |
| 220 private void addFolderItem(BookmarkNode folder, int depth, long selectedFold
erId) { |
| 221 boolean isSelectedFolder = (folder.id() == selectedFolderId); |
| 222 mFoldersAdapter.add(new FolderListEntry(folder, depth, isSelectedFolder)
); |
| 223 // Hiding sub folders will prevent current folder to be moved under a su
b folder. |
| 224 if (folder.id() != selectedFolderId || !mIsFolder) { |
| 225 for (BookmarkNode child : folder.children()) { |
| 226 addFolderItem(child, depth + 1, selectedFolderId); |
| 227 } |
| 228 } |
| 229 } |
| 230 |
| 231 /** |
| 232 * Data object used in the list adapter. |
| 233 */ |
| 234 private static class FolderListEntry { |
| 235 final BookmarkNode mFolder; |
| 236 final int mDepth; |
| 237 final boolean mIsSelectedFolder; |
| 238 |
| 239 FolderListEntry(BookmarkNode folder, int depth, boolean isSelectedFolder
) { |
| 240 mFolder = folder; |
| 241 mDepth = depth; |
| 242 mIsSelectedFolder = isSelectedFolder; |
| 243 } |
| 244 |
| 245 @Override |
| 246 public String toString() { |
| 247 return mFolder.name(); |
| 248 } |
| 249 } |
| 250 |
| 251 /** |
| 252 * List adapter for the folder selection view. |
| 253 */ |
| 254 private class FolderListAdapter extends ArrayAdapter<FolderListEntry> { |
| 255 private final int mDefaultPaddingLeft; |
| 256 private final int mPaddingLeftInc; |
| 257 |
| 258 public FolderListAdapter(Context context) { |
| 259 super(context, R.layout.select_bookmark_folder_item); |
| 260 |
| 261 Resources resources = context.getResources(); |
| 262 mDefaultPaddingLeft = |
| 263 resources.getDimensionPixelSize(R.dimen.select_bookmark_fold
er_item_left); |
| 264 mPaddingLeftInc = |
| 265 resources.getDimensionPixelSize(R.dimen.select_bookmark_fold
er_item_inc_left); |
| 266 } |
| 267 |
| 268 @Override |
| 269 public View getView(int position, View convertView, ViewGroup parent) { |
| 270 TextView view = (TextView) super.getView(position, convertView, pare
nt); |
| 271 final FolderListEntry entry = getItem(position); |
| 272 |
| 273 BitmapDrawable icon = TintedDrawable.constructTintedDrawable( |
| 274 getResources(), R.drawable.eb_others); |
| 275 ApiCompatibilityUtils.setCompoundDrawablesRelativeWithIntrinsicBound
s( |
| 276 view, icon, null, null, null); |
| 277 |
| 278 // TODO: For folders that exceed the maximum depth, come up with a U
I treatment to |
| 279 // give some indication of that. |
| 280 int paddingLeft = mDefaultPaddingLeft |
| 281 + Math.min(entry.mDepth, mMaximumFolderIndentDepth) * mPaddi
ngLeftInc; |
| 282 if (LocalizationUtils.isLayoutRtl()) { |
| 283 view.setPadding(0, 0, paddingLeft, 0); |
| 284 } else { |
| 285 view.setPadding(paddingLeft, 0, 0, 0); |
| 286 } |
| 287 view.setTypeface(null, entry.mIsSelectedFolder ? Typeface.BOLD : Typ
eface.NORMAL); |
| 288 view.setBackgroundResource(R.drawable.btn_bg_holo); |
| 289 view.setOnClickListener(new OnClickListener() { |
| 290 @Override |
| 291 public void onClick(View v) { |
| 292 executeFolderSelection(entry.mFolder.id(), entry.mFolder.nam
e()); |
| 293 } |
| 294 }); |
| 295 return view; |
| 296 } |
| 297 |
| 298 @Override |
| 299 public long getItemId(int position) { |
| 300 return getCount() > 0 ? getItem(position).mFolder.id() : -1; |
| 301 } |
| 302 |
| 303 @Override |
| 304 public boolean hasStableIds() { |
| 305 return true; |
| 306 } |
| 307 } |
| 308 |
| 309 /** |
| 310 * Select the folder to be used for the Add/Edit Fragment. |
| 311 * @param folderId Id of the selected folder. |
| 312 * @param folderName Name of the selected folder. |
| 313 */ |
| 314 public void executeFolderSelection(long folderId, String folderName) { |
| 315 getFragmentManager().popBackStackImmediate(); |
| 316 ((AddEditBookmarkFragment) getTargetFragment()).setParentFolderInfo( |
| 317 folderId, folderName); |
| 318 } |
| 319 |
| 320 /** |
| 321 * Asynchronously retrieves all the bookmark folders that the user can edit, |
| 322 * showing a progress dialog if the task takes too long. |
| 323 */ |
| 324 private class LoadAllFoldersTask extends FragmentAsyncTask { |
| 325 private final Context mContext; |
| 326 private final long mFolderId; |
| 327 private BookmarkNode mResult; |
| 328 |
| 329 LoadAllFoldersTask(long folderId) { |
| 330 mContext = getActivity().getApplicationContext(); |
| 331 mFolderId = folderId; |
| 332 } |
| 333 |
| 334 @Override |
| 335 protected void runBackgroundTask() { |
| 336 mResult = ChromeBrowserProviderClient.getEditableBookmarkFolderHiera
rchy(mContext); |
| 337 } |
| 338 |
| 339 @Override |
| 340 protected void onTaskFinished() { |
| 341 handleLoadAllFolders(mResult, mFolderId, AndroidSyncSettings.isSyncE
nabled(mContext)); |
| 342 } |
| 343 |
| 344 @Override |
| 345 protected void setDependentUIEnabled(boolean enabled) { |
| 346 mNewFolderButton.setEnabled(enabled); |
| 347 } |
| 348 } |
| 349 |
| 350 /** |
| 351 * Listener to handle actions triggered by this fragment. |
| 352 */ |
| 353 public static interface OnActionListener { |
| 354 /** |
| 355 * Triggered when the user asks to create a new subfolder. |
| 356 * @param selectedFolderId The currently selected folder ID, which shoul
d be used as the |
| 357 * default parent of the newly added folder. |
| 358 * @param selectedFolderName The currently selected folder name. |
| 359 */ |
| 360 public void triggerNewFolderCreation(long selectedFolderId, String selec
tedFolderName); |
| 361 } |
| 362 } |
OLD | NEW |