com.juick.android.ThreadFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.juick.android.ThreadFragment.java

Source

/*
 * Juick
 * Copyright (C) 2008-2012, Ugnich Anton
 *
 * 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 com.juick.android;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.*;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.*;
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentActivity;
import android.text.Editable;
import android.view.*;
import android.view.animation.*;
import android.widget.*;
import com.juickadvanced.data.juick.JuickMessage;
import android.support.v4.app.ListFragment;
import com.juickadvanced.data.MessageID;
import com.juick.android.juick.MessagesSource;
import com.juickadvanced.R;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;

/**
 * @author Ugnich Anton
 */
public class ThreadFragment extends ListFragment implements AdapterView.OnItemClickListener, View.OnTouchListener,
        XMPPMessageReceiver.MessageReceiverListener {

    public static int instanceCount;

    {
        instanceCount++;
    }

    private boolean paused;
    private Runnable doOnClick;
    private long doOnClickActualTime;
    private MyImageView navMenu;
    /**
     * @see MessagesFragment#alternativeLongClick
     */
    private JuickMessage prefetched; // this is partially obtained thread originating in 'pending' replies screen
    private Toast shownThreadToast;
    private boolean navigationMenuShown;
    private JuickFragmentActivity parent;
    private JuickMessage originalMessage;

    public interface ThreadExternalUpdater {

        void terminate();

        interface Listener {
            public void onNewMessages(ArrayList<JuickMessage> messages);
        }

        public void setListener(Listener listener);

        public void setPaused(boolean paused);

    }

    private ThreadFragmentListener parentActivity;
    public JuickMessagesAdapter listAdapter;
    private ScaleGestureDetector mScaleDetector = null;
    private ThreadExternalUpdater ws = null;
    private MessageID mid = null;

    Handler handler;
    private Object restoreData;
    boolean implicitlyCreated;
    Utils.ServiceGetter<XMPPService> xmppServiceServiceGetter;
    MessagesLoadNotification notification;
    SharedPreferences sp;
    private boolean trackLastRead;
    ProgressBar large, small;
    View cached_copy_label;
    View list_container;

    class ThreadMessagesLoadNotification extends MessagesLoadNotification
            implements Utils.HasCachedCopyNotification {

        ThreadMessagesLoadNotification(Activity activity, Handler handler) {
            super(activity, handler);
        }

        @Override
        public void onCachedCopyObtained(ArrayList<JuickMessage> messages) {

            onObtainAllThread(new MessagesFragment.RetainedData(messages, null), true);
            System.out.println();
        }
    }

    MessagesSource parentMessagesSource;
    BroadcastReceiver recv = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            if (intent.hasExtra("threadMessage")) {
                if (listAdapter != null && listAdapter.getCount() > 0) {
                    JuickMessage item = listAdapter.getItem(0);
                    JuickMessage jm = (JuickMessage) intent.getSerializableExtra("threadMessage");
                    if (jm.getMID().equals(item.getMID())) {
                        boolean found = false;
                        for (int i = 0; i < listAdapter.getCount(); i++) {
                            JuickMessage test = listAdapter.getItem(i);
                            if (test.getRID() == jm.getRID()) {
                                found = true;
                                break;
                            }
                        }
                        if (!found) {
                            ArrayList<JuickMessage> s = new ArrayList<JuickMessage>();
                            s.add(jm);
                            listAdapter.addAllMessages(s);
                            listAdapter.notifyDataSetChanged();
                        }
                    }
                }
            }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        xmppServiceServiceGetter = new Utils.ServiceGetter<XMPPService>(getActivity(), XMPPService.class);
        handler = new Handler();
        parentMessagesSource = (MessagesSource) getArguments().getSerializable("messagesSource");
        originalMessage = (JuickMessage) getArguments().getSerializable("originalMessage");
        prefetched = (JuickMessage) getArguments().getSerializable("prefetched");
        if (parentMessagesSource != null)
            parentMessagesSource.setContext(getActivity());
        sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
        trackLastRead = sp.getBoolean("lastReadMessages", true);
        if (Build.VERSION.SDK_INT >= 8) {
            mScaleDetector = new ScaleGestureDetector(getActivity(), new ScaleListener());
        }
        IntentFilter f = new IntentFilter(XMPPService.ACTION_THREAD_MESSAGE_RECEIVED);
        getActivity().registerReceiver(recv, f);

    }

    public void init(Object restoreData, JuickFragmentActivity parent) {
        this.restoreData = restoreData;
        this.parent = parent;
        implicitlyCreated = false;
    }

    public ThreadFragment() {
        implicitlyCreated = true;
    }

    FlyingItem[] flyingItems;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View inflate = inflater.inflate(R.layout.fragment_view, null);
        return inflate;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            parentActivity = (ThreadFragmentListener) activity;
            navMenu = (MyImageView) activity.findViewById(R.id.navmenu);
            if (navMenu != null) {
                navMenu.setVisibility(View.GONE);
                initNavMenuTranslationX = navMenu.initialTranslationX;
                FlyingItem top = new FlyingItem(activity.getWindow().getDecorView(), R.id.navbar_top);
                FlyingItem bottom = new FlyingItem(activity.getWindow().getDecorView(), R.id.navbar_bottom);
                flyingItems = new FlyingItem[] { top, bottom };
            }
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement ThreadFragmentListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        MainActivity.restyleChildrenOrWidget(view);
        Bundle args = getArguments();
        if (args != null) {
            mid = (MessageID) args.getSerializable("mid");
        }
        if (mid == null) {
            return;
        }
        large = (ProgressBar) view.findViewById(R.id.progress_bar);
        small = (ProgressBar) view.findViewById(R.id.progress_bar_small);
        cached_copy_label = view.findViewById(R.id.cached_copy_label);

        getListView().setOnTouchListener(this);

        initAdapter();
        MessagesFragment.installDividerColor(getListView());

        if (parent != null)
            parent.imagePreviewHelper = listAdapter.imagePreviewHelper = new ImagePreviewHelper(
                    (ViewGroup) getView().findViewById(R.id.imagepreview_container), getActivity());
    }

    private void initWebSocket() {
        if (ws != null) {
            ws.terminate();
            ws = null;

        }
        if (ws == null) {
            ws = MainActivity.getMicroBlog(mid.getMicroBlogCode()).getThreadExternalUpdater(getActivity(), mid);
            if (ws != null) {
                ws.setListener(new ThreadExternalUpdater.Listener() {
                    @Override
                    public void onNewMessages(ArrayList<JuickMessage> messages) {
                        if (!isAdded()) {
                            return;
                        }
                        onWebSocketMessages(messages);
                    }
                });
            }
        }
    }

    public void reload() {
        getView().findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
        large.setVisibility(View.VISIBLE);
        small.setVisibility(View.GONE);
        setCachedViewIndicators(false);
        getView().findViewById(android.R.id.list).setVisibility(View.GONE);
        restoreData = null;
        initAdapter();
    }

    public Object saveState() {
        MessagesFragment.RetainedData rd = new MessagesFragment.RetainedData(new ArrayList<JuickMessage>(),
                getListView().onSaveInstanceState());
        int count = listAdapter.getCount();
        for (int i = 0; i < count; i++) {
            JuickMessage item = listAdapter.getItem(i);
            if (item.User != null)
                rd.messages.add(item);
        }
        return rd;
    }

    private void initAdapter() {
        final FragmentActivity activity = getActivity();
        listAdapter = new JuickMessagesAdapter(activity, this, JuickMessagesAdapter.TYPE_THREAD,
                JuickMessagesAdapter.SUBTYPE_OTHER);
        if (implicitlyCreated || restoreData != null) {
            getView().findViewById(android.R.id.empty).setVisibility(View.GONE);
        }
        if (implicitlyCreated) {
            return;
        }
        final ListView listView = getListView();
        listView.setOnItemClickListener(this);
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(final AdapterView<?> parent, final View view, final int position,
                    final long id) {
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (activity.isFinishing()) {
                            System.out.println("OK");
                            return;
                        }
                        Object itemAtPosition = parent.getItemAtPosition(position);
                        if (itemAtPosition instanceof JuickMessage && parentMessagesSource != null) {
                            final JuickMessage msg = (JuickMessage) itemAtPosition;
                            if (msg.getMID() != null) {
                                doOnClickActualTime = System.currentTimeMillis();
                                doOnClick = new Runnable() {
                                    @Override
                                    public void run() {
                                        MessageMenu messageMenu = MainActivity.getMicroBlog(msg).getMessageMenu(
                                                activity, parentMessagesSource, listView, listAdapter);
                                        if (messageMenu != null) {
                                            messageMenu.onItemLongClick(parent, view, position, id);
                                        }
                                    }
                                };
                                doOnClick.run();
                                doOnClick = null;

                            }

                        }
                    }
                }, 100);
                return false;
            }
        });
        notification = new ThreadMessagesLoadNotification(activity, handler);
        navMenu.setVisibility(View.GONE);
        Thread thr = new Thread(new Runnable() {

            public void run() {
                final Utils.Function<Void, MessagesFragment.RetainedData> then = new Utils.Function<Void, MessagesFragment.RetainedData>() {
                    @Override
                    public Void apply(MessagesFragment.RetainedData retainedData) {
                        onObtainAllThread(retainedData, false);
                        return null;
                    }
                };
                if (restoreData == null) {
                    if (parentMessagesSource != null) {
                        if (prefetched != null) {
                            ArrayList<JuickMessage> messages = new ArrayList<JuickMessage>();
                            messages.add(prefetched.contextPost);
                            if (prefetched.contextReply != null) {
                                messages.add(prefetched.contextReply);
                            }
                            messages.add(prefetched);
                            prefetched.contextPost = null; // don't confuse later
                            prefetched.contextReply = null;
                            prefetched = null; // to enable proper reload
                            then.apply(new MessagesFragment.RetainedData(messages, null));
                        } else {
                            parentMessagesSource.getChildren(mid, notification,
                                    new Utils.Function<Void, ArrayList<JuickMessage>>() {
                                        @Override
                                        public Void apply(ArrayList<JuickMessage> messages) {
                                            if (messages != null && messages.size() > 0
                                                    && originalMessage != null) {
                                                if (messages.get(0).getRID() != 0) {
                                                    messages.add(0, originalMessage);
                                                }
                                            }
                                            preprocessMessages(messages);
                                            then.apply(new MessagesFragment.RetainedData(messages, null));
                                            return null;
                                        }
                                    });
                        }
                    }
                } else {
                    then.apply((MessagesFragment.RetainedData) restoreData);
                    restoreData = null;
                }

            }
        }, "Init adapter, mid=" + mid);
        thr.start();
    }

    /**
     * calculate delta times
     * @param messages
     */
    private void preprocessMessages(ArrayList<JuickMessage> messages) {
        if (messages == null || messages.size() == 0)
            return;
        Date prevTime = messages.get(0).Timestamp;
        for (int i = 1; i < messages.size(); i++) {
            JuickMessage message = messages.get(i);
            message.deltaTime = message.Timestamp.getTime() - prevTime.getTime();
            if (message.Timestamp.getDate() != prevTime.getDate()) {
                message.deltaTime = Long.MIN_VALUE;
            }
            prevTime = message.Timestamp;
        }
    }

    /**
     * unsafe
     */
    private void onObtainAllThread(final MessagesFragment.RetainedData retainedData, final boolean cached) {
        final ArrayList<JuickMessage> messages = retainedData.messages;
        if (isAdded()) {
            getActivity().runOnUiThread(new Runnable() {

                public void run() {
                    try {
                        getListView();
                    } catch (Exception e) {
                        handler.postDelayed(this, 50); // bugs in fragment manager
                        return;
                    }
                    if (notification.lastError != null && !cached) {
                        notification.statusText.setText(notification.lastError);
                        notification.progressBar.setVisibility(View.GONE);
                    } else {
                        Bundle args = getArguments();
                        Parcelable listPosition = retainedData.viewState;
                        boolean disableScrollToEnd = false;
                        if (listPosition == null && listAdapter.getCount() > 0) {
                            // probably transition from cached data to live data
                            listPosition = getListView().onSaveInstanceState();
                            int addMarkOnComment = listAdapter.getCount() - 2 + 1; // totalRecs-body-separator, 0-based (0=first comment)
                            if (messages.size() > addMarkOnComment)
                                messages.get(addMarkOnComment).continuationInformation = getString(
                                        R.string.UnreadPeriodStart);
                            disableScrollToEnd = true;
                            listAdapter.clear();
                        }
                        boolean scrollToBottom = args != null && args.getBoolean("scrollToBottom", false);
                        if (scrollToBottom && !disableScrollToEnd) {
                            getListView().setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
                            getListView().setStackFromBottom(true);
                        } else {
                            getListView().setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
                            getListView().setStackFromBottom(false);
                        }
                        if (parentMessagesSource instanceof MessagesSource.PersistedSorter && messages.size() > 0) {
                            MessagesSource.Sorter currentSorter = ((MessagesSource.PersistedSorter) parentMessagesSource)
                                    .getCurrentSorter();
                            JuickMessage first = messages.remove(0);
                            Collections.sort(messages, currentSorter);
                            messages.add(0, first);
                        }
                        listAdapter.addAllMessages(messages);
                        setListAdapter(listAdapter);
                        if (cached) {
                            getListView().setSelection(getListView().getAdapter().getCount() - 1);
                        }
                        getView().findViewById(android.R.id.list).setVisibility(View.VISIBLE);

                        initAdapterStageTwo(cached);
                        if (listPosition != null) {
                            try {
                                getListView().onRestoreInstanceState(listPosition);
                            } catch (Exception e) {
                                /* tmp fix for
                                java.lang.IllegalStateException: Content view not yet created
                                    at+android.support.v4.app.ListFragment.ensureList(ListFragment.java:328)
                                    at+android.support.v4.app.ListFragment.getListView(ListFragment.java:222)
                                 */

                            }
                        }
                        if (listAdapter.getCount() != 0) { // could be filtered out!
                            Utils.ServiceGetter<XMPPService> xmppServiceServiceGetter = new Utils.ServiceGetter<XMPPService>(
                                    getActivity(), XMPPService.class);
                            Activity activity = getActivity();
                            if (activity != null) {
                                if (!XMPPIncomingMessagesActivity.editMode) {
                                    xmppServiceServiceGetter
                                            .getService(new Utils.ServiceGetter.Receiver<XMPPService>() {
                                                @Override
                                                public void withService(XMPPService service) {
                                                    service.removeMessages(mid, false);
                                                }
                                            });
                                }
                                Utils.ServiceGetter<DatabaseService> databaseGetter = new Utils.ServiceGetter<DatabaseService>(
                                        activity, DatabaseService.class);
                                if (trackLastRead) {
                                    databaseGetter.getService(new Utils.ServiceGetter.Receiver<DatabaseService>() {
                                        @Override
                                        public void withService(DatabaseService service) {
                                            service.markAsRead(new DatabaseService.ReadMarker(mid,
                                                    messages.size() - 1, messages.get(0).Timestamp.getDate()));
                                            service.saveRecentlyOpenedThread(messages.get(0));
                                        }

                                        @Override
                                        public void withoutService() {
                                        }
                                    });
                                }
                            }
                            if (listAdapter.getCount() > 14) {
                                resetMainMenuButton(true);
                                navMenu.setVisibility(View.VISIBLE);
                            }
                        }
                        if (cached) {
                            large.setVisibility(View.GONE);
                            small.setVisibility(View.VISIBLE);
                            setCachedViewIndicators(true);
                            getView().findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
                        } else {
                            setCachedViewIndicators(false);
                            getView().findViewById(android.R.id.empty).setVisibility(View.GONE);
                        }
                    }
                }
            });
        }
    }

    private void setCachedViewIndicators(boolean cached) {
        cached_copy_label.setVisibility(cached ? View.VISIBLE : View.GONE);
        View container = getView().findViewById(R.id.list_container);
        if (cached) {
            container.setPadding(2, 2, 2, 2);
            container.setBackgroundColor(0xFFA0A000);
        } else {
            container.setPadding(0, 0, 0, 0);
            container.setBackgroundColor(0);
        }
    }

    private void initAdapterStageTwo(boolean cached) {
        if (!isAdded()) {
            return;
        }
        String replies = getResources().getString(R.string.Replies) + " ("
                + Integer.toString(listAdapter.getCount() - 1) + ")";
        listAdapter.addDisabledItem(replies, 1);
        if (!cached) {
            if (listAdapter.getCount() > 0)
                parentActivity.onThreadLoaded(listAdapter.getItem(0));
        }

        getListView().setRecyclerListener(new AbsListView.RecyclerListener() {
            @Override
            public void onMovedToScrapHeap(View view) {
                listAdapter.recycleView(view);
            }
        });

    }

    @Override
    public void onResume() {
        paused = false;
        super.onResume();
        XMPPMessageReceiver.listeners.add(this);
        doneWebSocket();
        initWebSocket();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                doneWebSocket();
                initWebSocket();
                handler.postDelayed(this, 3 * 60000);
            }
        }, 3 * 60000);
    }

    @Override
    public void onPause() {
        paused = true;
        if (ws != null)
            ws.setPaused(true);
        XMPPMessageReceiver.listeners.remove(this);
        handler.removeCallbacksAndMessages(null);
        super.onPause();
    }

    @Override
    public void onDestroy() {
        getActivity().unregisterReceiver(recv);
        handler.removeCallbacksAndMessages(null);
        doneWebSocket();
        super.onDestroy();
    }

    private void doneWebSocket() {
        if (ws != null) {
            ws.terminate();
            ws = null;
        }
    }

    private void onWebSocketMessages(final ArrayList<JuickMessage> messages) {
        getActivity().runOnUiThread(new Runnable() {
            public void run() {
                // don't scroll
                ListView listView = null;
                try {
                    listView = getListView();
                } catch (Exception e) {
                    // view not yet created
                    return;
                }
                listView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
                listView.setStackFromBottom(false);
                //
                if (sp.getBoolean("current_vibration_enabled", true)) {
                    if (!paused) {
                        ((Vibrator) getActivity().getSystemService(Activity.VIBRATOR_SERVICE)).vibrate(250);
                    }
                }
                if (listAdapter.getCount() > 0) {
                    for (JuickMessage message : messages) {
                        if (message.getReplyTo() != 0 && !message.Text.startsWith("@")) {
                            for (int z = 0; z < listAdapter.getCount(); z++) {
                                JuickMessage item = listAdapter.getItem(z);
                                if (item != null && item.getRID() == message.getReplyTo()) {
                                    message.Text = "@" + item.User.UName + " " + message.Text;
                                }
                            }
                        }
                    }
                    listAdapter.addAllMessages(messages);
                    if (getActivity() instanceof ThreadActivity) {
                        ThreadActivity ta = (ThreadActivity) getActivity();
                        Editable text = ta.etMessage.getText();
                        for (JuickMessage message : messages) {
                            if (message.Text.contains(text)) {
                                if (ta.replyInProgress != null) {
                                    ta.replyInProgress.preliminarySuccess();
                                }
                            }
                        }
                    }
                }
                xmppServiceServiceGetter.getService(new Utils.ServiceGetter.Receiver<XMPPService>() {
                    @Override
                    public void withService(XMPPService service) {
                        for (JuickMessage message : messages) {
                            service.removeMessages(message.getMID(), false);
                        }
                    }
                });
                Utils.ServiceGetter<DatabaseService> databaseGetter = new Utils.ServiceGetter<DatabaseService>(
                        getActivity(), DatabaseService.class);
                databaseGetter.getService(new Utils.ServiceGetter.Receiver<DatabaseService>() {
                    @Override
                    public void withService(DatabaseService service) {
                        service.maybeSaveMessages(messages);
                        super.withService(service);
                    }
                });
                try {
                    listAdapter.getItem(1).Text = getResources().getString(R.string.Replies) + " ("
                            + Integer.toString(listAdapter.getCount() - 2) + ")";
                } catch (Throwable ex) {
                    // web socket came earlier than body itself!
                }
            }
        });
    }

    public JuickMessage findReply(AdapterView<?> parent, int replyNo) {
        for (int q = 0; q < parent.getCount(); q++) {
            Object itemAtPosition = parent.getItemAtPosition(q);
            if (itemAtPosition instanceof JuickMessage) {
                JuickMessage maybeReplied = (JuickMessage) itemAtPosition;
                if (maybeReplied.getRID() == replyNo) {
                    return maybeReplied;
                }
            }
        }
        return null;
    }

    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        if (doOnClick != null) {
            if (System.currentTimeMillis() < doOnClickActualTime + 1000) {
                doOnClick.run();
            }
            doOnClick = null;
            return;
        }
        JuickMessage jmsg = (JuickMessage) parent.getItemAtPosition(position);
        parentActivity.onReplySelected(jmsg);
    }

    boolean touchOnNavMenu = false;
    boolean ignoreMove = false;
    float touchOriginX = -1;
    float touchOriginY = -1;
    float initNavMenuTranslationX;

    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    public boolean onTouch(View view, MotionEvent event) {
        if (parent.onListTouchEvent(view, event))
            return true;
        if (parent.isFinishing())
            return true;
        if (mScaleDetector != null) {
            try {
                mScaleDetector.onTouchEvent(event);
            } catch (Exception e) {
                // shit happens there inside
            }
        }
        try {
            MotionEvent.PointerCoords pc = new MotionEvent.PointerCoords();
            event.getPointerCoords(0, pc);
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (navMenu != null && navMenu.getVisibility() == View.VISIBLE) {
                    int[] listViewLocation = new int[2];
                    int[] imageLocation = new int[2];
                    getListView().getLocationOnScreen(listViewLocation);
                    navMenu.getLocationOnScreen(imageLocation);
                    imageLocation[0] += navMenu.initialTranslationX;
                    float touchX = pc.x + listViewLocation[0] - imageLocation[0];
                    float touchY = pc.y + listViewLocation[1] - imageLocation[1];
                    System.out.println("TOUCH: ACTION_DOWN: x=" + pc.x + " y=" + pc.y);
                    if (touchX > -20 && touchX < navMenu.getWidth() && touchY > 0
                            && touchY < navMenu.getHeight() * 1.5) { // extra Y pixels due to picture not balanced
                        touchOriginX = pc.x;
                        touchOriginY = pc.y;
                        System.out.println("TOUCH: OK TOUCH NAVMENU");
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (!ignoreMove && !isNavigationMenuShown())
                    resetMainMenuButton(false);
                if (touchOriginX > 0 || ignoreMove) {
                    touchOriginX = -1;
                    ignoreMove = false;
                    return true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (ignoreMove || navMenu == null)
                    return true;
                event.getPointerCoords(0, pc);
                double travelledDistance = Math
                        .sqrt(Math.pow(touchOriginX - pc.x, 2) + Math.pow(touchOriginY - pc.y, 2));
                boolean inZone = false;
                if (!isNavigationMenuShown()) {
                    if (touchOriginX >= 0) {
                        // detect angle where finger moves
                        if (travelledDistance < 10) { // grace period
                            inZone = true;
                        } else {
                            float dx = Math.abs(touchOriginX - pc.x);
                            float dy = Math.abs(touchOriginY - pc.y);
                            if (dx > dy) {
                                // movement in 45 degree zone
                                if (touchOriginX > pc.x) {
                                    // towards left
                                    inZone = true;
                                    double neededDistance = 1.5 / 2.54 * getResources().getDisplayMetrics().xdpi;
                                    if (travelledDistance > neededDistance) {
                                        // moved 1.5 centimeters
                                        System.out.println("TOUCH: OPEN MENU");
                                        ignoreMove = true;
                                        openNavigationMenu(pc.x - touchOriginX + initNavMenuTranslationX);
                                        touchOriginX = -1;
                                    }
                                }
                            } else {
                                System.out.println("TOUCH: LEAVING ZONE: dx=" + dx + " dy=" + dy);
                            }
                        }
                        if (inZone && !ignoreMove) {
                            TranslateAnimation immediate = new TranslateAnimation(Animation.ABSOLUTE,
                                    pc.x - touchOriginX + initNavMenuTranslationX, Animation.ABSOLUTE,
                                    pc.x - touchOriginX + initNavMenuTranslationX, Animation.ABSOLUTE, 0,
                                    Animation.ABSOLUTE, 0);
                            immediate.setDuration(5);
                            immediate.setFillAfter(true);
                            immediate.setFillBefore(true);
                            immediate.setFillEnabled(true);
                            navMenu.startAnimation(immediate);
                        }
                    }
                    if (!inZone) {
                        resetMainMenuButton(false);
                        if (touchOriginX >= 0) {
                            System.out.println("TOUCH: ACTION_MOVE: x=" + pc.x + " y=" + pc.y);
                            System.out.println("TOUCH: LEFT ZONE");
                            touchOriginX = -1;
                        }
                    }
                    if (inZone) {
                        return true;
                    }
                    if (doOnClick != null || ignoreMove) {
                        return true;
                    }
                }
                break;
            }
        } catch (NoClassDefFoundError err) {
            // so be it.
        }
        return false;
    }

    private void resetMainMenuButton(boolean animate) {
        if (navMenu != null) {
            TranslateAnimation immediate = new TranslateAnimation(Animation.ABSOLUTE,
                    animate ? initNavMenuTranslationX + 100 : initNavMenuTranslationX, Animation.ABSOLUTE,
                    initNavMenuTranslationX, Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 0);
            immediate.setDuration(500);
            immediate.setFillEnabled(true);
            immediate.setFillBefore(true);
            immediate.setFillAfter(true);
            navMenu.startAnimation(immediate);
        }
        //navMenu.startAnimation(immediate);
    }

    class FlyingItem implements View.OnClickListener {
        MyImageView widget;
        float designedX;
        float designedY;
        TranslateAnimation ani;
        final int id;
        public Rect originalHitRect = new Rect();

        FlyingItem(View view, final int id) {
            this.id = id;
            widget = (MyImageView) view.findViewById(id);
            designedX = widget.initialTranslationX;
            designedY = widget.initialTranslationY;
            setVisibility(View.GONE);
            widget.setOnClickListener(this);
        }

        private void setVisibility(int gone) {
            widget.setVisibility(gone);
        }

        public void onClick(View v) {
            onNavigationMenuItemPressed(id);
        }
    }

    private void onNavigationMenuItemPressed(int id) {
        if (id == R.id.navbar_top) {
            getListView().setSelection(0);
        }
        if (id == R.id.navbar_bottom) {
            getListView().setSelection(getListView().getAdapter().getCount() - 1);
        }
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                closeNavigationMenu();
            }
        }, 200);
    }

    private void closeNavigationMenu() {
        navigationMenuShown = false;
        for (int i = 0; i < flyingItems.length; i++) {
            final FlyingItem item = flyingItems[i];
            item.setVisibility(View.VISIBLE);
            item.ani = new TranslateAnimation(Animation.ABSOLUTE, 0, Animation.ABSOLUTE,
                    -item.designedX + item.widget.getWidth() * 1.5f, Animation.ABSOLUTE, 0, Animation.ABSOLUTE,
                    -item.designedY);
            item.ani.setDuration(500);
            item.ani.setInterpolator(new AccelerateInterpolator(1));

            item.ani.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                    //To change body of implemented methods use File | Settings | File Templates.
                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    item.setVisibility(View.GONE);
                    item.widget.clearAnimation();
                    item.widget.disableReposition = false;
                    item.widget.layout(item.originalHitRect.left, item.originalHitRect.top,
                            item.originalHitRect.right, item.originalHitRect.bottom);
                    if (item == flyingItems[0]) {
                        TranslateAnimation aniIn = new TranslateAnimation(Animation.ABSOLUTE, 600,
                                Animation.ABSOLUTE, initNavMenuTranslationX, Animation.ABSOLUTE, 0,
                                Animation.ABSOLUTE, 0);
                        aniIn.setInterpolator(new DecelerateInterpolator(1));
                        item.ani.setDuration(500);
                        aniIn.setFillAfter(true);
                        navMenu.startAnimation(aniIn);
                    }
                }

                @Override
                public void onAnimationRepeat(Animation animation) {
                    //To change body of implemented methods use File | Settings | File Templates.
                }
            });
            item.ani.setFillAfter(true);
            item.widget.startAnimation(item.ani);
        }
    }

    private void openNavigationMenu(float currentTranslation) {
        try {
            navigationMenuShown = true;
            for (final FlyingItem item : flyingItems) {
                item.setVisibility(View.VISIBLE);
                item.ani = new TranslateAnimation(Animation.ABSOLUTE, 300, Animation.ABSOLUTE, item.designedX,
                        Animation.ABSOLUTE, 0, Animation.ABSOLUTE, item.designedY);

                item.ani.setInterpolator(new OvershootInterpolator(2));
                item.ani.setDuration(500);
                item.ani.setFillAfter(true);
                item.ani.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                        //To change body of implemented methods use File | Settings | File Templates.
                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        // this code is very ugly because it's all android 2.3 animations.
                        item.widget.getHitRect(item.originalHitRect);
                        item.widget.clearAnimation();
                        item.widget.layout(item.originalHitRect.left + (int) item.widget.initialTranslationX,
                                item.originalHitRect.top + (int) item.widget.initialTranslationY,
                                item.originalHitRect.right + (int) item.widget.initialTranslationX,
                                item.originalHitRect.bottom + (int) item.widget.initialTranslationY);
                        item.widget.disableReposition = true;
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {
                        //To change body of implemented methods use File | Settings | File Templates.
                    }
                });
                item.widget.startAnimation(item.ani);
            }
            TranslateAnimation aniOut = new TranslateAnimation(Animation.ABSOLUTE, currentTranslation,
                    Animation.ABSOLUTE, -1.2f * getActivity().getWindowManager().getDefaultDisplay().getWidth(),
                    Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 0);
            aniOut.setInterpolator(new DecelerateInterpolator(1));
            aniOut.setDuration(700);
            aniOut.setFillAfter(true);
            navMenu.startAnimation(aniOut);

            getListView().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        } catch (Throwable e) {
            e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
        }
    }

    public interface ThreadFragmentListener {

        public void onThreadLoaded(JuickMessage message);

        public void onReplySelected(JuickMessage reply);
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            listAdapter.setScale(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(),
                    getListView());
            listAdapter.notifyDataSetChanged();
            return true;
        }
    }

    @Override
    public boolean onMessageReceived(XMPPService.IncomingMessage message) {
        if (message instanceof XMPPService.JuickThreadIncomingMessage) {
            XMPPService.JuickThreadIncomingMessage jtim = (XMPPService.JuickThreadIncomingMessage) message;
            if (jtim.getMID() == mid) {
                xmppServiceServiceGetter.getService(new Utils.ServiceGetter.Receiver<XMPPService>() {
                    @Override
                    public void withService(XMPPService service) {
                        service.removeMessages(mid, false);
                    }
                });
                //
                return true;
            }
        }
        return false; //To change body of implemented methods use File | Settings | File Templates.
    }

    public void hideThread() {
        if (shownThreadToast != null) {
            shownThreadToast.cancel();
            shownThreadToast = null;
        }

    }

    public void showThread(JuickMessage jmsg, boolean keepShow) {
        if (jmsg.getReplyTo() != 0) {
            JuickMessage reply = jmsg;
            LinearLayout ll = new LinearLayout(getActivity());
            ll.setOrientation(LinearLayout.VERTICAL);
            ll.setBackgroundDrawable(new ColorDrawable(Color.WHITE));
            int totalCount = 0;
            while (reply != null) {
                totalCount += reply.Text.length();
                if (totalCount > 500 || ll.getChildCount() > 10)
                    break;
                JuickMessagesAdapter.ParsedMessage parsedMessage = JuickMessagesAdapter
                        .formatMessageText(getActivity(), reply, true);
                TextView child = new TextView(getActivity());
                ll.addView(child, 0);
                child.setText(parsedMessage.textContent);
                if (reply.getReplyTo() < 1)
                    break;
                reply = findReply(getListView(), reply.getReplyTo());
            }
            if (ll.getChildCount() != 0) {
                int xy[] = new int[2];
                getListView().getLocationOnScreen(xy);
                int windowHeight = getActivity().getWindow().getWindowManager().getDefaultDisplay().getHeight();
                int listBottom = getListView().getHeight() + xy[1];
                int bottomSize = windowHeight - listBottom;
                ll.setPressed(true);
                MainActivity.restyleChildrenOrWidget(ll);
                if (!keepShow || shownThreadToast.getView().getParent() == null) { // new or already hidden
                    shownThreadToast = new Toast(getActivity());
                    shownThreadToast.setView(ll);
                    shownThreadToast.setGravity(Gravity.BOTTOM | Gravity.LEFT, 0, bottomSize);
                    shownThreadToast.setDuration(Toast.LENGTH_LONG);
                }
                shownThreadToast.show();
            }
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        instanceCount--;
    }

    public boolean onBackPressed() {
        if (isNavigationMenuShown()) {
            closeNavigationMenu();
            return true;
        }
        return false; //To change body of created methods use File | Settings | File Templates.
    }

    private boolean isNavigationMenuShown() {
        return navigationMenuShown;
    }

    public void scrollMessages(int delta) {
        String scollMode = sp.getString("keyScrollMode", "page");
        ListView lv = getListView();
        if (lv.getChildCount() == 1 && scollMode.equals("message"))
            scollMode = "page";
        if (scollMode.equals("message")) {
            int firstVisiblePosition = lv.getFirstVisiblePosition();
            if (delta == +1) {
                if (firstVisiblePosition == 0) {
                    firstVisiblePosition++; // list separator
                }
                lv.setSelection(firstVisiblePosition + 1);
            } else {
                if (firstVisiblePosition != 0) {
                    lv.setSelection(firstVisiblePosition - 1);
                }
            }
        }
        if (scollMode.equals("page")) {
            if (delta == +1) {
                lv.smoothScrollBy((int) (lv.getHeight() * 0.93), 200);
            } else {
                lv.smoothScrollBy(-(int) (lv.getHeight() * 0.93), 200);
            }
        }
    }

    public void sortMessages(MessagesSource.Sorter sorter) {
        ArrayList<JuickMessage> stuffToSort = new ArrayList<JuickMessage>();
        ArrayList<JuickMessage> header = new ArrayList<JuickMessage>();
        int count = listAdapter.getCount();
        for (int i = 2; i < count; i++) {
            JuickMessage item = listAdapter.getItem(i);
            stuffToSort.add(item);
        }
        header.add(listAdapter.getItem(0));
        header.add(listAdapter.getItem(1));
        listAdapter.clear();
        listAdapter.addAllMessages(header);
        Collections.sort(stuffToSort, sorter);
        listAdapter.addAllMessages(stuffToSort);
        listAdapter.notifyDataSetChanged();
    }

}