milosmns/silly-android

View on GitHub
sillyandroid/src/main/java/me/angrybyte/sillyandroid/dialogs/DialogManagerImpl.java

Summary

Maintainability
D
1 day
Test Coverage
package me.angrybyte.sillyandroid.dialogs;

import android.app.Dialog;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.util.Log;

import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/**
 * The basic implementation of the {@link DialogManager} API. Of course, you can modify this implementation
 * by extending the class, or even write your own version - but really there is no need as this covers 99% of
 * the use cases you might come across, and it's also covered by JUnit so you don't have to worry about tests.
 */
public class DialogManagerImpl implements DialogManager {

    private static final String TAG = DialogManagerImpl.class.getSimpleName();

    static final class DialogInfo implements Parcelable {

        public final int id;
        @Nullable
        public final Bundle config;
        public final boolean isFragment;

        public DialogInfo(final int id, @Nullable final Bundle config, final boolean isFragment) {
            this.id = id;
            this.config = config;
            this.isFragment = isFragment;
        }

        // <editor-fold desc="Equals & HashCode">
        @Override
        public boolean equals(final Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            DialogInfo info = (DialogInfo) o;
            return id == info.id && isFragment == info.isFragment &&
                    (config != null ? config.equals(info.config) : info.config == null);
        }

        @Override
        public int hashCode() {
            int result = id;
            result = 31 * result + (config != null ? config.hashCode() : 0);
            result = 31 * result + (isFragment ? 1 : 0);
            return result;
        }
        // </editor-fold>

        // <editor-fold desc="Parcelable implementation">
        public DialogInfo(Parcel in) {
            id = in.readInt();
            config = in.readBundle(getClass().getClassLoader());
            isFragment = in.readByte() != 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(id);
            dest.writeBundle(config);
            dest.writeByte((byte) (isFragment ? 1 : 0));
        }

        @Override
        public int describeContents() {
            return 0;
        }

        public static final Creator<DialogInfo> CREATOR = new Creator<DialogInfo>() {
            @Override
            public DialogInfo createFromParcel(Parcel in) {
                return new DialogInfo(in);
            }

            @Override
            public DialogInfo[] newArray(int size) {
                return new DialogInfo[size];
            }
        };
        // </editor-fold>
    }

    static final class State implements Parcelable {

        public int size;
        public Set<DialogInfo> configs;

        public State(@NonNull final Map<Integer, DialogInfo> dialogConfigs) {
            configs = new LinkedHashSet<>(dialogConfigs.values());
            size = configs.size();
        }

        // <editor-fold desc="Equals & HashCode">
        @Override
        public boolean equals(final Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            State state = (State) o;
            return size == state.size &&
                    (configs != null ? configs.equals(state.configs) : state.configs == null);
        }

        @Override
        public int hashCode() {
            int result = size;
            result = 31 * result + (configs != null ? configs.hashCode() : 0);
            return result;
        }
        // </editor-fold>

        // <editor-fold desc="Parcelable implementation">
        public State(Parcel in) {
            size = in.readInt();
            configs = new LinkedHashSet<>();
            for (int i = 0; i < size; i++) {
                final Parcelable parcelable = in.readParcelable(getClass().getClassLoader());
                if (parcelable instanceof DialogInfo) {
                    configs.add((DialogInfo) parcelable);
                }
            }
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(size);
            for (DialogInfo iDialogInfo : configs) {
                dest.writeParcelable(iDialogInfo, 0);
            }
        }

        @Override
        public int describeContents() {
            return 0;
        }

        public static final Creator<State> CREATOR = new Creator<State>() {
            @Override
            public State createFromParcel(Parcel in) {
                return new State(in);
            }

            @Override
            public State[] newArray(int size) {
                return new State[size];
            }
        };
        // </editor-fold>
    }

    @Nullable
    private DialogManagerCallback mCallback;
    @Nullable
    private DialogManagerListener mListener;
    @NonNull
    private final WeakReference<FragmentManager> mFragmentManagerRef;
    private Map<Integer, DialogInfo> mDialogConfigs = new LinkedHashMap<>();
    private Map<Integer, Dialog> mDialogInstances = new LinkedHashMap<>();
    private Map<Integer, DialogFragment> mDialogFragmentInstances = new LinkedHashMap<>();

    public DialogManagerImpl(@NonNull final FragmentManager fragmentManager) {
        mFragmentManagerRef = new WeakReference<>(fragmentManager);
    }

    @Override
    public void setCallback(@Nullable final DialogManagerCallback callback) {
        mCallback = callback;
    }

    @Nullable
    @Override
    public DialogManagerCallback getCallback() {
        return mCallback;
    }

    @Override
    public void setListener(@Nullable final DialogManagerListener listener) {
        mListener = listener;
    }

    @Nullable
    @Override
    public DialogManagerListener getListener() {
        return mListener;
    }

    @Override
    public void showDialog(final int dialogId) {
        showDialog(dialogId, null);
    }

    @Override
    public void showDialog(final int dialogId, @Nullable final Bundle config) {
        final Dialog created = createDialog(dialogId, config);
        if (created == null) { return; }
        showDialogInternal(dialogId, created);
    }

    @Override
    public void showDialogFragment(final int dialogId) {
        showDialog(dialogId, null);
    }

    @Override
    public void showDialogFragment(final int dialogId, @Nullable final Bundle config) {
        final DialogFragment created = createDialogFragment(dialogId, config);
        if (created == null) { return; }
        showDialogFragmentInternal(dialogId, created);
    }

    @Override
    public boolean isDialogShowing(final int dialogId) {
        final Dialog dialog = mDialogInstances.get(dialogId);
        if (dialog != null) {
            return dialog.isShowing();
        }
        final DialogFragment dialogFragment = mDialogFragmentInstances.get(dialogId);
        // noinspection SimplifiableIfStatement
        if (dialogFragment != null && dialogFragment.getDialog() != null) {
            return dialogFragment.getDialog().isShowing();
        }
        return false;
    }

    @Override
    public void dismissDialog(final int dialogId) {
        final Dialog dialog = mDialogInstances.get(dialogId);
        if (dialog != null) {
            dialog.dismiss();
            mDialogInstances.remove(dialogId);
            mDialogConfigs.remove(dialogId);
            return;
        }
        final DialogFragment dialogFragment = mDialogFragmentInstances.get(dialogId);
        if (dialogFragment != null) {
            dialogFragment.dismiss();
            mDialogFragmentInstances.remove(dialogId);
            mDialogConfigs.remove(dialogId);
        }
    }

    @NonNull
    @Override
    public Parcelable saveState() {
        return new State(mDialogConfigs);
    }

    @Override
    public void restoreState(@Nullable final Parcelable state, final boolean showNow) {
        if (state instanceof State) {
            final State toRestoreFrom = (State) state;
            recreateFromConfigs(toRestoreFrom.configs, showNow);
        }
    }

    @Override
    public void recreateAll(final boolean showNow) {
        final Collection<DialogInfo> configs = new LinkedHashSet<>(mDialogConfigs.values());
        dismissAll();
        recreateFromConfigs(configs, showNow);
    }

    @Override
    public void unhideAll() {
        for (Dialog iDialog : mDialogInstances.values()) {
            if (!iDialog.isShowing()) {
                iDialog.show();
            }
        }
        for (DialogFragment iDialogFragment : mDialogFragmentInstances.values()) {
            if (iDialogFragment.getDialog() != null && !iDialogFragment.getDialog().isShowing()) {
                iDialogFragment.getDialog().show();
            }
        }
    }

    @Override
    public void hideAll() {
        for (Dialog iDialog : mDialogInstances.values()) {
            if (iDialog.isShowing()) {
                iDialog.hide();
            }
        }
        for (DialogFragment iDialogFragment : mDialogFragmentInstances.values()) {
            if (iDialogFragment.getDialog() != null && iDialogFragment.getDialog().isShowing()) {
                iDialogFragment.getDialog().hide();
            }
        }
    }

    @Override
    public void dismissAll() {
        for (Dialog iDialog : mDialogInstances.values()) {
            if (iDialog.isShowing()) {
                iDialog.dismiss();
            }
        }
        for (DialogFragment iDialogFragment : mDialogFragmentInstances.values()) {
            if (iDialogFragment.getDialog() != null && iDialogFragment.getDialog().isShowing()) {
                iDialogFragment.dismissAllowingStateLoss();
            }
        }
        clearAllMappings();
    }

    @Override
    public void dispose() {
        dismissAll();
        mCallback = null;
        mListener = null;
        mFragmentManagerRef.clear();
    }

    /* Private helpers */

    @Nullable
    private Dialog createDialog(final int dialogId, @Nullable final Bundle config) {
        if (mCallback == null) {
            Log.w(TAG, "Can't show a dialog without the callback being set prior to this call");
            return null;
        }
        final Dialog created = mCallback.onCreateDialog(dialogId, config);
        mDialogConfigs.put(dialogId, new DialogInfo(dialogId, config, false));
        mDialogInstances.put(dialogId, created);
        return created;
    }

    @Nullable
    private DialogFragment createDialogFragment(final int dialogId, @Nullable final Bundle config) {
        if (mCallback == null) {
            Log.w(TAG, "Can't create a dialog fragment without the callback being set prior to this call");
            return null;
        }
        final DialogFragment created = mCallback.onCreateDialogFragment(dialogId, config);
        mDialogConfigs.put(dialogId, new DialogInfo(dialogId, config, true));
        mDialogFragmentInstances.put(dialogId, created);
        return created;
    }

    private void showDialogInternal(final int dialogId, @NonNull final Dialog instance) {
        instance.setOnShowListener(dialog -> {
            if (mListener != null) {
                mListener.onDialogShown(dialogId);
            }
        });
        instance.setOnDismissListener(dialog -> {
            if (mListener != null) {
                mListener.onDialogDismissed(dialogId);
                mDialogConfigs.remove(dialogId);
                mDialogInstances.remove(dialogId);
            }
        });
        instance.show();
    }

    private void showDialogFragmentInternal(final int dialogId, @NonNull final DialogFragment instance) {
        final FragmentManager manager = mFragmentManagerRef.get();
        if (manager == null) {
            Log.w(TAG, "Can't show a dialog fragment, FragmentManager instance expired");
            return;
        }
        instance.show(manager, getFragmentTag(dialogId));
        // DialogFragment#getDialog() is `null` prior to this call
        manager.executePendingTransactions();
        instance.getDialog().setOnShowListener(dialog -> {
            if (mListener != null) {
                mListener.onDialogShown(dialogId);
            }
        });
        instance.getDialog().setOnDismissListener(dialog -> {
            if (mListener != null) {
                mListener.onDialogDismissed(dialogId);
                mDialogConfigs.remove(dialogId);
                mDialogFragmentInstances.remove(dialogId);
            }
        });
    }

    private void recreateFromConfigs(@NonNull final Collection<DialogInfo> configs, boolean showNow) {
        clearAllMappings();
        for (DialogInfo iDialogInfo : configs) {
            if (!iDialogInfo.isFragment) {
                final Dialog created = createDialog(iDialogInfo.id, iDialogInfo.config);
                if (created == null) { return; }
                if (showNow) {
                    showDialogInternal(iDialogInfo.id, created);
                }
            } else {
                final DialogFragment created = createDialogFragment(iDialogInfo.id, iDialogInfo.config);
                if (created == null) { return; }
                if (showNow) {
                    showDialogFragmentInternal(iDialogInfo.id, created);
                }
            }
        }
    }

    private void clearAllMappings() {
        mDialogConfigs.clear();
        mDialogInstances.clear();
        mDialogFragmentInstances.clear();
    }

    @NonNull
    private String getFragmentTag(final int dialogId) {
        return TAG + "_ID:" + dialogId;
    }

}