Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14792 manas 1
/**
2
 * Copyright 2010-present Facebook.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *    http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.facebook.widget;
18
 
19
import android.app.Activity;
20
import android.content.Context;
21
import android.content.res.TypedArray;
22
import android.location.Location;
23
import android.os.Bundle;
24
import android.os.Handler;
25
import android.os.Looper;
26
import android.text.Editable;
27
import android.text.TextUtils;
28
import android.text.TextWatcher;
29
import android.util.AttributeSet;
30
import android.view.View;
31
import android.view.ViewGroup;
32
import android.view.inputmethod.InputMethodManager;
33
import android.widget.EditText;
34
import android.widget.ListView;
35
import com.facebook.*;
36
import com.facebook.android.R;
37
import com.facebook.internal.AnalyticsEvents;
38
import com.facebook.internal.Logger;
39
import com.facebook.internal.Utility;
40
import com.facebook.model.GraphPlace;
41
 
42
import java.util.*;
43
 
44
public class PlacePickerFragment extends PickerFragment<GraphPlace> {
45
    /**
46
     * The key for an int parameter in the fragment's Intent bundle to indicate the radius in meters around
47
     * the center point to search. The default is 1000 meters.
48
     */
49
    public static final String RADIUS_IN_METERS_BUNDLE_KEY = "com.facebook.widget.PlacePickerFragment.RadiusInMeters";
50
    /**
51
     * The key for an int parameter in the fragment's Intent bundle to indicate what how many results to
52
     * return at a time. The default is 100 results.
53
     */
54
    public static final String RESULTS_LIMIT_BUNDLE_KEY = "com.facebook.widget.PlacePickerFragment.ResultsLimit";
55
    /**
56
     * The key for a String parameter in the fragment's Intent bundle to indicate what search text should
57
     * be sent to the service. The default is to have no search text.
58
     */
59
    public static final String SEARCH_TEXT_BUNDLE_KEY = "com.facebook.widget.PlacePickerFragment.SearchText";
60
    /**
61
     * The key for a Location parameter in the fragment's Intent bundle to indicate what geographical
62
     * location should be the center of the search.
63
     */
64
    public static final String LOCATION_BUNDLE_KEY = "com.facebook.widget.PlacePickerFragment.Location";
65
    /**
66
     * The key for a boolean parameter in the fragment's Intent bundle to indicate that the fragment
67
     * should display a search box and automatically update the search text as it changes.
68
     */
69
    public static final String SHOW_SEARCH_BOX_BUNDLE_KEY = "com.facebook.widget.PlacePickerFragment.ShowSearchBox";
70
 
71
    /**
72
     * The default radius around the center point to search.
73
     */
74
    public static final int DEFAULT_RADIUS_IN_METERS = 1000;
75
    /**
76
     * The default number of results to retrieve.
77
     */
78
    public static final int DEFAULT_RESULTS_LIMIT = 100;
79
 
80
    private static final int searchTextTimerDelayInMilliseconds = 2 * 1000;
81
 
82
    private static final String ID = "id";
83
    private static final String NAME = "name";
84
    private static final String LOCATION = "location";
85
    private static final String CATEGORY = "category";
86
    private static final String WERE_HERE_COUNT = "were_here_count";
87
    private static final String TAG = "PlacePickerFragment";
88
 
89
    private Location location;
90
    private int radiusInMeters = DEFAULT_RADIUS_IN_METERS;
91
    private int resultsLimit = DEFAULT_RESULTS_LIMIT;
92
    private String searchText;
93
    private Timer searchTextTimer;
94
    private boolean hasSearchTextChangedSinceLastQuery;
95
    private boolean showSearchBox = true;
96
    private EditText searchBox;
97
 
98
    /**
99
     * Default constructor. Creates a Fragment with all default properties.
100
     */
101
    public PlacePickerFragment() {
102
        this(null);
103
    }
104
 
105
    /**
106
     * Constructor.
107
     *
108
     * @param args a Bundle that optionally contains one or more values containing additional
109
     *             configuration information for the Fragment.
110
     */
111
    public PlacePickerFragment(Bundle args) {
112
        super(GraphPlace.class, R.layout.com_facebook_placepickerfragment, args);
113
        setPlacePickerSettingsFromBundle(args);
114
    }
115
 
116
    /**
117
     * Gets the location to search around. Either the location or the search text (or both) must be specified.
118
     *
119
     * @return the Location to search around
120
     */
121
    public Location getLocation() {
122
        return location;
123
    }
124
 
125
    /**
126
     * Sets the location to search around. Either the location or the search text (or both) must be specified.
127
     *
128
     * @param location the Location to search around
129
     */
130
    public void setLocation(Location location) {
131
        this.location = location;
132
    }
133
 
134
    /**
135
     * Gets the radius in meters around the location to search.
136
     *
137
     * @return the radius in meters
138
     */
139
    public int getRadiusInMeters() {
140
        return radiusInMeters;
141
    }
142
 
143
    /**
144
     * Sets the radius in meters around the location to search.
145
     *
146
     * @param radiusInMeters the radius in meters
147
     */
148
    public void setRadiusInMeters(int radiusInMeters) {
149
        this.radiusInMeters = radiusInMeters;
150
    }
151
 
152
    /**
153
     * Gets the number of results to retrieve.
154
     *
155
     * @return the number of results to retrieve
156
     */
157
    public int getResultsLimit() {
158
        return resultsLimit;
159
    }
160
 
161
    /**
162
     * Sets the number of results to retrieve.
163
     *
164
     * @param resultsLimit the number of results to retrieve
165
     */
166
    public void setResultsLimit(int resultsLimit) {
167
        this.resultsLimit = resultsLimit;
168
    }
169
 
170
    /**
171
     * Gets the search text (e.g., category, name) to search for. Either the location or the search
172
     * text (or both) must be specified.
173
     *
174
     * @return the search text
175
     */
176
    public String getSearchText() {
177
        return searchText;
178
    }
179
 
180
    /**
181
     * Sets the search text (e.g., category, name) to search for. Either the location or the search
182
     * text (or both) must be specified. If a search box is displayed, this will update its contents
183
     * to the specified text.
184
     *
185
     * @param searchText the search text
186
     */
187
    public void setSearchText(String searchText) {
188
        if (TextUtils.isEmpty(searchText)) {
189
            searchText = null;
190
        }
191
        this.searchText = searchText;
192
        if (this.searchBox != null) {
193
            this.searchBox.setText(searchText);
194
        }
195
    }
196
 
197
    /**
198
     * Sets the search text and reloads the data in the control. This is used to provide search-box
199
     * functionality where the user may be typing or editing text rapidly. It uses a timer to avoid repeated
200
     * requerying, preferring to wait until the user pauses typing to refresh the data. Note that this
201
     * method will NOT update the text in the search box, if any, as it is intended to be called as a result
202
     * of changes to the search box (and is public to enable applications to provide their own search box
203
     * UI instead of the default one).
204
     *
205
     * @param searchText                 the search text
206
     * @param forceReloadEventIfSameText if true, will reload even if the search text has not changed; if false,
207
     *                                   identical search text will not force a reload
208
     */
209
    public void onSearchBoxTextChanged(String searchText, boolean forceReloadEventIfSameText) {
210
        if (!forceReloadEventIfSameText && Utility.stringsEqualOrEmpty(this.searchText, searchText)) {
211
            return;
212
        }
213
 
214
        if (TextUtils.isEmpty(searchText)) {
215
            searchText = null;
216
        }
217
        this.searchText = searchText;
218
 
219
        // If search text is being set in response to user input, it is wasteful to send a new request
220
        // with every keystroke. Send a request the first time the search text is set, then set up a 2-second timer
221
        // and send whatever changes the user has made since then. (If nothing has changed
222
        // in 2 seconds, we reset so the next change will cause an immediate re-query.)
223
        hasSearchTextChangedSinceLastQuery = true;
224
        if (searchTextTimer == null) {
225
            searchTextTimer = createSearchTextTimer();
226
        }
227
    }
228
 
229
    /**
230
     * Gets the currently-selected place.
231
     *
232
     * @return the currently-selected place, or null if there is none
233
     */
234
    public GraphPlace getSelection() {
235
        Collection<GraphPlace> selection = getSelectedGraphObjects();
236
        return (selection != null && !selection.isEmpty()) ? selection.iterator().next() : null;
237
    }
238
 
239
    public void setSettingsFromBundle(Bundle inState) {
240
        super.setSettingsFromBundle(inState);
241
        setPlacePickerSettingsFromBundle(inState);
242
    }
243
 
244
    @Override
245
    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {
246
        super.onInflate(activity, attrs, savedInstanceState);
247
        TypedArray a = activity.obtainStyledAttributes(attrs, R.styleable.com_facebook_place_picker_fragment);
248
 
249
        setRadiusInMeters(a.getInt(R.styleable.com_facebook_place_picker_fragment_radius_in_meters, radiusInMeters));
250
        setResultsLimit(a.getInt(R.styleable.com_facebook_place_picker_fragment_results_limit, resultsLimit));
251
        if (a.hasValue(R.styleable.com_facebook_place_picker_fragment_results_limit)) {
252
            setSearchText(a.getString(R.styleable.com_facebook_place_picker_fragment_search_text));
253
        }
254
        showSearchBox = a.getBoolean(R.styleable.com_facebook_place_picker_fragment_show_search_box, showSearchBox);
255
 
256
        a.recycle();
257
    }
258
 
259
    @Override
260
    void setupViews(ViewGroup view) {
261
        if (showSearchBox) {
262
            ListView listView = (ListView) view.findViewById(R.id.com_facebook_picker_list_view);
263
 
264
            View searchHeaderView = getActivity().getLayoutInflater().inflate(
265
                    R.layout.com_facebook_picker_search_box, listView, false);
266
 
267
            listView.addHeaderView(searchHeaderView, null, false);
268
 
269
            searchBox = (EditText) view.findViewById(R.id.com_facebook_picker_search_text);
270
 
271
            searchBox.addTextChangedListener(new SearchTextWatcher());
272
            if (!TextUtils.isEmpty(searchText)) {
273
                searchBox.setText(searchText);
274
            }
275
        }
276
    }
277
 
278
    @Override
279
    public void onAttach(Activity activity) {
280
        super.onAttach(activity);
281
 
282
        if (searchBox != null) {
283
            InputMethodManager imm = (InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
284
            imm.showSoftInput(searchBox, InputMethodManager.SHOW_IMPLICIT);
285
        }
286
    }
287
 
288
    @Override
289
    public void onDetach() {
290
        super.onDetach();
291
 
292
        if (searchBox != null) {
293
            InputMethodManager imm = (InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
294
            imm.hideSoftInputFromWindow(searchBox.getWindowToken(), 0);
295
        }
296
    }
297
 
298
    void saveSettingsToBundle(Bundle outState) {
299
        super.saveSettingsToBundle(outState);
300
 
301
        outState.putInt(RADIUS_IN_METERS_BUNDLE_KEY, radiusInMeters);
302
        outState.putInt(RESULTS_LIMIT_BUNDLE_KEY, resultsLimit);
303
        outState.putString(SEARCH_TEXT_BUNDLE_KEY, searchText);
304
        outState.putParcelable(LOCATION_BUNDLE_KEY, location);
305
        outState.putBoolean(SHOW_SEARCH_BOX_BUNDLE_KEY, showSearchBox);
306
    }
307
 
308
    @Override
309
    void onLoadingData() {
310
        hasSearchTextChangedSinceLastQuery = false;
311
    }
312
 
313
    @Override
314
    Request getRequestForLoadData(Session session) {
315
        return createRequest(location, radiusInMeters, resultsLimit, searchText, extraFields, session);
316
    }
317
 
318
    @Override
319
    String getDefaultTitleText() {
320
        return getString(R.string.com_facebook_nearby);
321
    }
322
 
323
    @Override
324
    void logAppEvents(boolean doneButtonClicked) {
325
        AppEventsLogger logger = AppEventsLogger.newLogger(this.getActivity(), getSession());
326
        Bundle parameters = new Bundle();
327
 
328
        // If Done was clicked, we know this completed successfully. If not, we don't know (caller might have
329
        // dismissed us in response to selection changing, or user might have hit back button). Either way
330
        // we'll log the number of selections.
331
        String outcome = doneButtonClicked ? AnalyticsEvents.PARAMETER_DIALOG_OUTCOME_VALUE_COMPLETED :
332
                AnalyticsEvents.PARAMETER_DIALOG_OUTCOME_VALUE_UNKNOWN;
333
        parameters.putString(AnalyticsEvents.PARAMETER_DIALOG_OUTCOME, outcome);
334
        parameters.putInt("num_places_picked", (getSelection() != null) ? 1 : 0);
335
 
336
        logger.logSdkEvent(AnalyticsEvents.EVENT_PLACE_PICKER_USAGE, null, parameters);
337
    }
338
 
339
    @Override
340
    PickerFragmentAdapter<GraphPlace> createAdapter() {
341
        PickerFragmentAdapter<GraphPlace> adapter = new PickerFragmentAdapter<GraphPlace>(
342
                this.getActivity()) {
343
            @Override
344
            protected CharSequence getSubTitleOfGraphObject(GraphPlace graphObject) {
345
                String category = graphObject.getCategory();
346
                Integer wereHereCount = (Integer) graphObject.getProperty(WERE_HERE_COUNT);
347
 
348
                String result = null;
349
                if (category != null && wereHereCount != null) {
350
                    result = getString(R.string.com_facebook_placepicker_subtitle_format, category, wereHereCount);
351
                } else if (category == null && wereHereCount != null) {
352
                    result = getString(R.string.com_facebook_placepicker_subtitle_were_here_only_format, wereHereCount);
353
                } else if (category != null && wereHereCount == null) {
354
                    result = getString(R.string.com_facebook_placepicker_subtitle_catetory_only_format, category);
355
                }
356
                return result;
357
            }
358
 
359
            @Override
360
            protected int getGraphObjectRowLayoutId(GraphPlace graphObject) {
361
                return R.layout.com_facebook_placepickerfragment_list_row;
362
            }
363
 
364
            @Override
365
            protected int getDefaultPicture() {
366
                return R.drawable.com_facebook_place_default_icon;
367
            }
368
 
369
        };
370
        adapter.setShowCheckbox(false);
371
        adapter.setShowPicture(getShowPictures());
372
        return adapter;
373
    }
374
 
375
    @Override
376
    LoadingStrategy createLoadingStrategy() {
377
        return new AsNeededLoadingStrategy();
378
    }
379
 
380
    @Override
381
    SelectionStrategy createSelectionStrategy() {
382
        return new SingleSelectionStrategy();
383
    }
384
 
385
    private Request createRequest(Location location, int radiusInMeters, int resultsLimit, String searchText,
386
            Set<String> extraFields,
387
            Session session) {
388
        Request request = Request.newPlacesSearchRequest(session, location, radiusInMeters, resultsLimit, searchText,
389
                null);
390
 
391
        Set<String> fields = new HashSet<String>(extraFields);
392
        String[] requiredFields = new String[]{
393
                ID,
394
                NAME,
395
                LOCATION,
396
                CATEGORY,
397
                WERE_HERE_COUNT
398
        };
399
        fields.addAll(Arrays.asList(requiredFields));
400
 
401
        String pictureField = adapter.getPictureFieldSpecifier();
402
        if (pictureField != null) {
403
            fields.add(pictureField);
404
        }
405
 
406
        Bundle parameters = request.getParameters();
407
        parameters.putString("fields", TextUtils.join(",", fields));
408
        request.setParameters(parameters);
409
 
410
        return request;
411
    }
412
 
413
    private void setPlacePickerSettingsFromBundle(Bundle inState) {
414
        // We do this in a separate non-overridable method so it is safe to call from the constructor.
415
        if (inState != null) {
416
            setRadiusInMeters(inState.getInt(RADIUS_IN_METERS_BUNDLE_KEY, radiusInMeters));
417
            setResultsLimit(inState.getInt(RESULTS_LIMIT_BUNDLE_KEY, resultsLimit));
418
            if (inState.containsKey(SEARCH_TEXT_BUNDLE_KEY)) {
419
                setSearchText(inState.getString(SEARCH_TEXT_BUNDLE_KEY));
420
            }
421
            if (inState.containsKey(LOCATION_BUNDLE_KEY)) {
422
                Location location = inState.getParcelable(LOCATION_BUNDLE_KEY);
423
                setLocation(location);
424
            }
425
            showSearchBox = inState.getBoolean(SHOW_SEARCH_BOX_BUNDLE_KEY, showSearchBox);
426
        }
427
    }
428
 
429
    private Timer createSearchTextTimer() {
430
        Timer timer = new Timer();
431
        timer.schedule(new TimerTask() {
432
            @Override
433
            public void run() {
434
                onSearchTextTimerTriggered();
435
            }
436
        }, 0, searchTextTimerDelayInMilliseconds);
437
 
438
        return timer;
439
    }
440
 
441
    private void onSearchTextTimerTriggered() {
442
        if (hasSearchTextChangedSinceLastQuery) {
443
            Handler handler = new Handler(Looper.getMainLooper());
444
            handler.post(new Runnable() {
445
                @Override
446
                public void run() {
447
                    FacebookException error = null;
448
                    try {
449
                        loadData(true);
450
                    } catch (FacebookException fe) {
451
                        error = fe;
452
                    } catch (Exception e) {
453
                        error = new FacebookException(e);
454
                    } finally {
455
                        if (error != null) {
456
                            OnErrorListener onErrorListener = getOnErrorListener();
457
                            if (onErrorListener != null) {
458
                                onErrorListener.onError(PlacePickerFragment.this, error);
459
                            } else {
460
                                Logger.log(LoggingBehavior.REQUESTS, TAG, "Error loading data : %s", error);
461
                            }
462
                        }
463
                    }
464
                }
465
            });
466
        } else {
467
            // Nothing has changed in 2 seconds. Invalidate and forget about this timer.
468
            // Next time the user types, we will fire a query immediately again.
469
            searchTextTimer.cancel();
470
            searchTextTimer = null;
471
        }
472
    }
473
 
474
    private class AsNeededLoadingStrategy extends LoadingStrategy {
475
        @Override
476
        public void attach(GraphObjectAdapter<GraphPlace> adapter) {
477
            super.attach(adapter);
478
 
479
            this.adapter.setDataNeededListener(new GraphObjectAdapter.DataNeededListener() {
480
                @Override
481
                public void onDataNeeded() {
482
                    // Do nothing if we are currently loading data . We will get notified again when that load finishes if the adapter still
483
                    // needs more data. Otherwise, follow the next link.
484
                    if (!loader.isLoading()) {
485
                        loader.followNextLink();
486
                    }
487
                }
488
            });
489
        }
490
 
491
        @Override
492
        protected void onLoadFinished(GraphObjectPagingLoader<GraphPlace> loader,
493
                SimpleGraphObjectCursor<GraphPlace> data) {
494
            super.onLoadFinished(loader, data);
495
 
496
            // We could be called in this state if we are clearing data or if we are being re-attached
497
            // in the middle of a query.
498
            if (data == null || loader.isLoading()) {
499
                return;
500
            }
501
 
502
            hideActivityCircle();
503
 
504
            if (data.isFromCache()) {
505
                // Only the first page can be cached, since all subsequent pages will be round-tripped. Force
506
                // a refresh of the first page before we allow paging to begin. If the first page produced
507
                // no data, launch the refresh immediately, otherwise schedule it for later.
508
                loader.refreshOriginalRequest(data.areMoreObjectsAvailable() ? CACHED_RESULT_REFRESH_DELAY : 0);
509
            }
510
        }
511
    }
512
 
513
    private class SearchTextWatcher implements TextWatcher {
514
 
515
        @Override
516
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
517
        }
518
 
519
        @Override
520
        public void onTextChanged(CharSequence s, int start, int before, int count) {
521
            onSearchBoxTextChanged(s.toString(), false);
522
        }
523
 
524
        @Override
525
        public void afterTextChanged(Editable s) {
526
        }
527
    }
528
}