diff options
author | Michael W <baddaemon87@gmail.com> | 2020-06-14 11:20:35 +0200 |
---|---|---|
committer | Michael Bestas <mkbestas@lineageos.org> | 2020-12-08 19:39:12 +0200 |
commit | 9ef3d79e043ad33934616b710eb201f032c539b8 (patch) | |
tree | f521336734e1b2ec8836c24d75b64c70d7d43b14 | |
parent | 248c6793c4892b8444d64e89c221533270b2946a (diff) |
DeskClock: Replace ViewPager with manual fragment handling
Change-Id: Iaadfb417f0ca8638936875113ec2f39853f2a39c
-rw-r--r-- | Android.bp | 3 | ||||
-rw-r--r-- | proguard.flags | 1 | ||||
-rw-r--r-- | res/layout/clock_fragment.xml | 3 | ||||
-rw-r--r-- | res/layout/desk_clock.xml | 43 | ||||
-rw-r--r-- | src/com/android/deskclock/AlarmClockFragment.java | 10 | ||||
-rw-r--r-- | src/com/android/deskclock/ClockFragment.java | 5 | ||||
-rw-r--r-- | src/com/android/deskclock/DeskClock.java | 165 | ||||
-rw-r--r-- | src/com/android/deskclock/DeskClockFragment.java | 2 | ||||
-rw-r--r-- | src/com/android/deskclock/FabController.java | 8 | ||||
-rw-r--r-- | src/com/android/deskclock/FragmentTabPagerAdapter.java | 167 | ||||
-rw-r--r-- | src/com/android/deskclock/FragmentUtils.java | 113 | ||||
-rw-r--r-- | src/com/android/deskclock/stopwatch/StopwatchFragment.java | 5 | ||||
-rw-r--r-- | src/com/android/deskclock/timer/TimerFragment.java | 20 | ||||
-rw-r--r-- | src/com/android/deskclock/uidata/TabModel.java | 2 |
14 files changed, 213 insertions, 334 deletions
diff --git a/Android.bp b/Android.bp index 2d27ae94a..0e49ab5f1 100644 --- a/Android.bp +++ b/Android.bp @@ -3,6 +3,9 @@ android_app { resource_dirs: ["res"], sdk_version: "current", overrides: ["AlarmClock"], + optimize: { + proguard_flags_files: ["proguard.flags"], + }, srcs: [ "src/**/*.java", "gen/**/*.java", diff --git a/proguard.flags b/proguard.flags new file mode 100644 index 000000000..a6832665d --- /dev/null +++ b/proguard.flags @@ -0,0 +1 @@ +-keep class androidx.viewpager.widget.* { *; } diff --git a/res/layout/clock_fragment.xml b/res/layout/clock_fragment.xml index ec97381a4..d1b7619f2 100644 --- a/res/layout/clock_fragment.xml +++ b/res/layout/clock_fragment.xml @@ -18,9 +18,8 @@ <androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/cities" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_weight="1" android:clickable="false" android:clipToPadding="false" android:paddingBottom="@dimen/fab_height" diff --git a/res/layout/desk_clock.xml b/res/layout/desk_clock.xml index 76ea224c3..e39d1bbbd 100644 --- a/res/layout/desk_clock.xml +++ b/res/layout/desk_clock.xml @@ -25,25 +25,19 @@ app:statusBarBackground="@null" android:fitsSystemWindows="true"> - <androidx.coordinatorlayout.widget.CoordinatorLayout - android:id="@+id/content" + <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1"> + android:layout_height="wrap_content" + android:background="@null" + app:elevation="0dp"> - <com.google.android.material.appbar.AppBarLayout + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@null" - app:elevation="0dp"> - - <androidx.appcompat.widget.Toolbar - android:id="@+id/toolbar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:contentInsetStart="0dp" - tools:ignore="RtlSymmetry" - android:gravity="center"> + app:contentInsetStart="0dp" + tools:ignore="RtlSymmetry" + android:gravity="center"> <TextView android:id="@+id/title_view" @@ -52,16 +46,19 @@ android:layout_gravity="center" style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" /> - </androidx.appcompat.widget.Toolbar> - </com.google.android.material.appbar.AppBarLayout> + </androidx.appcompat.widget.Toolbar> + </com.google.android.material.appbar.AppBarLayout> + + <androidx.coordinatorlayout.widget.CoordinatorLayout + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"> - <androidx.viewpager.widget.ViewPager - android:id="@+id/desk_clock_pager" + <FrameLayout + android:id="@+id/fragment_container" android:layout_width="match_parent" - android:layout_height="match_parent" - android:importantForAccessibility="no" - android:saveEnabled="false" - app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + android:layout_height="match_parent"/> <LinearLayout android:layout_width="match_parent" diff --git a/src/com/android/deskclock/AlarmClockFragment.java b/src/com/android/deskclock/AlarmClockFragment.java index ac1966a0d..70ffee3a5 100644 --- a/src/com/android/deskclock/AlarmClockFragment.java +++ b/src/com/android/deskclock/AlarmClockFragment.java @@ -31,6 +31,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; +import androidx.loader.content.CursorLoader; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -81,7 +82,7 @@ public final class AlarmClockFragment extends DeskClockFragment implements private RecyclerView mRecyclerView; // Data - private Loader mCursorLoader; + private CursorLoader mCursorLoader; private long mScrollToAlarmId = Alarm.INVALID_ID; private long mExpandedAlarmId = Alarm.INVALID_ID; private long mCurrentUpdateToken; @@ -103,7 +104,7 @@ public final class AlarmClockFragment extends DeskClockFragment implements @Override public void onCreate(Bundle savedState) { super.onCreate(savedState); - mCursorLoader = LoaderManager.getInstance(this).initLoader(0, null, this); + mCursorLoader = (CursorLoader) LoaderManager.getInstance(this).initLoader(0, null, this); if (savedState != null) { mExpandedAlarmId = savedState.getLong(KEY_EXPANDED_ID, Alarm.INVALID_ID); } @@ -402,6 +403,11 @@ public final class AlarmClockFragment extends DeskClockFragment implements right.setVisibility(View.INVISIBLE); } + @Override + public final int getFabTargetVisibility() { + return View.VISIBLE; + } + private void startCreatingAlarm() { // Clear the currently selected alarm. mAlarmTimeClickHandler.setSelectedAlarm(null); diff --git a/src/com/android/deskclock/ClockFragment.java b/src/com/android/deskclock/ClockFragment.java index 1536b55df..b487e52be 100644 --- a/src/com/android/deskclock/ClockFragment.java +++ b/src/com/android/deskclock/ClockFragment.java @@ -215,6 +215,11 @@ public final class ClockFragment extends DeskClockFragment { right.setVisibility(INVISIBLE); } + @Override + public final int getFabTargetVisibility() { + return View.VISIBLE; + } + /** * Refresh the next alarm time. */ diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index cc3687002..4dea5d269 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -88,9 +88,6 @@ public class DeskClock extends BaseActivity /** Hides, updates, and shows only the {@link #mLeftButton} and {@link #mRightButton}. */ private final AnimatorSet mUpdateButtonsOnlyAnimation = new AnimatorSet(); - /** Automatically starts the {@link #mShowAnimation} after {@link #mHideAnimation} ends. */ - private final AnimatorListenerAdapter mAutoStartShowListener = new AutoStartShowListener(); - /** Updates the user interface to reflect the selected tab from the backing model. */ private final TabListener mTabChangeWatcher = new TabChangeWatcher(); @@ -119,15 +116,14 @@ public class DeskClock extends BaseActivity /** The ViewPager that pages through the fragments representing the content of the tabs. */ private ViewPager mFragmentTabPager; - /** Generates the fragments that are displayed by the {@link #mFragmentTabPager}. */ - private FragmentTabPagerAdapter mFragmentTabPagerAdapter; - /** The view that displays the current tab's title */ private TextView mTitleView; /** The bottom navigation bar */ private BottomNavigationView mBottomNavigation; + private FragmentUtils mFragmentUtils; + /** {@code true} when a settings change necessitates recreating this activity. */ private boolean mRecreateActivity; @@ -248,18 +244,7 @@ public class DeskClock extends BaseActivity .after(leftHideAnimation) .after(rightHideAnimation); - // Customize the view pager. - mFragmentTabPagerAdapter = new FragmentTabPagerAdapter(this); - mFragmentTabPager = (ViewPager) findViewById(R.id.desk_clock_pager); - // Keep all four tabs to minimize jank. - mFragmentTabPager.setOffscreenPageLimit(3); - // Set Accessibility Delegate to null so view pager doesn't intercept movements and - // prevent the fab from being selected. - mFragmentTabPager.setAccessibilityDelegate(null); - // Mirror changes made to the selected page of the view pager into UiDataModel. - mFragmentTabPager.addOnPageChangeListener(new PageChangeWatcher()); - mFragmentTabPager.setAdapter(mFragmentTabPagerAdapter); - + mFragmentUtils = new FragmentUtils(this); // Mirror changes made to the selected tab into UiDataModel. mBottomNavigation = findViewById(R.id.bottom_view); mBottomNavigation.setOnNavigationItemSelectedListener(mNavigationListener); @@ -275,27 +260,40 @@ public class DeskClock extends BaseActivity @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { - UiDataModel.Tab tab = null; + UiDataModel.Tab selectedTab = null; switch (item.getItemId()) { case R.id.page_alarm: - tab = UiDataModel.Tab.ALARMS; + selectedTab = UiDataModel.Tab.ALARMS; break; case R.id.page_clock: - tab = UiDataModel.Tab.CLOCKS; + selectedTab = UiDataModel.Tab.CLOCKS; break; case R.id.page_timer: - tab = UiDataModel.Tab.TIMERS; + selectedTab = UiDataModel.Tab.TIMERS; break; case R.id.page_stopwatch: - tab = UiDataModel.Tab.STOPWATCH; + selectedTab = UiDataModel.Tab.STOPWATCH; break; } - if (tab != null) { - UiDataModel.getUiDataModel().setSelectedTab(tab); + if (selectedTab != null) { + UiDataModel.Tab currentTab = UiDataModel.getUiDataModel().getSelectedTab(); + DeskClockFragment currentFrag = mFragmentUtils.getDeskClockFragment(currentTab); + DeskClockFragment selectedFrag = mFragmentUtils.getDeskClockFragment(selectedTab); + + int currentVisibility = currentFrag.getFabTargetVisibility(); + int targetVisibility = selectedFrag.getFabTargetVisibility(); + if (currentVisibility != targetVisibility) { + if (targetVisibility == View.VISIBLE) { + mShowAnimation.start(); + } else { + mHideAnimation.start(); + } + } + UiDataModel.getUiDataModel().setSelectedTab(selectedTab); return true; } @@ -324,15 +322,6 @@ public class DeskClock extends BaseActivity if (mRecreateActivity) { mRecreateActivity = false; - - // A runnable must be posted here or the new DeskClock activity will be recreated in a - // paused state, even though it is the foreground activity. - mFragmentTabPager.post(new Runnable() { - @Override - public void run() { - recreate(); - } - }); } } @@ -468,16 +457,7 @@ public class DeskClock extends BaseActivity final UiDataModel.Tab selectedTab = UiDataModel.getUiDataModel().getSelectedTab(); // Update the selected tab in the mBottomNavigation if it does not agree with UiDataModel. mBottomNavigation.setSelectedItemId(selectedTab.getPageResId()); - - // Update the selected fragment in the viewpager if it does not agree with UiDataModel. - for (int i = 0; i < mFragmentTabPagerAdapter.getCount(); i++) { - final DeskClockFragment fragment = mFragmentTabPagerAdapter.getDeskClockFragment(i); - if (fragment.isTabSelected() && mFragmentTabPager.getCurrentItem() != i) { - mFragmentTabPager.setCurrentItem(i); - break; - } - } - + mFragmentUtils.showFragment(selectedTab); mTitleView.setText(selectedTab.getLabelResId()); } @@ -485,14 +465,7 @@ public class DeskClock extends BaseActivity * @return the DeskClockFragment that is currently selected according to UiDataModel */ private DeskClockFragment getSelectedDeskClockFragment() { - for (int i = 0; i < mFragmentTabPagerAdapter.getCount(); i++) { - final DeskClockFragment fragment = mFragmentTabPagerAdapter.getDeskClockFragment(i); - if (fragment.isTabSelected()) { - return fragment; - } - } - final UiDataModel.Tab selectedTab = UiDataModel.getUiDataModel().getSelectedTab(); - throw new IllegalStateException("Unable to locate selected fragment (" + selectedTab + ")"); + return mFragmentUtils.getCurrentFragment(); } /** @@ -503,93 +476,6 @@ public class DeskClock extends BaseActivity } /** - * As the view pager changes the selected page, update the model to record the new selected tab. - */ - private final class PageChangeWatcher implements OnPageChangeListener { - - /** The last reported page scroll state; used to detect exotic state changes. */ - private int mPriorState = SCROLL_STATE_IDLE; - - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - // Only hide the fab when a non-zero drag distance is detected. This prevents - // over-scrolling from needlessly hiding the fab. - if (mFabState == FabState.HIDE_ARMED && positionOffsetPixels != 0) { - mFabState = FabState.HIDING; - mHideAnimation.start(); - } - } - - @Override - public void onPageScrollStateChanged(int state) { - if (mPriorState == SCROLL_STATE_IDLE && state == SCROLL_STATE_SETTLING) { - // The user has tapped a tab button; play the hide and show animations linearly. - mHideAnimation.addListener(mAutoStartShowListener); - mHideAnimation.start(); - mFabState = FabState.HIDING; - } else if (mPriorState == SCROLL_STATE_SETTLING && state == SCROLL_STATE_DRAGGING) { - // The user has interrupted settling on a tab and the fab button must be re-hidden. - if (mShowAnimation.isStarted()) { - mShowAnimation.cancel(); - } - if (mHideAnimation.isStarted()) { - // Let the hide animation finish naturally; don't auto show when it ends. - mHideAnimation.removeListener(mAutoStartShowListener); - } else { - // Start and immediately end the hide animation to jump to the hidden state. - mHideAnimation.start(); - mHideAnimation.end(); - } - mFabState = FabState.HIDING; - - } else if (state != SCROLL_STATE_DRAGGING && mFabState == FabState.HIDING) { - // The user has lifted their finger; show the buttons now or after hide ends. - if (mHideAnimation.isStarted()) { - // Finish the hide animation and then start the show animation. - mHideAnimation.addListener(mAutoStartShowListener); - } else { - updateFab(FAB_AND_BUTTONS_IMMEDIATE); - mShowAnimation.start(); - - // The animation to show the fab has begun; update the state to showing. - mFabState = FabState.SHOWING; - } - } else if (state == SCROLL_STATE_DRAGGING) { - // The user has started a drag so arm the hide animation. - mFabState = FabState.HIDE_ARMED; - } - - // Update the last known state. - mPriorState = state; - } - - @Override - public void onPageSelected(int position) { - mFragmentTabPagerAdapter.getDeskClockFragment(position).selectTab(); - } - } - - /** - * If this listener is attached to {@link #mHideAnimation} when it ends, the corresponding - * {@link #mShowAnimation} is automatically started. - */ - private final class AutoStartShowListener extends AnimatorListenerAdapter { - @Override - public void onAnimationEnd(Animator animation) { - // Prepare the hide animation for its next use; by default do not auto-show after hide. - mHideAnimation.removeListener(mAutoStartShowListener); - - // Update the buttons now that they are no longer visible. - updateFab(FAB_AND_BUTTONS_IMMEDIATE); - - // Automatically start the grow animation now that shrinking is complete. - mShowAnimation.start(); - - // The animation to show the fab has begun; update the state to showing. - mFabState = FabState.SHOWING; - } - } - - /** * Shows/hides a snackbar as silencing settings are enabled/disabled. */ private final class SilentSettingChangeWatcher implements OnSilentSettingsListener { @@ -666,6 +552,7 @@ public class DeskClock extends BaseActivity // If the hide animation has already completed, the buttons must be updated now when the // new tab is known. Otherwise they are updated at the end of the hide animation. if (!mHideAnimation.isStarted()) { + getSupportFragmentManager().executePendingTransactions(); updateFab(FAB_AND_BUTTONS_IMMEDIATE); } } diff --git a/src/com/android/deskclock/DeskClockFragment.java b/src/com/android/deskclock/DeskClockFragment.java index 1f51a332e..c08a15a8e 100644 --- a/src/com/android/deskclock/DeskClockFragment.java +++ b/src/com/android/deskclock/DeskClockFragment.java @@ -20,6 +20,7 @@ import android.view.KeyEvent; import android.widget.Button; import android.widget.ImageView; import androidx.annotation.ColorInt; +import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -104,6 +105,7 @@ public abstract class DeskClockFragment extends Fragment implements FabContainer /** * Select the tab that displays this fragment. */ + @Keep public final void selectTab() { UiDataModel.getUiDataModel().setSelectedTab(mTab); } diff --git a/src/com/android/deskclock/FabController.java b/src/com/android/deskclock/FabController.java index bab7f46ad..11c5d5375 100644 --- a/src/com/android/deskclock/FabController.java +++ b/src/com/android/deskclock/FabController.java @@ -57,4 +57,10 @@ public interface FabController { * @param right the button to the right of the fab component */ void onRightButtonClick(@NonNull Button right); -}
\ No newline at end of file + + /** + * + * @return the target visibility of the FAB component + */ + int getFabTargetVisibility(); +} diff --git a/src/com/android/deskclock/FragmentTabPagerAdapter.java b/src/com/android/deskclock/FragmentTabPagerAdapter.java deleted file mode 100644 index de3e25c35..000000000 --- a/src/com/android/deskclock/FragmentTabPagerAdapter.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * 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.android.deskclock; - -import android.util.ArrayMap; -import android.view.View; -import android.view.ViewGroup; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; -import androidx.viewpager.widget.PagerAdapter; - -import com.android.deskclock.uidata.UiDataModel; - -import java.util.Map; - -/** - * This adapter produces the DeskClockFragments that are the content of the DeskClock tabs. The - * adapter presents the tabs in LTR and RTL order depending on the text layout direction for the - * current locale. To prevent issues when switching between LTR and RTL, fragments are registered - * with the manager using position-independent tags, which is an important departure from - * FragmentPagerAdapter. - */ -final class FragmentTabPagerAdapter extends PagerAdapter { - - private final DeskClock mDeskClock; - - /** The manager into which fragments are added. */ - private final FragmentManager mFragmentManager; - - /** A fragment cache that can be accessed before {@link #instantiateItem} is called. */ - private final Map<UiDataModel.Tab, DeskClockFragment> mFragmentCache; - - /** The active fragment transaction if one exists. */ - private FragmentTransaction mCurrentTransaction; - - /** The current fragment displayed to the user. */ - private Fragment mCurrentPrimaryItem; - - FragmentTabPagerAdapter(DeskClock deskClock) { - mDeskClock = deskClock; - mFragmentCache = new ArrayMap<>(getCount()); - mFragmentManager = deskClock.getSupportFragmentManager(); - } - - @Override - public int getCount() { - return UiDataModel.getUiDataModel().getTabCount(); - } - - /** - * @param position the left-to-right index of the fragment to be returned - * @return the fragment displayed at the given {@code position} - */ - DeskClockFragment getDeskClockFragment(int position) { - // Fetch the tab the UiDataModel reports for the position. - final UiDataModel.Tab tab = UiDataModel.getUiDataModel().getTabAt(position); - - // First check the local cache for the fragment. - DeskClockFragment fragment = mFragmentCache.get(tab); - if (fragment != null) { - return fragment; - } - - // Next check the fragment manager; relevant when app is rebuilt after locale changes - // because this adapter will be new and mFragmentCache will be empty, but the fragment - // manager will retain the Fragments built on original application launch. - fragment = (DeskClockFragment) mFragmentManager.findFragmentByTag(tab.name()); - if (fragment != null) { - fragment.setFabContainer(mDeskClock); - mFragmentCache.put(tab, fragment); - return fragment; - } - - // Otherwise, build the fragment from scratch. - final String fragmentClassName = tab.getFragmentClassName(); - fragment = (DeskClockFragment) Fragment.instantiate(mDeskClock, fragmentClassName); - fragment.setFabContainer(mDeskClock); - mFragmentCache.put(tab, fragment); - return fragment; - } - - @Override - public void startUpdate(ViewGroup container) { - if (container.getId() == View.NO_ID) { - throw new IllegalStateException("ViewPager with adapter " + this + " has no id"); - } - } - - @Override - public Object instantiateItem(ViewGroup container, int position) { - if (mCurrentTransaction == null) { - mCurrentTransaction = mFragmentManager.beginTransaction(); - } - - // Use the fragment located in the fragment manager if one exists. - final UiDataModel.Tab tab = UiDataModel.getUiDataModel().getTabAt(position); - Fragment fragment = mFragmentManager.findFragmentByTag(tab.name()); - if (fragment != null) { - mCurrentTransaction.attach(fragment); - } else { - fragment = getDeskClockFragment(position); - mCurrentTransaction.add(container.getId(), fragment, tab.name()); - } - - if (fragment != mCurrentPrimaryItem) { - fragment.setMenuVisibility(false); - fragment.setUserVisibleHint(false); - } - - return fragment; - } - - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - if (mCurrentTransaction == null) { - mCurrentTransaction = mFragmentManager.beginTransaction(); - } - final DeskClockFragment fragment = (DeskClockFragment) object; - fragment.setFabContainer(null); - mCurrentTransaction.detach(fragment); - } - - @Override - public void setPrimaryItem(ViewGroup container, int position, Object object) { - final Fragment fragment = (Fragment) object; - if (fragment != mCurrentPrimaryItem) { - if (mCurrentPrimaryItem != null) { - mCurrentPrimaryItem.setMenuVisibility(false); - mCurrentPrimaryItem.setUserVisibleHint(false); - } - if (fragment != null) { - fragment.setMenuVisibility(true); - fragment.setUserVisibleHint(true); - } - mCurrentPrimaryItem = fragment; - } - } - - @Override - public void finishUpdate(ViewGroup container) { - if (mCurrentTransaction != null) { - mCurrentTransaction.commitAllowingStateLoss(); - mCurrentTransaction = null; - mFragmentManager.executePendingTransactions(); - } - } - - @Override - public boolean isViewFromObject(View view, Object object) { - return ((Fragment) object).getView() == view; - } -}
\ No newline at end of file diff --git a/src/com/android/deskclock/FragmentUtils.java b/src/com/android/deskclock/FragmentUtils.java new file mode 100644 index 000000000..43d7a4dc7 --- /dev/null +++ b/src/com/android/deskclock/FragmentUtils.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The LineageOS Project + * + * 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.android.deskclock; + +import android.util.ArrayMap; +import android.view.View; +import android.view.ViewGroup; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import com.android.deskclock.uidata.UiDataModel; + +import java.util.Map; + +/** + * This class produces the DeskClockFragments that are the content of the DeskClock tabs. + * It presents the tabs in LTR and RTL order depending on the text layout direction for the + * current locale. To prevent issues when switching between LTR and RTL, fragments are registered + * with the manager using position-independent tags, which is an important departure from + * FragmentPagerAdapter. + */ +public final class FragmentUtils { + + private final DeskClock mDeskClock; + + /** The manager into which fragments are added. */ + private final FragmentManager mFragmentManager; + + /** A fragment cache that can be accessed before {@link #instantiateItem} is called. */ + private final Map<UiDataModel.Tab, DeskClockFragment> mFragmentCache; + + /** The current fragment displayed to the user. */ + private DeskClockFragment mCurrentPrimaryItem; + + public FragmentUtils(DeskClock deskClock) { + mDeskClock = deskClock; + mFragmentCache = new ArrayMap<>(getCount()); + mFragmentManager = deskClock.getSupportFragmentManager(); + } + + private int getCount() { + return UiDataModel.getUiDataModel().getTabCount(); + } + + public DeskClockFragment getDeskClockFragment(UiDataModel.Tab tab) { + // First check the local cache for the fragment. + DeskClockFragment fragment = mFragmentCache.get(tab); + if (fragment != null) { + return fragment; + } + + // Next check the fragment manager; relevant when app is rebuilt after locale changes + // because this adapter will be new and mFragmentCache will be empty, but the fragment + // manager will retain the Fragments built on original application launch. + fragment = (DeskClockFragment) mFragmentManager.findFragmentByTag(tab.name()); + if (fragment != null) { + fragment.setFabContainer(mDeskClock); + mFragmentCache.put(tab, fragment); + return fragment; + } + + // Otherwise, build the fragment from scratch. + final String fragmentClassName = tab.getFragmentClassName(); + fragment = (DeskClockFragment) Fragment.instantiate(mDeskClock, fragmentClassName); + fragment.setFabContainer(mDeskClock); + + FragmentTransaction transaction = mFragmentManager.beginTransaction(); + transaction.add(R.id.fragment_container, fragment, tab.name()); + transaction.commit(); + + mFragmentCache.put(tab, fragment); + return fragment; + } + + public void hideAllFragments() { + FragmentTransaction transaction = mFragmentManager.beginTransaction(); + for (UiDataModel.Tab tab : UiDataModel.Tab.values()) { + Fragment fragment = mFragmentManager.findFragmentByTag(tab.name()); + if (fragment != null) { + transaction.hide(fragment); + } + } + + transaction.commit(); + } + + public void showFragment(UiDataModel.Tab tab) { + hideAllFragments(); + Fragment fragment = getDeskClockFragment(tab); + mFragmentManager.beginTransaction().show(fragment).commit(); + mCurrentPrimaryItem = (DeskClockFragment) fragment; + } + + public DeskClockFragment getCurrentFragment() { + return mCurrentPrimaryItem; + } +}
\ No newline at end of file diff --git a/src/com/android/deskclock/stopwatch/StopwatchFragment.java b/src/com/android/deskclock/stopwatch/StopwatchFragment.java index d0b2466fe..87d22ea60 100644 --- a/src/com/android/deskclock/stopwatch/StopwatchFragment.java +++ b/src/com/android/deskclock/stopwatch/StopwatchFragment.java @@ -294,6 +294,11 @@ public final class StopwatchFragment extends DeskClockFragment { } } + @Override + public final int getFabTargetVisibility() { + return View.VISIBLE; + } + /** * @param color the newly installed app window color */ diff --git a/src/com/android/deskclock/timer/TimerFragment.java b/src/com/android/deskclock/timer/TimerFragment.java index 7779be656..0b2a49cec 100644 --- a/src/com/android/deskclock/timer/TimerFragment.java +++ b/src/com/android/deskclock/timer/TimerFragment.java @@ -236,6 +236,26 @@ public final class TimerFragment extends DeskClockFragment { } } + @Override + public int getFabTargetVisibility() { + if (mCurrentView == mTimersView) { + final Timer timer = getTimer(); + if (timer == null) { + return INVISIBLE; + } else { + return VISIBLE; + } + } else if (mCurrentView == mCreateTimerView) { + if (mCreateTimerView.hasValidInput()) { + return VISIBLE; + } else { + return INVISIBLE; + } + } + + return INVISIBLE; + } + private void updateFab(@NonNull ImageView fab) { if (mCurrentView == mTimersView) { final Timer timer = getTimer(); diff --git a/src/com/android/deskclock/uidata/TabModel.java b/src/com/android/deskclock/uidata/TabModel.java index 5b878ed9d..9a1af3bd0 100644 --- a/src/com/android/deskclock/uidata/TabModel.java +++ b/src/com/android/deskclock/uidata/TabModel.java @@ -18,6 +18,7 @@ package com.android.deskclock.uidata; import android.content.SharedPreferences; import android.text.TextUtils; +import androidx.annotation.Keep; import java.util.ArrayList; import java.util.Arrays; @@ -88,6 +89,7 @@ final class TabModel { * @param position the position of the tab in the user interface * @return the tab at the given {@code ordinal} */ + @Keep Tab getTabAt(int position) { final int ordinal; if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == LAYOUT_DIRECTION_RTL) { |