/*
 * Copyright (C) 2017 Schürmann & Breitmoser GbR
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.sufficientlysecure.keychain.ui;


import java.io.IOException;
import java.util.List;

import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ViewAnimator;

import androidx.annotation.WorkerThread;
import androidx.appcompat.widget.SearchView;
import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.getbase.floatingactionbutton.FloatingActionButton;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
import eu.davidea.fastscroller.FastScroller;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener;
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener;
import eu.davidea.flexibleadapter.SelectableAdapter.Mode;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.KeySyncParcel;
import org.sufficientlysecure.keychain.operations.results.BenchmarkResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem;
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDummyItem;
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyHeader;
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem;
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectionableKeyItem;
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItemFactory;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel;
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Preferences;
import timber.log.Timber;


public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKeyItem>>
        implements SearchView.OnQueryTextListener, OnItemClickListener, OnItemLongClickListener, FabContainer {

    static final int REQUEST_ACTION = 1;
    private static final int REQUEST_DELETE = 2;
    private static final int REQUEST_VIEW_KEY = 3;

    private ActionMode mActionMode = null;

    private Button vSearchButton;
    private ViewAnimator vSearchContainer;

    private FloatingActionsMenu mFab;

    private KeyRepository keyRepository;
    private FlexibleKeyItemFactory flexibleKeyItemFactory;

    private Long queuedHighlightMasterKeyId;

    private final ActionMode.Callback mActionCallback = new ActionMode.Callback() {
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            mode.getMenuInflater().inflate(R.menu.key_list_multi, menu);
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            switch (item.getItemId()) {
                case R.id.menu_key_list_multi_encrypt: {
                    long[] keyIds = getSelectedMasterKeyIds();
                    multiSelectEncrypt(keyIds);
                    mode.finish();
                    break;
                }

                case R.id.menu_key_list_multi_delete: {
                    long[] keyIds = getSelectedMasterKeyIds();
                    boolean hasSecret = isAnySecretKeySelected();
                    multiSelectDelete(keyIds, hasSecret);
                    mode.finish();
                    break;
                }
            }
            return false;
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            mActionMode = null;
            if (getAdapter() != null) {
                getAdapter().clearSelection();
            }
        }
    };
    private FastScroller fastScroller;

    private void multiSelectDelete(long[] keyIds, boolean hasSecret) {
        Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class);
        intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, keyIds);
        intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret);
        if (hasSecret) {
            intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER,
                    Preferences.getPreferences(getActivity()).getPreferredKeyserver());
        }
        startActivityForResult(intent, REQUEST_DELETE);
    }

    private void multiSelectEncrypt(long[] keyIds) {
        Intent intent = new Intent(getActivity(), EncryptFilesActivity.class);
        intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA);
        intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, keyIds);

        startActivityForResult(intent, REQUEST_ACTION);
    }

    private long[] getSelectedMasterKeyIds() {
        FlexibleAdapter<FlexibleKeyItem> adapter = getAdapter();
        List<Integer> selectedPositions = adapter.getSelectedPositions();
        long[] keyIds = new long[selectedPositions.size()];
        for (int i = 0; i < selectedPositions.size(); i++) {
            FlexibleKeyDetailsItem selectedItem = adapter.getItem(selectedPositions.get(i), FlexibleKeyDetailsItem.class);
            if (selectedItem != null) {
                keyIds[i] = selectedItem.keyInfo.master_key_id();
            }
        }
        return keyIds;
    }

    private boolean isAnySecretKeySelected() {
        FlexibleAdapter<FlexibleKeyItem> adapter = getAdapter();
        for (int position : adapter.getSelectedPositions()) {
            FlexibleKeyDetailsItem item = adapter.getItem(position, FlexibleKeyDetailsItem.class);
            if (item != null && item.keyInfo.has_any_secret()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.key_list_fragment, container, false);

        mFab = view.findViewById(R.id.fab_main);

        FloatingActionButton fabQrCode = view.findViewById(R.id.fab_add_qr_code);
        FloatingActionButton fabCloud = view.findViewById(R.id.fab_add_cloud);
        FloatingActionButton fabFile = view.findViewById(R.id.fab_add_file);

        fabQrCode.setOnClickListener(v -> {
            mFab.collapse();
            scanQrCode();
        });
        fabCloud.setOnClickListener(v -> {
            mFab.collapse();
            searchCloud();
        });
        fabFile.setOnClickListener(v -> {
            mFab.collapse();
            importFile();
        });

        fastScroller = view.findViewById(R.id.fast_scroller);

        vSearchContainer = view.findViewById(R.id.search_container);
        vSearchButton = view.findViewById(R.id.search_button);
        vSearchButton.setOnClickListener(v -> startSearchForQuery());

        return view;
    }

    /**
     * Define Adapter and Loader on create of Activity
     */
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // show app name instead of "keys" from nav drawer
        FragmentActivity activity = getActivity();
        if (activity == null) {
            throw new NullPointerException("Activity must be bound!");
        }
        activity.setTitle(R.string.app_name);

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true);

        setLayoutManager(new LinearLayoutManager(activity));

        keyRepository = KeyRepository.create(requireContext());
        flexibleKeyItemFactory = new FlexibleKeyItemFactory(requireContext().getResources());

        Intent intent = getActivity().getIntent();
        if (intent != null && intent.hasExtra(ImportKeyResult.EXTRA_RESULT)) {
            ImportKeyResult importKeyResult = intent.getParcelableExtra(ImportKeyResult.EXTRA_RESULT);
            long[] importedMasterKeyIds = importKeyResult.getImportedMasterKeyIds();
            if (importedMasterKeyIds != null && importedMasterKeyIds.length > 0) {
                queuedHighlightMasterKeyId = importedMasterKeyIds[0];
            }
        }

        GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class);
        LiveData<List<FlexibleKeyItem>> liveData = viewModel.getGenericLiveData(requireContext(), this::loadFlexibleKeyItems);
        liveData.observe(this, this::onLoadKeyItems);
    }

    @WorkerThread
    private List<FlexibleKeyItem> loadFlexibleKeyItems() {
        List<UnifiedKeyInfo> unifiedKeyInfo = keyRepository.getAllUnifiedKeyInfo();
        return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo);
    }

    private void onLoadKeyItems(List<FlexibleKeyItem> flexibleKeyItems) {
        FlexibleAdapter<FlexibleKeyItem> adapter = getAdapter();
        if (adapter == null) {
            adapter = new FlexibleAdapter<FlexibleKeyItem>(flexibleKeyItems, this, true) {
                @Override
                public long getItemId(int position) {
                    FlexibleKeyItem item = getItem(position);
                    if (item instanceof FlexibleKeyDetailsItem) {
                        return ((FlexibleKeyDetailsItem) item).keyInfo.master_key_id();
                    }
                    return super.getItemId(position);
                }
            };
            adapter.setDisplayHeadersAtStartUp(true);
            adapter.setStickyHeaders(true);
            adapter.setMode(Mode.MULTI);
            setAdapter(adapter);
            adapter.setFastScroller(fastScroller);
            fastScroller.setBubbleTextCreator(this::getBubbleText);
        } else {
            adapter.updateDataSet(flexibleKeyItems, true);
        }
        maybeHighlightKey(adapter);
    }

    private void maybeHighlightKey(FlexibleAdapter<FlexibleKeyItem> adapter) {
        if (queuedHighlightMasterKeyId == null) {
            return;
        }
        for (int position = 0; position < adapter.getItemCount(); position++) {
            if (adapter.getItemId(position) == queuedHighlightMasterKeyId) {
                adapter.smoothScrollToPosition(position);
            }
        }

        queuedHighlightMasterKeyId = null;
    }

    private String getBubbleText(int position) {
        FlexibleKeyItem item = getAdapter().getItem(position);
        if (item == null) {
            return "";
        }
        if (item instanceof FlexibleSectionableKeyItem) {
            FlexibleKeyHeader header = ((FlexibleSectionableKeyItem) item).getHeader();
            return header.getSectionTitle();
        }
        if (item instanceof FlexibleKeyHeader) {
            return ((FlexibleKeyHeader) item).getSectionTitle();
        }
        return "";
    }

    @Override
    public void onStart() {
        super.onStart();

        checkClipboardForPublicKeyMaterial();
    }

    private void checkClipboardForPublicKeyMaterial() {
        CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());

        new AsyncTask<Void, Void, Boolean>() {
            @Override
            protected Boolean doInBackground(Void... voids) {
                if (clipboardText == null) {
                    return false;
                }

                // see if it looks like a pgp thing
                String publicKeyContent = PgpHelper.getPgpPublicKeyContent(clipboardText);

                return publicKeyContent != null;
            }

            @Override
            protected void onPostExecute(Boolean clipboardDataFound) {
                super.onPostExecute(clipboardDataFound);

                if (clipboardDataFound) {
                    showClipboardDataSnackbar();
                }
            }
        }.execute();
    }

    private void showClipboardDataSnackbar() {
        Activity activity = getActivity();
        if (activity == null) {
            return;
        }

        Notify.create(activity, R.string.snack_keylist_clipboard_title, Notify.LENGTH_INDEFINITE, Style.OK,
                () -> {
                    Intent intentImportExisting = new Intent(getActivity(), ImportKeysActivity.class);
                    intentImportExisting.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_CLIPBOARD);
                    startActivity(intentImportExisting);
                }, R.string.snack_keylist_clipboard_action).show(this);
    }

    private void startSearchForQuery() {
        Activity activity = getActivity();
        if (activity == null) {
            return;
        }

        Intent searchIntent = new Intent(activity, ImportKeysActivity.class);
        searchIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, getAdapter().getFilter(String.class));
        searchIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
        startActivity(searchIntent);
    }

    @Override
    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
        inflater.inflate(R.menu.key_list, menu);

        if (Constants.DEBUG) {
            menu.findItem(R.id.menu_key_list_debug_bench).setVisible(true);
            menu.findItem(R.id.menu_key_list_debug_read).setVisible(true);
            menu.findItem(R.id.menu_key_list_debug_write).setVisible(true);
            menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true);
            menu.findItem(R.id.menu_key_list_debug_bgsync).setVisible(true);
        }

        // Get the searchview
        MenuItem searchItem = menu.findItem(R.id.menu_key_list_search);
        SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);

        // Execute this when searching
        searchView.setOnQueryTextListener(this);

        // Erase search result without focus
        MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() {
            @Override
            public boolean onMenuItemActionExpand(MenuItem item) {
                return true;
            }

            @Override
            public boolean onMenuItemActionCollapse(MenuItem item) {
                getAdapter().setFilter(null);
                getAdapter().filterItems();
                return true;
            }
        });

        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onItemClick(View view, int position) {
        FlexibleKeyItem item = getAdapter().getItem(position);
        if (item == null) {
            return false;
        }

        if (item instanceof FlexibleKeyDummyItem) {
            createKey();
            return false;
        }

        if (!(item instanceof FlexibleKeyDetailsItem)) {
            return false;
        }

        if (mActionMode != null && position != RecyclerView.NO_POSITION) {
            toggleSelection(position);
            return true;
        }

        long masterKeyId = ((FlexibleKeyDetailsItem) item).keyInfo.master_key_id();
        Intent viewIntent = ViewKeyActivity.getViewKeyActivityIntent(requireActivity(), masterKeyId);
        startActivityForResult(viewIntent, REQUEST_VIEW_KEY);
        return false;
    }

    @Override
    public void onItemLongClick(int position) {
        if (getAdapter().getItem(position) instanceof FlexibleKeyDetailsItem) {
            if (mActionMode == null) {
                FragmentActivity activity = getActivity();
                if (activity != null) {
                    mActionMode = activity.startActionMode(mActionCallback);
                }
            }
            toggleSelection(position);
        }
    }

    private void toggleSelection(int position) {
        getAdapter().toggleSelection(position);

        int count = getAdapter().getSelectedItemCount();

        if (count == 0) {
            mActionMode.finish();
        } else {
            setContextTitle(count);
        }
    }

    private void setContextTitle(int selectedCount) {
        String keysSelected = getResources().getQuantityString(
                R.plurals.key_list_selected_keys, selectedCount, selectedCount);
        mActionMode.setTitle(keysSelected);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_key_list_create: {
                createKey();
                return true;
            }
            case R.id.menu_key_list_update_all_keys: {
                updateAllKeys();
                return true;
            }
            case R.id.menu_key_list_debug_read: {
                try {
                    KeychainDatabase.debugBackup(getActivity(), true);
                    Notify.create(getActivity(), "Restored debug_backup.db", Notify.Style.OK).show();
                    DatabaseNotifyManager.create(requireContext()).notifyAllKeysChange();
                } catch (IOException e) {
                    Timber.e(e, "IO Error");
                    Notify.create(getActivity(), "IO Error " + e.getMessage(), Notify.Style.ERROR).show();
                }
                return true;
            }
            case R.id.menu_key_list_debug_write: {
                try {
                    KeychainDatabase.debugBackup(getActivity(), false);
                    Notify.create(getActivity(), "Backup to debug_backup.db completed", Notify.Style.OK).show();
                } catch (IOException e) {
                    Timber.e(e, "IO Error");
                    Notify.create(getActivity(), "IO Error: " + e.getMessage(), Notify.Style.ERROR).show();
                }
                return true;
            }
            case R.id.menu_key_list_debug_first_time: {
                Preferences prefs = Preferences.getPreferences(getActivity());
                prefs.setFirstTime(true);
                Intent intent = new Intent(getActivity(), CreateKeyActivity.class);
                intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true);
                startActivity(intent);
                getActivity().finish();
                return true;
            }
            case R.id.menu_key_list_debug_bgsync: {
                KeyserverSyncManager.debugRunSyncNow(requireContext());
                return true;
            }
            case R.id.menu_key_list_debug_bench: {
                benchmark();
                return true;
            }
            default: {
                return super.onOptionsItemSelected(item);
            }
        }
    }

    @Override
    public boolean onQueryTextSubmit(String s) {
        return true;
    }

    @Override
    public boolean onQueryTextChange(String searchText) {
        getAdapter().setFilter(searchText);
        getAdapter().filterItems(300);

        if (searchText.length() > 2) {
            vSearchButton.setText(getString(R.string.btn_search_for_query, searchText));
            vSearchContainer.setDisplayedChild(1);
            vSearchContainer.setVisibility(View.VISIBLE);
        } else {
            vSearchContainer.setDisplayedChild(0);
            vSearchContainer.setVisibility(View.GONE);
        }

        return true;
    }

    private void searchCloud() {
        Intent importIntent = new Intent(getActivity(), ImportKeysActivity.class);
        importIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, (String) null); // hack to show only cloud tab
        startActivity(importIntent);
    }

    private void scanQrCode() {
        Intent scanQrCode = new Intent(getActivity(), ImportKeysProxyActivity.class);
        scanQrCode.setAction(ImportKeysProxyActivity.ACTION_SCAN_IMPORT);
        startActivityForResult(scanQrCode, REQUEST_ACTION);
    }

    private void importFile() {
        Intent intentImportExisting = new Intent(getActivity(), ImportKeysActivity.class);
        intentImportExisting.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
        startActivityForResult(intentImportExisting, REQUEST_ACTION);
    }

    private void createKey() {
        Intent intent = new Intent(getActivity(), CreateKeyActivity.class);
        startActivityForResult(intent, REQUEST_ACTION);
    }

    private void updateAllKeys() {
        CryptoOperationHelper.Callback<KeySyncParcel, ImportKeyResult> callback
                = new CryptoOperationHelper.Callback<KeySyncParcel, ImportKeyResult>() {

            @Override
            public KeySyncParcel createOperationInput() {
                return KeySyncParcel.createRefreshAll();
            }

            @Override
            public void onCryptoOperationSuccess(ImportKeyResult result) {
                result.createNotify(getActivity()).show();
            }

            @Override
            public void onCryptoOperationCancelled() {
            }

            @Override
            public void onCryptoOperationError(ImportKeyResult result) {
                result.createNotify(getActivity()).show();
            }

            @Override
            public boolean onCryptoSetProgress(String msg, int progress, int max) {
                return false;
            }
        };

        CryptoOperationHelper opHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_importing);
        opHelper.setProgressCancellable(true);
        opHelper.cryptoOperation();
    }

    private void benchmark() {
        CryptoOperationHelper.Callback<BenchmarkInputParcel, BenchmarkResult> callback
                = new CryptoOperationHelper.Callback<BenchmarkInputParcel, BenchmarkResult>() {

            @Override
            public BenchmarkInputParcel createOperationInput() {
                return BenchmarkInputParcel.newInstance(); // we want to perform a full consolidate
            }

            @Override
            public void onCryptoOperationSuccess(BenchmarkResult result) {
                result.createNotify(getActivity()).show();
            }

            @Override
            public void onCryptoOperationCancelled() {
            }

            @Override
            public void onCryptoOperationError(BenchmarkResult result) {
                result.createNotify(getActivity()).show();
            }

            @Override
            public boolean onCryptoSetProgress(String msg, int progress, int max) {
                return false;
            }
        };

        CryptoOperationHelper opHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing);
        opHelper.cryptoOperation();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_DELETE: {
                if (mActionMode != null) {
                    mActionMode.finish();
                }

                if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
                    OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
                    result.createNotify(getActivity()).show();
                } else {
                    super.onActivityResult(requestCode, resultCode, data);
                }
                break;
            }
            case REQUEST_ACTION: {
                // if a result has been returned, display a notify
                if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
                    OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
                    result.createNotify(getActivity()).show();
                } else {
                    super.onActivityResult(requestCode, resultCode, data);
                }
                break;
            }
            case REQUEST_VIEW_KEY: {
                if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
                    OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
                    result.createNotify(getActivity()).show();
                } else {
                    super.onActivityResult(requestCode, resultCode, data);
                }
                break;
            }
        }
    }

    @Override
    public void fabMoveUp(int height) {
        ObjectAnimator anim = ObjectAnimator.ofFloat(mFab, "translationY", 0, -height);
        // we're a little behind, so skip 1/10 of the time
        anim.setDuration(270);
        anim.start();
    }

    @Override
    public void fabRestorePosition() {
        ObjectAnimator anim = ObjectAnimator.ofFloat(mFab, "translationY", 0);
        // we're a little ahead, so wait a few ms
        anim.setStartDelay(70);
        anim.setDuration(300);
        anim.start();
    }

}
