/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.samples.apps.iosched.ui; import android.app.ActionBar; import android.app.Fragment; import android.app.FragmentManager; import android.app.ListFragment; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.database.ContentObserver; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.text.SpannableString; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ScrollView; import android.widget.TextView; import com.google.samples.apps.iosched.Config; import com.google.samples.apps.iosched.R; import com.google.samples.apps.iosched.model.ScheduleHelper; import com.google.samples.apps.iosched.provider.ScheduleContract; import com.google.samples.apps.iosched.ui.widget.MyScheduleView; import com.google.samples.apps.iosched.ui.widget.SlidingTabLayout; import com.google.samples.apps.iosched.util.*; import java.lang.ref.WeakReference; import java.util.*; import static com.google.samples.apps.iosched.util.LogUtils.LOGD; import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; public class MyScheduleActivity extends BaseActivity implements MyScheduleFragment.Listener { // Interval that a timer will redraw the UI when in conference day, so that time sensitive // widgets, like the "Now" and "Ended" indicators can be properly updated. private static final long INTERVAL_TO_REDRAW_UI = 60 * 1000L; private static final String SCREEN_LABEL = "My Schedule"; private static final String TAG = makeLogTag(MyScheduleActivity.class); // If true, we are in the wide (tablet) mode where we show conference days side by side; // if false, we are in narrow (handset) mode where we use a ViewPager and show only // one conference day at a time. private boolean mWideMode = false; // If in wide mode, we have MyScheduleView widgets showing each day private MyScheduleView[] mMyScheduleViewWide = new MyScheduleView[2]; // The adapters that serves as the source of data for the UI, indicating the available // items. We have one adapter per day of the conference. When we push new data into these // adapters, the corresponding UIs update automatically. private MyScheduleAdapter[] mScheduleAdapters = new MyScheduleAdapter[ Config.CONFERENCE_DAYS.length]; // The ScheduleHelper is responsible for feeding data in a format suitable to the Adapter. private ScheduleHelper mDataHelper; // View pager and adapter (for narrow mode) ViewPager mViewPager = null; OurViewPagerAdapter mViewPagerAdapter = null; SlidingTabLayout mSlidingTabLayout = null; ScrollView mScrollViewWide; // Login failed butter bar View mButterBar; boolean mDestroyed = false; private static final String ARG_CONFERENCE_DAY_INDEX = "com.google.samples.apps.iosched.ARG_CONFERENCE_DAY_INDEX"; private Set<MyScheduleFragment> mMyScheduleFragments = new HashSet<MyScheduleFragment>(); public static final String EXTRA_DIALOG_TITLE = "com.google.samples.apps.iosched.EXTRA_DIALOG_TITLE"; public static final String EXTRA_DIALOG_MESSAGE = "com.google.samples.apps.iosched.EXTRA_DIALOG_MESSAGE"; public static final String EXTRA_DIALOG_YES = "com.google.samples.apps.iosched.EXTRA_DIALOG_YES"; public static final String EXTRA_DIALOG_NO = "com.google.samples.apps.iosched.EXTRA_DIALOG_NO"; public static final String EXTRA_DIALOG_URL = "com.google.samples.apps.iosched.EXTRA_DIALOG_URL"; private boolean mShowedAnnouncementDialog = false; public MyScheduleActivity() { mDataHelper = new ScheduleHelper(this); } @Override protected int getSelfNavDrawerItem() { return NAVDRAWER_ITEM_MY_SCHEDULE; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my_schedule); getLPreviewUtils().trySetActionBar(); /* [ANALYTICS:SCREEN] * TRIGGER: View the My Schedule screen. * LABEL: 'My Schedule' * [/ANALYTICS] */ AnalyticsManager.sendScreenView(SCREEN_LABEL); mViewPager = (ViewPager) findViewById(R.id.view_pager); mScrollViewWide = (ScrollView) findViewById(R.id.main_content_wide); mWideMode = findViewById(R.id.my_schedule_first_day) != null; int i; for (i = 0; i < Config.CONFERENCE_DAYS.length; i++) { mScheduleAdapters[i] = new MyScheduleAdapter(this, getLPreviewUtils()); } mViewPagerAdapter = new OurViewPagerAdapter(getFragmentManager()); mViewPager.setAdapter(mViewPagerAdapter); if (mWideMode) { mMyScheduleViewWide[0] = (MyScheduleView) findViewById(R.id.my_schedule_first_day); mMyScheduleViewWide[0].setAdapter(mScheduleAdapters[0]); mMyScheduleViewWide[1] = (MyScheduleView) findViewById(R.id.my_schedule_second_day); mMyScheduleViewWide[1].setAdapter(mScheduleAdapters[1]); TextView firstDayHeaderView = (TextView) findViewById(R.id.day_label_first_day); TextView secondDayHeaderView = (TextView) findViewById(R.id.day_label_second_day); if (firstDayHeaderView != null) { firstDayHeaderView.setText(getDayName(0)); } if (secondDayHeaderView != null) { secondDayHeaderView.setText(getDayName(1)); } } else { // it's PagerAdapter set. mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.sliding_tabs); mSlidingTabLayout.setCustomTabView(R.layout.tab_indicator, android.R.id.text1); setSlidingTabLayoutContentDescriptions(); Resources res = getResources(); mSlidingTabLayout.setSelectedIndicatorColors(res.getColor(R.color.tab_selected_strip)); mSlidingTabLayout.setDistributeEvenly(true); mSlidingTabLayout.setViewPager(mViewPager); } mButterBar = findViewById(R.id.butter_bar); removeLoginFailed(); if (mSlidingTabLayout != null) { mSlidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { mSlidingTabLayout.announceForAccessibility( getString(R.string.my_schedule_page_desc_a11y, getDayName(position))); } } @Override public void onPageScrollStateChanged(int state) { enableDisableSwipeRefresh(state == ViewPager.SCROLL_STATE_IDLE); } }); } overridePendingTransition(0, 0); addDataObservers(); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); if (mViewPager != null) { long now = UIUtils.getCurrentTime(this); for (int i = 0; i < Config.CONFERENCE_DAYS.length; i++) { if (now >= Config.CONFERENCE_DAYS[i][0] && now <= Config.CONFERENCE_DAYS[i][1]) { mViewPager.setCurrentItem(i); setTimerToUpdateUI(i); break; } } } setProgressBarTopWhenActionBarShown((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics())); } private void updateActionBarNavigation() { boolean show = !isNavDrawerOpen(); if (getLPreviewUtils().shouldChangeActionBarForDrawer()) { ActionBar ab = getActionBar(); ab.setDisplayShowTitleEnabled(show); ab.setDisplayUseLogoEnabled(!show); } } @Override protected void onNavDrawerStateChanged(boolean isOpen, boolean isAnimating) { super.onNavDrawerStateChanged(isOpen, isAnimating); updateActionBarNavigation(); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); LOGD(TAG, "onNewIntent, extras " + intent.getExtras()); if (intent.hasExtra(EXTRA_DIALOG_MESSAGE)) { mShowedAnnouncementDialog = false; showAnnouncementDialogIfNeeded(intent); } } private String getDayName(int position) { if (position >= 0 && position < Config.CONFERENCE_DAYS.length) { long timestamp = Config.CONFERENCE_DAYS[position][0]; return TimeUtils.formatHumanFriendlyShortDate(this, timestamp); } else { return ""; } } private void setSlidingTabLayoutContentDescriptions() { for (int i = 0; i < Config.CONFERENCE_DAYS.length; i++) { mSlidingTabLayout.setContentDescription(i, getString(R.string.my_schedule_tab_desc_a11y, getDayName(i))); } } private void removeLoginFailed() { mButterBar.setVisibility(View.GONE); deregisterHideableHeaderView(mButterBar); } @Override public void onAuthFailure(String accountName) { super.onAuthFailure(accountName); UIUtils.setUpButterBar(mButterBar, getString(R.string.login_failed_text), getString(R.string.login_failed_text_retry), new View.OnClickListener() { @Override public void onClick(View v) { removeLoginFailed(); retryAuth(); } } ); registerHideableHeaderView(findViewById(R.id.butter_bar)); } @Override protected void onAccountChangeRequested() { super.onAccountChangeRequested(); removeLoginFailed(); } @Override public boolean canSwipeRefreshChildScrollUp() { if (mWideMode) { return ViewCompat.canScrollVertically(mScrollViewWide, -1); } for (MyScheduleFragment fragment : mMyScheduleFragments) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { if (!fragment.getUserVisibleHint()) { continue; } } return ViewCompat.canScrollVertically(fragment.getListView(), -1); } return false; } @Override public void onResume() { super.onResume(); updateData(); showAnnouncementDialogIfNeeded(getIntent()); } private void showAnnouncementDialogIfNeeded(Intent intent) { final String title = intent.getStringExtra(EXTRA_DIALOG_TITLE); final String message = intent.getStringExtra(EXTRA_DIALOG_MESSAGE); if (!mShowedAnnouncementDialog && !TextUtils.isEmpty(title) && !TextUtils .isEmpty(message)) { LOGD(TAG, "showAnnouncementDialogIfNeeded, title: " + title); LOGD(TAG, "showAnnouncementDialogIfNeeded, message: " + message); final String yes = intent.getStringExtra(EXTRA_DIALOG_YES); LOGD(TAG, "showAnnouncementDialogIfNeeded, yes: " + yes); final String no = intent.getStringExtra(EXTRA_DIALOG_NO); LOGD(TAG, "showAnnouncementDialogIfNeeded, no: " + no); final String url = intent.getStringExtra(EXTRA_DIALOG_URL); LOGD(TAG, "showAnnouncementDialogIfNeeded, url: " + url); final SpannableString spannable = new SpannableString(message == null ? "" : message); Linkify.addLinks(spannable, Linkify.WEB_URLS); AlertDialog.Builder builder = new AlertDialog.Builder(this); if (!TextUtils.isEmpty(title)) { builder.setTitle(title); } builder.setMessage(spannable); if (!TextUtils.isEmpty(no)) { builder.setNegativeButton(no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); } if (!TextUtils.isEmpty(yes)) { builder.setPositiveButton(yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(intent); } }); } final AlertDialog dialog = builder.create(); dialog.show(); final TextView messageView = (TextView) dialog.findViewById(android.R.id.message); if (messageView != null) { // makes the embedded links in the text clickable, if there are any messageView.setMovementMethod(LinkMovementMethod.getInstance()); } mShowedAnnouncementDialog = true; } } @Override protected void onDestroy() { super.onDestroy(); mDestroyed = true; removeDataObservers(); } protected void updateData() { for (int i = 0; i < Config.CONFERENCE_DAYS.length; i++) { mDataHelper.getScheduleDataAsync(mScheduleAdapters[i], Config.CONFERENCE_DAYS[i][0], Config.CONFERENCE_DAYS[i][1]); } } @Override public void onFragmentViewCreated(ListFragment fragment) { fragment.getListView().addHeaderView( getLayoutInflater().inflate(R.layout.reserve_action_bar_space_header_view, null)); int dayIndex = fragment.getArguments().getInt(ARG_CONFERENCE_DAY_INDEX, 0); fragment.setListAdapter(mScheduleAdapters[dayIndex]); fragment.getListView().setRecyclerListener(mScheduleAdapters[dayIndex]); } @Override public void onFragmentAttached(MyScheduleFragment fragment) { mMyScheduleFragments.add(fragment); } @Override public void onFragmentDetached(MyScheduleFragment fragment) { mMyScheduleFragments.remove(fragment); } private class OurViewPagerAdapter extends FragmentPagerAdapter { public OurViewPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { LOGD(TAG, "Creating fragment #" + position); MyScheduleFragment frag = new MyScheduleFragment(); Bundle args = new Bundle(); args.putInt(ARG_CONFERENCE_DAY_INDEX, position); frag.setArguments(args); return frag; } @Override public int getCount() { return Config.CONFERENCE_DAYS.length; } @Override public CharSequence getPageTitle(int position) { return getDayName(position); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); if (getLPreviewUtils().shouldChangeActionBarForDrawer() && isNavDrawerOpen()) { // nothing to show if nav drawer is open or animating return true; } getMenuInflater().inflate(R.menu.my_schedule, menu); configureStandardMenuItems(menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_search: /* [ANALYTICS:EVENT] * TRIGGER: Click the search button on the Schedule screen. * CATEGORY: 'Schedule' * ACTION: 'launchsearch' * LABEL: (none) * [/ANALYTICS] */ AnalyticsManager.sendEvent(SCREEN_LABEL, "launchsearch", ""); startActivity(new Intent(this, SearchActivity.class)); return true; } return super.onOptionsItemSelected(item); } protected void addDataObservers() { getContentResolver().registerContentObserver( ScheduleContract.BASE_CONTENT_URI, true, mObserver); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); sp.registerOnSharedPreferenceChangeListener(mPrefChangeListener); } public void removeDataObservers() { getContentResolver().unregisterContentObserver(mObserver); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); sp.unregisterOnSharedPreferenceChangeListener(mPrefChangeListener); } private final SharedPreferences.OnSharedPreferenceChangeListener mPrefChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sp, String key) { LOGD(TAG, "sharedpreferences key " + key + " changed, maybe reloading data."); for (MyScheduleAdapter adapter : mScheduleAdapters) { if (PrefUtils.PREF_LOCAL_TIMES.equals(key)) { adapter.forceUpdate(); } else if (PrefUtils.PREF_ATTENDEE_AT_VENUE.equals(key)) { updateData(); } } } }; private final ContentObserver mObserver = new ThrottledContentObserver( new ThrottledContentObserver.Callbacks() { @Override public void onThrottledContentObserverFired() { LOGD(TAG, "content may be changed, reloading data"); updateData(); } }); /** * If in conference day, redraw the day's UI every @{link #INTERVAL_TO_REDRAW_UI} ms, so * that time sensitive widgets, like "now", "ended" and appropriate styles are updated. * * @param today the index in the conference days array that corresponds to the current day. */ private void setTimerToUpdateUI(final int today) { new UpdateUIRunnable(this, today, new Handler()).scheduleNextRun(); } boolean hasBeenDestroyed() { return mDestroyed; } static final class UpdateUIRunnable implements Runnable { final WeakReference<MyScheduleActivity> weakRefToParent; final Handler handler; final int today; public UpdateUIRunnable(MyScheduleActivity activity, int today, Handler handler) { weakRefToParent = new WeakReference<MyScheduleActivity>(activity); this.handler = handler; this.today = today; } public void scheduleNextRun() { handler.postDelayed(this, INTERVAL_TO_REDRAW_UI); } @Override public void run() { MyScheduleActivity activity = weakRefToParent.get(); if (activity == null || activity.hasBeenDestroyed()) { LOGD(TAG, "Ativity is not valid anymore. Stopping UI Updater"); return; } LOGD(TAG, "Running MySchedule UI updater (now=" + new Date(UIUtils.getCurrentTime(activity)) + ")"); if (activity.mScheduleAdapters != null && activity.mScheduleAdapters.length > today && activity.mScheduleAdapters[today] != null) { try { activity.mScheduleAdapters[today].forceUpdate(); } finally { // schedule again this.scheduleNextRun(); } } } } }