/* * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) * * This file is part of RoboSwag library. * * 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 ru.touchin.roboswag.components.navigation; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.CallSuper; import android.support.annotation.ColorInt; import android.support.annotation.ColorRes; import android.support.annotation.DrawableRes; import android.support.annotation.IdRes; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.v4.app.Fragment; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment; import ru.touchin.roboswag.components.utils.BaseLifecycleBindable; import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; import rx.Observable; import rx.Subscription; import rx.functions.Action0; import rx.functions.Action1; /** * Created by Gavriil Sitnikov on 21/10/2015. * Class to control view of specific fragment, activity and application by logic bridge. * * @param Type of activity where such {@link ViewController} could be; * @param Type of fragment where such {@link ViewController} could be; */ @SuppressWarnings("PMD.TooManyMethods") public class ViewController, TFragment extends ViewControllerFragment> implements LifecycleBindable { @NonNull private final TActivity activity; @NonNull private final TFragment fragment; @NonNull private final ViewGroup container; @NonNull private final BaseLifecycleBindable baseLifecycleBindable = new BaseLifecycleBindable(); private boolean destroyed; @SuppressWarnings({"unchecked", "PMD.UnusedFormalParameter"}) //UnusedFormalParameter: savedInstanceState could be used by children public ViewController(@NonNull final CreationContext creationContext, @Nullable final Bundle savedInstanceState) { this.activity = (TActivity) creationContext.activity; this.fragment = (TFragment) creationContext.fragment; this.container = creationContext.container; } /** * Returns activity where {@link ViewController} could be. * * @return Returns activity. */ @NonNull public final TActivity getActivity() { return activity; } /** * Returns fragment where {@link ViewController} could be. * * @return Returns fragment. */ @NonNull public final TFragment getFragment() { return fragment; } /** * Returns view instantiated in {@link #getFragment()} fragment attached to {@link #getActivity()} activity. * Use it to inflate your views into at construction of this {@link ViewController}. * * @return Returns view. */ @NonNull public final ViewGroup getContainer() { return container; } /** * Returns if {@link ViewController} destroyed or not. * * @return True if it is destroyed. */ public final boolean isDestroyed() { return destroyed; } /** * Return a localized string from the application's package's default string table. * * @param resId Resource id for the string */ @NonNull public final String getString(@StringRes final int resId) { return getActivity().getString(resId); } /** * Return a localized formatted string from the application's package's default string table, substituting the format arguments as defined in * {@link java.util.Formatter} and {@link java.lang.String#format}. * * @param resId Resource id for the format string * @param formatArgs The format arguments that will be used for substitution. */ @NonNull public final String getString(@StringRes final int resId, @NonNull final Object... formatArgs) { return getActivity().getString(resId, formatArgs); } /** * Set the view controller content from a layout resource. * This layout is placed directly into the container's ({@link #getContainer()}) view hierarchy. * * @param layoutResId Resource ID to be inflated. */ public void setContentView(@LayoutRes final int layoutResId) { if (getContainer().getChildCount() > 0) { getContainer().removeAllViews(); } UiUtils.inflateAndAdd(layoutResId, getContainer()); } /** * Set the view controller content to an explicit view. * This view is placed directly into the container's ({@link #getContainer()}) view hierarchy. * * @param view The desired content to display. */ public void setContentView(@NonNull final View view) { setContentView(view, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } /** * Set the view controller content to an explicit view with specific layout parameters. * This view is placed directly into the container's ({@link #getContainer()}) view hierarchy. * * @param view The desired content to display; * @param layoutParams Layout parameters for the view. */ public void setContentView(@NonNull final View view, @NonNull final ViewGroup.LayoutParams layoutParams) { if (getContainer().getChildCount() > 0) { getContainer().removeAllViews(); } getContainer().addView(view, layoutParams); } /** * Look for a child view with the given id. If this view has the given id, return this view. * * @param id The id to search for; * @return The view that has the given id in the hierarchy. */ @NonNull @SuppressWarnings("unchecked") public T findViewById(@IdRes final int id) { final T viewById = (T) getContainer().findViewById(id); if (viewById == null) { throw new ShouldNotHappenException("No view for id=" + getActivity().getResources().getResourceName(id)); } return viewById; } /** * Return the color value associated with a particular resource ID. * Starting in {@link android.os.Build.VERSION_CODES#M}, the returned * color will be styled for the specified Context's theme. * * @param resId The resource id to search for data; * @return int A single color value in the form 0xAARRGGBB. */ @ColorInt public int getColor(@ColorRes final int resId) { return getActivity().getColorCompat(resId); } /** * Returns a drawable object associated with a particular resource ID. * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the * returned drawable will be styled for the specified Context's theme. * * @param resId The resource id to search for data; * @return Drawable An object that can be used to draw this resource. */ @NonNull public Drawable getDrawable(@DrawableRes final int resId) { return getActivity().getDrawableCompat(resId); } /** * Calls when activity configuring ActionBar, Toolbar, Sidebar etc. * If it will be called or not depends on {@link Fragment#hasOptionsMenu()} and {@link Fragment#isMenuVisible()}. * * @param menu The options menu in which you place your items; * @param inflater Helper to inflate menu items. */ public void onConfigureNavigation(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { // do nothing } /** * Calls right after construction of {@link ViewController}. * Happens at {@link ViewControllerFragment#onActivityCreated(View, ViewControllerActivity, Bundle)}. */ @CallSuper public void onCreate() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); baseLifecycleBindable.onCreate(); } /** * Calls when {@link ViewController} have started. * Happens at {@link ViewControllerFragment#onStart(View, ViewControllerActivity)}. */ @CallSuper public void onStart() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); baseLifecycleBindable.onStart(); } /** * Called when fragment is moved in started state and it's {@link #getFragment().isMenuVisible()} sets to true. * Usually it is indicating that user can't see fragment on screen and useful to track analytics events. */ public void onAppear() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); } /** * Calls when {@link ViewController} have resumed. * Happens at {@link ViewControllerFragment#onResume(View, ViewControllerActivity)}. */ @CallSuper public void onResume() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); } /** * Calls when {@link ViewController} have goes near out of memory state. * Happens at {@link ViewControllerFragment#onLowMemory()}. */ @CallSuper public void onLowMemory() { //do nothing } /** * Calls when {@link ViewController} have paused. * Happens at {@link ViewControllerFragment#onPause(View, ViewControllerActivity)}. */ @CallSuper public void onPause() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); } /** * Calls when {@link ViewController} should save it's state. * Happens at {@link ViewControllerFragment#onSaveInstanceState(Bundle)}. * Try not to use such method for saving state but use {@link ViewControllerFragment#getState()} from {@link #getFragment()}. */ @CallSuper public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); } /** * Called when fragment is moved in stopped state or it's {@link #getFragment().isMenuVisible()} sets to false. * Usually it is indicating that user can't see fragment on screen and useful to track analytics events. */ public void onDisappear() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); } /** * Calls when {@link ViewController} have stopped. * Happens at {@link ViewControllerFragment#onStop(View, ViewControllerActivity)}. */ @CallSuper public void onStop() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); baseLifecycleBindable.onStop(); } /** * Calls when {@link ViewController} have destroyed. * Happens usually at {@link ViewControllerFragment#onDestroyView(View)}. In some cases at {@link ViewControllerFragment#onDestroy()}. */ @CallSuper public void onDestroy() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); baseLifecycleBindable.onDestroy(); destroyed = true; } /** * Similar to {@link ViewControllerFragment#onOptionsItemSelected(MenuItem)}. * * @param item Selected menu item; * @return True if selection processed. */ public boolean onOptionsItemSelected(@NonNull final MenuItem item) { return false; } @SuppressWarnings("CPD-START") //CPD: it is same as in other implementation based on BaseLifecycleBindable @NonNull @Override public Subscription bind(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { return baseLifecycleBindable.bind(observable, onNextAction); } @NonNull @Override public Subscription untilStop(@NonNull final Observable observable) { return baseLifecycleBindable.untilStop(observable); } @NonNull @Override public Subscription untilStop(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { return baseLifecycleBindable.untilStop(observable, onNextAction); } @NonNull @Override public Subscription untilStop(@NonNull final Observable observable, @NonNull final Action1 onNextAction, @NonNull final Action1 onErrorAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); } @NonNull @Override public Subscription untilStop(@NonNull final Observable observable, @NonNull final Action1 onNextAction, @NonNull final Action1 onErrorAction, @NonNull final Action0 onCompletedAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override public Subscription untilDestroy(@NonNull final Observable observable) { return baseLifecycleBindable.untilDestroy(observable); } @NonNull @Override public Subscription untilDestroy(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction); } @NonNull @Override public Subscription untilDestroy(@NonNull final Observable observable, @NonNull final Action1 onNextAction, @NonNull final Action1 onErrorAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); } @NonNull @Override public Subscription untilDestroy(@NonNull final Observable observable, @NonNull final Action1 onNextAction, @NonNull final Action1 onErrorAction, @NonNull final Action0 onCompletedAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); } @SuppressWarnings("CPD-END") //CPD: it is same as in other implementation based on BaseLifecycleBindable /** * Helper class to simplify constructor override. */ public static class CreationContext { @NonNull private final ViewControllerActivity activity; @NonNull private final ViewControllerFragment fragment; @NonNull private final ViewGroup container; public CreationContext(@NonNull final ViewControllerActivity activity, @NonNull final ViewControllerFragment fragment, @NonNull final ViewGroup container) { this.activity = activity; this.fragment = fragment; this.container = container; } } }