aboutsummaryrefslogtreecommitdiffstats
path: root/libs/emojicon/src/main/java/github/ankushsachdeva/emojicon/EmojiconsPopup.java
blob: 5bcafccfd6cb383cc4d3a3f0851d8367dad838b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
/*
 * Copyright 2014 Ankush Sachdeva
 *
 * 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 github.ankushsachdeva.emojicon;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager.LayoutParams;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.PopupWindow;


/**
 * @author Ankush Sachdeva (sankush@yahoo.co.in).
 */

public class EmojiconsPopup extends PopupWindow implements ViewPager.OnPageChangeListener, EmojiAdapter.OnEmojiClickedListener {
    private View[] mEmojiTabs;
    private EmojiconRecentsManager mRecentsManager;
    private int keyBoardHeight = 0;
    private boolean mWaitingForKbOpen = false;
    private boolean mIsOpened = false;
    private View mRootView;
    private Context mContext;
    private ViewPager mEmojisPager;

    @Nullable
    private OnSoftKeyboardOpenCloseListener mSoftKeyboardOpenCloseListener;

    @Nullable
    private OnEmojiconClickedListener mEmojiconClickedListener;

    @Nullable
    private OnEmojiconBackspaceClickedListener mEmojiconBackspaceClickedListener;

    private GlobalLayoutListener mGlobalLayoutListener = new GlobalLayoutListener();

    /**
     * Constructor
     *
     * @param rootView The top most layout in your view hierarchy. The difference of this view and the screen height will be used to calculate the keyboard height.
     * @param context  The context of current activity.
     */
    public EmojiconsPopup(View rootView, Context context) {
        super(context, null, R.attr.emojicon_dialog_style);
        mContext = context;
        mRootView = rootView;
        mRecentsManager = new EmojiconRecentsManager(context);
        setContentView(createCustomView());
        setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    }

    /**
     * Set the listener for the event of keyboard opening or closing.
     */
    public void setOnSoftKeyboardOpenCloseListener(OnSoftKeyboardOpenCloseListener listener) {
        this.mSoftKeyboardOpenCloseListener = listener;
    }

    /**
     * Set the listener for the event when any of the emojicon is clicked
     */
    public void setOnEmojiconClickedListener(OnEmojiconClickedListener listener) {
        this.mEmojiconClickedListener = listener;
    }

    /**
     * Set the listener for the event when backspace on emojicon popup is clicked
     */
    public void setOnEmojiconBackspaceClickedListener(OnEmojiconBackspaceClickedListener listener) {
        this.mEmojiconBackspaceClickedListener = listener;
    }

    /**
     * Use this function to show the emoji popup.
     * NOTE: Since, the soft keyboard sizes are variable on different android devices, the
     * library needs you to open the soft keyboard atleast once before calling this function.
     * If that is not possible see showAtBottomPending() function.
     */
    public void showAtBottom() {
        showAtLocation(mRootView, Gravity.BOTTOM, 0, 0);
    }

    /**
     * Use this function when the soft keyboard has not been opened yet. This
     * will show the emoji popup after the keyboard is up next time.
     * Generally, you will be calling InputMethodManager.showSoftInput function after
     * calling this function.
     */
    public void showAtBottomPending() {
        if (isKeyBoardOpen()) {
            showAtBottom();
        } else {
            mWaitingForKbOpen = true;
        }
    }

    /**
     * @return Returns true if the soft keyboard is open, false otherwise.
     */
    public Boolean isKeyBoardOpen() {
        return mIsOpened;
    }

    /**
     * Dismiss the popup
     */
    @Override
    public void dismiss() {
        super.dismiss();
        mRecentsManager.save();
    }

    /**
     * Call this function to resize the emoji popup according to your soft keyboard size
     */
    public void attachGlobalLayoutListener() {
        mRootView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
    }

    public void detachGlobalLayoutListener() {
        mRootView.getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
    }

    /**
     * Manually set the popup window size
     *
     * @param width  Width of the popup
     * @param height Height of the popup
     */
    public void setSize(int width, int height) {
        setWidth(width);
        setHeight(height);
    }

    private View createCustomView() {
        View view = LayoutInflater.from(mContext).inflate(R.layout.emojicons, null);
        List<EmojiconGroup> displayedGroups = collectDisplayedGroups();
        initViewPager(view, displayedGroups);
        initTabs(view, displayedGroups);
        initCurrentPage();
        return view;
    }

    private void initCurrentPage() {
        // get last selected page
        int page = mRecentsManager.getRecentPage();
        // last page was recents, check if there are recents to use
        // if none was found, go to page 1
        if (page == 0 && mRecentsManager.getEmojiList().isEmpty()) {
            page = 1;
        }
        if (page == 0) {
            onPageSelected(page);
        } else {
            mEmojisPager.setCurrentItem(page, false);
        }
    }

    private void initTabs(View view, List<EmojiconGroup> displayedGroups) {
        LinearLayout tabHostLayout = (LinearLayout) view.findViewById(R.id.emojis_tab);
        mEmojiTabs = new View[displayedGroups.size()];
        for (int i = 0; i < displayedGroups.size(); i++) {
            final int position = i;
            mEmojiTabs[i] = inflateTab(tabHostLayout, displayedGroups.get(i).getIconResId());
            mEmojiTabs[i].setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mEmojisPager.setCurrentItem(position);
                }
            });
            inflateDivider(tabHostLayout);
        }
        View backSpace = inflateTab(tabHostLayout, R.drawable.ic_keyboard_delete);
        backSpace.setOnTouchListener(new RepeatTouchListener(1000, 50, new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mEmojiconBackspaceClickedListener != null) {
                    mEmojiconBackspaceClickedListener.onEmojiconBackspaceClicked(v);
                }
            }
        }));
    }

    private View inflateTab(LinearLayout tabHostLayout, int iconResId) {
        ImageButton tabView = (ImageButton) LayoutInflater.from(mContext)
                .inflate(R.layout.emojicon_tab, tabHostLayout, false);

        tabHostLayout.addView(tabView);
        tabView.setImageDrawable(mContext.getResources().getDrawable(iconResId));
        return tabView;
    }

    private View inflateDivider(LinearLayout tabHostLayout) {
        return LayoutInflater.from(mContext).inflate(R.layout.emojicon_tab_divider, tabHostLayout);
    }

    private void initViewPager(View view, List<EmojiconGroup> displayedGroups) {
        mEmojisPager = (ViewPager) view.findViewById(R.id.emojis_pager);
        mEmojisPager.setOnPageChangeListener(this);
        mEmojisPager.setAdapter(new EmojisPagerAdapter(displayedGroups));
    }

    private List<EmojiconGroup> collectDisplayedGroups() {
        List<EmojiconGroup> groups = new ArrayList<>();
        groups.add(new RecentsEmojiconGroup(mRecentsManager, EmojiconGroupsLoader.KnownGroupNames.RECENT.getIconResId()));
        groups.addAll(EmojiconGroupsLoader.getInstance(mContext).getGroups());
        return groups;
    }

    @Override
    public void onPageScrolled(int i, float v, int i2) {
        // ignore
    }

    @Override
    public void onPageSelected(int position) {
        for (int i = 0; i < mEmojiTabs.length; i++) {
            mEmojiTabs[i].setSelected(i == position);
        }
        mRecentsManager.setRecentPage(position);
        EmojiAdapter adapter = ((EmojisPagerAdapter) mEmojisPager.getAdapter()).getPageAdapter(position);
        if (adapter instanceof Updatable) {
            ((Updatable) adapter).update();
        }
    }

    @Override
    public void onPageScrollStateChanged(int i) {
        // ignore
    }

    @Override
    public void onEmojiClicked(Emojicon emojicon) {
        mRecentsManager.push(emojicon);
        if (mEmojiconClickedListener != null) {
            mEmojiconClickedListener.onEmojiconClicked(emojicon);
        }
    }

    private class EmojisPagerAdapter extends PagerAdapter {
        private final List<EmojiconGroup> mGroups;
        private final SparseArray<EmojiAdapter> mAdapters = new SparseArray<>();

        public EmojisPagerAdapter(List<EmojiconGroup> groups) {
            mGroups = groups;
        }

        public EmojiAdapter getPageAdapter(int pageIndex) {
            return mAdapters.get(pageIndex);
        }

        @Override
        public int getCount() {
            return mGroups.size();
        }

        @Override
        public GridView instantiateItem(ViewGroup container, final int viewPosition) {
            GridView gridView = (GridView) LayoutInflater.from(mContext).inflate(R.layout.emojicon_grid, container, false);
            container.addView(gridView);
            final EmojiAdapter adapter = mGroups.get(viewPosition).createAdapter(mContext);
            gridView.setAdapter(adapter);
            adapter.setClickListener(EmojiconsPopup.this);
            mAdapters.put(viewPosition, adapter);
            return gridView;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object view) {
            container.removeView((View) view);
            mAdapters.remove(position);
        }

        @Override
        public boolean isViewFromObject(View view, Object key) {
            return key == view;
        }
    }

    public interface OnEmojiconClickedListener {
        void onEmojiconClicked(Emojicon emojicon);
    }

    public interface OnEmojiconBackspaceClickedListener {
        void onEmojiconBackspaceClicked(View v);
    }

    public interface OnSoftKeyboardOpenCloseListener {
        void onKeyboardOpen(int keyBoardHeight);

        void onKeyboardClose();
    }

    private class GlobalLayoutListener implements OnGlobalLayoutListener {
        @Override
        public void onGlobalLayout() {
            Rect r = new Rect();
            mRootView.getWindowVisibleDisplayFrame(r);

            int screenHeight = mRootView.getRootView().getHeight();
            int heightDifference = screenHeight - (r.bottom - r.top);
            int resourceId = mContext.getResources()
                    .getIdentifier("status_bar_height", "dimen", "android");
            if (resourceId > 0) {
                heightDifference -= mContext.getResources().getDimensionPixelSize(resourceId);
            }
            if (heightDifference > 100) {
                int oldHeight = getHeight();
                keyBoardHeight = heightDifference;
                setSize(LayoutParams.MATCH_PARENT, keyBoardHeight);
                if (!mIsOpened) {
                    if (mSoftKeyboardOpenCloseListener != null) {
                        mSoftKeyboardOpenCloseListener.onKeyboardOpen(keyBoardHeight);
                    }
                }
                mIsOpened = true;
                if (mWaitingForKbOpen) {
                    showAtBottom();
                    mWaitingForKbOpen = false;
                } else if(isShowing() && oldHeight != keyBoardHeight) {
                    dismiss();
                    showAtBottom();
                }
            } else {
                mIsOpened = false;
                if (mSoftKeyboardOpenCloseListener != null) {
                    mSoftKeyboardOpenCloseListener.onKeyboardClose();
                }
            }
        }
    }
}