package org.edx.mobile.view; import androidx.databinding.DataBindingUtil; import android.os.Bundle; import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import android.text.TextUtils; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.RadioButton; import android.widget.RadioGroup; import com.google.inject.Inject; import org.edx.mobile.R; import org.edx.mobile.base.BaseFragment; import org.edx.mobile.core.IEdxEnvironment; import org.edx.mobile.databinding.FragmentMainDiscoveryBinding; import org.edx.mobile.deeplink.Screen; import org.edx.mobile.deeplink.ScreenDef; import org.edx.mobile.event.DiscoveryTabSelectedEvent; import org.edx.mobile.event.ScreenArgumentsEvent; import org.edx.mobile.module.analytics.Analytics; import org.edx.mobile.util.ConfigUtil; import org.edx.mobile.view.dialog.NativeFindCoursesFragment; import de.greenrobot.event.EventBus; import static org.edx.mobile.view.Router.EXTRA_PATH_ID; import static org.edx.mobile.view.Router.EXTRA_SCREEN_NAME; public class MainDiscoveryFragment extends BaseFragment { @Inject protected IEdxEnvironment environment; @Nullable protected FragmentMainDiscoveryBinding binding; private ToolbarCallbacks toolbarCallbacks; private SparseArray<Fragment> fragmentsArray = new SparseArray<>(); @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main_discovery, container, false); return binding.getRoot(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); initFragments(); binding.options.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { onFragmentSelected(checkedId, true); } }); EventBus.getDefault().register(this); if (getArguments() != null) { handleTabSelection(getArguments()); } } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); toolbarCallbacks = getActivity() instanceof ToolbarCallbacks ? (ToolbarCallbacks) getActivity() : null; onFragmentVisibilityChanged(getUserVisibleHint()); } private void initFragments() { // Course discovery if (ConfigUtil.Companion.isCourseDiscoveryEnabled(environment)) { Fragment courseDiscoveryFragment; if (ConfigUtil.Companion.isCourseWebviewDiscoveryEnabled(environment)) { courseDiscoveryFragment = getChildFragmentManager().findFragmentByTag("fragment_courses_webview"); if (courseDiscoveryFragment == null) { courseDiscoveryFragment = new WebViewDiscoverCoursesFragment(); commitFragmentTransaction(courseDiscoveryFragment, "fragment_courses_webview"); } } else { courseDiscoveryFragment = getChildFragmentManager().findFragmentByTag("fragment_courses_native"); if (courseDiscoveryFragment == null) { courseDiscoveryFragment = new NativeFindCoursesFragment(); commitFragmentTransaction(courseDiscoveryFragment, "fragment_courses_native"); } } courseDiscoveryFragment.setArguments(getArguments()); fragmentsArray.put(R.id.option_courses, courseDiscoveryFragment); addTabItem(R.id.option_courses, R.string.label_my_courses); } // Program discovery if (ConfigUtil.Companion.isProgramDiscoveryEnabled(environment)) { Fragment programDiscoveryFragment = getChildFragmentManager().findFragmentByTag("fragment_programs"); if (programDiscoveryFragment == null) { programDiscoveryFragment = new WebViewDiscoverProgramsFragment(); commitFragmentTransaction(programDiscoveryFragment, "fragment_programs"); } fragmentsArray.put(R.id.option_programs, programDiscoveryFragment); addTabItem(R.id.option_programs, R.string.label_my_programs); } // Degree discovery if (ConfigUtil.Companion.isDegreeDiscoveryEnabled(environment)) { Fragment degreeDiscoveryFragment = getChildFragmentManager().findFragmentByTag("fragment_degrees"); if (degreeDiscoveryFragment == null) { degreeDiscoveryFragment = new WebViewDiscoverDegreesFragment(); commitFragmentTransaction(degreeDiscoveryFragment, "fragment_degrees"); } fragmentsArray.put(R.id.option_degrees, degreeDiscoveryFragment); addTabItem(R.id.option_degrees, R.string.label_degrees); } if (fragmentsArray.size() < 2) { hideTabsBar(); } if (fragmentsArray.size() > 0) { setTabsBackground(binding.options); final int firstBtnId = fragmentsArray.keyAt(0); onFragmentSelected(firstBtnId, false); binding.options.check(firstBtnId); } } private void onFragmentSelected(@IdRes int fragmentTabId, final boolean isUserSelected) { // First we need to hide all the fragments along with their shared search view, // then show the required fragment and check either that fragment needs to show the // search view or not through its `onHiddenChanged` method. Fragment selectedFragment = null; for (int i = 0; i < fragmentsArray.size(); i++) { if (fragmentTabId == fragmentsArray.keyAt(i)) { selectedFragment = fragmentsArray.valueAt(i); } else { hideFragment(fragmentsArray.valueAt(i)); } } if (selectedFragment != null) { showFragment(selectedFragment); } if (isUserSelected) { switch (fragmentTabId) { case R.id.option_courses: environment.getAnalyticsRegistry().trackScreenView(Analytics.Screens.FIND_COURSES); break; case R.id.option_programs: environment.getAnalyticsRegistry().trackScreenView(Analytics.Screens.FIND_PROGRAMS); break; case R.id.option_degrees: environment.getAnalyticsRegistry().trackScreenView(Analytics.Screens.FIND_DEGREES); break; } } } private void commitFragmentTransaction(@NonNull Fragment fragment, @Nullable String tag) { final FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction(); fragmentTransaction.add(R.id.fl_container, fragment, tag); fragmentTransaction.commit(); } private void showFragment(@Nullable Fragment fragment) { if (fragment == null || !fragment.isHidden()) { return; } // Use commitNow() method to perform the FragmentManager transaction synchronously // instated of commit(), otherwise program discovery screen is not visible to the guest user // in case of deep linking. getChildFragmentManager().beginTransaction() .show(fragment) .commitNow(); } private void hideFragment(@Nullable Fragment fragment) { if (fragment == null || fragment.isHidden()) { return; } // Use commitNow() method to perform the FragmentManager transaction synchronously // instated of commit(), otherwise program discovery screen is not visible to the guest user // in case of deep linking. getChildFragmentManager().beginTransaction() .hide(fragment) .commitNow(); } private void addTabItem(@IdRes int id, @StringRes int label) { final RadioButton radioButton = (RadioButton) getLayoutInflater().inflate( R.layout.segment_control_button_base, binding.options, false); radioButton.setId(id); radioButton.setText(label); binding.options.addView(radioButton); } private void setTabsBackground(@NonNull RadioGroup options) { final int childCount = options.getChildCount(); for (int i = 0; i < childCount; i++) { if (i == 0) { options.getChildAt(i).setBackgroundResource(R.drawable.edx_segmented_control_left_background); } else if (i == childCount - 1) { options.getChildAt(i).setBackgroundResource(R.drawable.edx_segmented_control_right_background); } else { options.getChildAt(i).setBackgroundResource(R.drawable.edx_segmented_control_middle_background); } } } private void hideTabsBar() { binding.options.setVisibility(View.GONE); } @SuppressWarnings("unused") public void onEventMainThread(@NonNull DiscoveryTabSelectedEvent event) { onFragmentSelected(binding.options.getCheckedRadioButtonId(), true); } @SuppressWarnings("unused") public void onEventMainThread(@NonNull ScreenArgumentsEvent event) { handleTabSelection(event.getBundle()); } private void handleTabSelection(@NonNull Bundle bundle) { @ScreenDef final String screenName = bundle.getString(EXTRA_SCREEN_NAME); if (screenName == null) { return; } final int btnId = getBtnIdAgainstScreeName(screenName); if (btnId != -1) { onFragmentSelected(btnId, true); binding.options.check(btnId); } final String pathId = bundle.getString(EXTRA_PATH_ID); if (!TextUtils.isEmpty(pathId)) { switch (screenName) { case Screen.PROGRAM: environment.getRouter().showProgramWebViewActivity(getActivity(), environment, pathId, getActivity().getString(R.string.label_my_programs)); break; case Screen.COURSE_DISCOVERY: environment.getRouter().showCourseInfo(getActivity(), pathId); break; case Screen.PROGRAM_DISCOVERY: case Screen.DEGREE_DISCOVERY: environment.getRouter().showProgramInfo(getActivity(), pathId); break; } } // Setting this to null, so that upon recreation of the fragment, relevant activity // shouldn't be auto created again. bundle.putString(Router.EXTRA_SCREEN_NAME, null); } private int getBtnIdAgainstScreeName(@NonNull @ScreenDef String screeName) { switch (screeName) { case Screen.COURSE_DISCOVERY: return R.id.option_courses; case Screen.PROGRAM_DISCOVERY: return R.id.option_programs; case Screen.DEGREE_DISCOVERY: return R.id.option_degrees; default: return -1; } } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); onFragmentVisibilityChanged(isVisibleToUser); } public void onFragmentVisibilityChanged(final boolean isVisibleToUser) { if (toolbarCallbacks != null && toolbarCallbacks.getSearchView() != null) { // Sometimes when fragment visibility is changed by swiping left/right the viewpager, // visibility of SearchView doesn't get changed accordingly, putting the code within // runnable does the job in this case. toolbarCallbacks.getSearchView().post(new Runnable() { @Override public void run() { // This check is added to fix a crash i-e- LEARNER-7137, it happens when the // fragment is no longer attached to its parent activity/fragment, so a simple // isAdded() check should resolve the issue. Ref: https://stackoverflow.com/a/44845661 if (!isAdded()) { return; } final Fragment nativeCoursesFragment = getChildFragmentManager().findFragmentByTag("fragment_courses_native"); if ((nativeCoursesFragment != null && nativeCoursesFragment.isVisible()) || !isVisibleToUser) { toolbarCallbacks.getSearchView().setVisibility(View.GONE); } else { updateShownFragmentsVisibility(); } } }); } } /** * This function lets the currently shown Fragment know that it has now become visible to * the user by calling its {@link Fragment#setUserVisibleHint(boolean)} function. */ private void updateShownFragmentsVisibility() { for (int i = 0; i < fragmentsArray.size(); i++) { final Fragment fragment = fragmentsArray.get(fragmentsArray.keyAt(i)); if (fragment != null && fragment.getUserVisibleHint() && fragment.isVisible()) { fragment.setUserVisibleHint(true); return; } } } @Override public void onDestroyView() { super.onDestroyView(); EventBus.getDefault().unregister(this); } }