[
  {
    "path": ".gitignore",
    "content": "# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\nout/\n\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n\n# Android Studio Navigation editor temp files\n.navigation/\n\n# Android Studio captures folder\ncaptures/\n\n# Intellij\n*.iml\n.idea/workspace.xml\n.idea/tasks.xml\n.idea/gradle.xml\n.idea/dictionaries\n.idea/libraries\n\n# Keystore files\n*.jks\n\n# External native build folder generated in Android Studio 2.2 and later\n.externalNativeBuild\n\n# Google Services (e.g. APIs or Firebase)\ngoogle-services.json\n\n# Freeline\nfreeline.py\nfreeline/\nfreeline_project_description.json\n"
  },
  {
    "path": ".idea/.name",
    "content": "ChatUI"
  },
  {
    "path": ".idea/compiler.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <resourceExtensions />\n    <wildcardResourcePatterns>\n      <entry name=\"!?*.java\" />\n      <entry name=\"!?*.form\" />\n      <entry name=\"!?*.class\" />\n      <entry name=\"!?*.groovy\" />\n      <entry name=\"!?*.scala\" />\n      <entry name=\"!?*.flex\" />\n      <entry name=\"!?*.kt\" />\n      <entry name=\"!?*.clj\" />\n      <entry name=\"!?*.aj\" />\n    </wildcardResourcePatterns>\n    <annotationProcessing>\n      <profile default=\"true\" name=\"Default\" enabled=\"false\">\n        <processorPath useClasspath=\"true\" />\n      </profile>\n    </annotationProcessing>\n  </component>\n</project>"
  },
  {
    "path": ".idea/copyright/profiles_settings.xml",
    "content": "<component name=\"CopyrightManager\">\n  <settings default=\"\" />\n</component>"
  },
  {
    "path": ".idea/encodings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\">\n    <file url=\"PROJECT\" charset=\"UTF-8\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"EntryPointsManager\">\n    <entry_points version=\"2.0\" />\n  </component>\n  <component name=\"NullableNotNullManager\">\n    <option name=\"myDefaultNullable\" value=\"android.support.annotation.Nullable\" />\n    <option name=\"myDefaultNotNull\" value=\"android.support.annotation.NonNull\" />\n    <option name=\"myNullables\">\n      <value>\n        <list size=\"4\">\n          <item index=\"0\" class=\"java.lang.String\" itemvalue=\"org.jetbrains.annotations.Nullable\" />\n          <item index=\"1\" class=\"java.lang.String\" itemvalue=\"javax.annotation.Nullable\" />\n          <item index=\"2\" class=\"java.lang.String\" itemvalue=\"edu.umd.cs.findbugs.annotations.Nullable\" />\n          <item index=\"3\" class=\"java.lang.String\" itemvalue=\"android.support.annotation.Nullable\" />\n        </list>\n      </value>\n    </option>\n    <option name=\"myNotNulls\">\n      <value>\n        <list size=\"4\">\n          <item index=\"0\" class=\"java.lang.String\" itemvalue=\"org.jetbrains.annotations.NotNull\" />\n          <item index=\"1\" class=\"java.lang.String\" itemvalue=\"javax.annotation.Nonnull\" />\n          <item index=\"2\" class=\"java.lang.String\" itemvalue=\"edu.umd.cs.findbugs.annotations.NonNull\" />\n          <item index=\"3\" class=\"java.lang.String\" itemvalue=\"android.support.annotation.NonNull\" />\n        </list>\n      </value>\n    </option>\n  </component>\n  <component name=\"ProjectLevelVcsManager\" settingsEditedManually=\"false\">\n    <OptionsSetting value=\"true\" id=\"Add\" />\n    <OptionsSetting value=\"true\" id=\"Remove\" />\n    <OptionsSetting value=\"true\" id=\"Checkout\" />\n    <OptionsSetting value=\"true\" id=\"Update\" />\n    <OptionsSetting value=\"true\" id=\"Status\" />\n    <OptionsSetting value=\"true\" id=\"Edit\" />\n    <ConfirmationsSetting value=\"0\" id=\"Add\" />\n    <ConfirmationsSetting value=\"0\" id=\"Remove\" />\n  </component>\n  <component name=\"ProjectRootManager\" version=\"2\" languageLevel=\"JDK_1_7\" default=\"true\" assert-keyword=\"true\" jdk-15=\"true\" project-jdk-name=\"1.8\" project-jdk-type=\"JavaSDK\">\n    <output url=\"file://$PROJECT_DIR$/build/classes\" />\n  </component>\n  <component name=\"ProjectType\">\n    <option name=\"id\" value=\"Android\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/ChatUI.iml\" filepath=\"$PROJECT_DIR$/ChatUI.iml\" />\n      <module fileurl=\"file://$PROJECT_DIR$/app/app.iml\" filepath=\"$PROJECT_DIR$/app/app.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <option name=\"ignoredProducers\">\n      <set>\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer\" />\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer\" />\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "修改自开源项目：https://github.com/Rance935/ChatUI \n\n## 修改内容\n+ 替换部分图片资源\n+ 用源生RecyclerView替换了EasyRecyclerView,比较相信官方。\n+ 修复拍照在7.0系统中无法获取权限问题。\n+ 增加文件分享，点击文件打开。\n+ 增加联系人信息分享。\n+ 长按消息弹出上下文菜单\n+ 注册app到系统分享面板，能够处理分享自外部的URL、图片、文档。\n\n## 截图\n![image](https://github.com/stridercheng/chatui/raw/master/images/preview.png)\n\n## 问题\n代码结构待优化\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 24\n    buildToolsVersion \"24.0.2\"\n\n    aaptOptions {\n        cruncherEnabled = false\n    }\n\n    defaultConfig {\n        applicationId \"com.rance.chatui\"\n        minSdkVersion 15\n        targetSdkVersion 24\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    //noinspection GradleCompatible\n    compile 'com.android.support:appcompat-v7:24.2.1'\n    compile 'com.android.support:design:24.2.1'\n    compile 'com.android.support:support-v4:24.2.1'\n    compile 'org.greenrobot:eventbus:3.0.0'\n    compile 'com.github.bumptech.glide:glide:3.7.0'\n    compile 'com.labo.kaji:relativepopupwindow:0.3.1'\n    compile 'com.android.support.constraint:constraint-layout:1.0.2'\n    testCompile 'junit:junit:4.12'\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in C:\\Users\\Administrator\\AppData\\Local\\Android\\Sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "app/src/androidTest/java/com/rance/chatui/ApplicationTest.java",
    "content": "package com.rance.chatui;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.rance.chatui\">\n\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n\n    <uses-feature android:name=\"android.hardware.camera\" />\n    <uses-feature android:name=\"android.hardware.camera.autofocus\" />\n\n    <uses-permission android:name=\"android.permission.READ_CONTACTS\" />\n    <uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n\n    <application\n        android:name=\".base.MyApplication\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".ui.activity.IMActivity\"\n            android:configChanges=\"orientation|keyboardHidden|navigation|screenSize\"\n            android:screenOrientation=\"portrait\"\n            android:windowSoftInputMode=\"stateHidden|adjustResize\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n            <intent-filter>\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <action android:name=\"android.intent.action.SEND\" />\n                <data android:mimeType=\"*/*\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".ui.activity.FullImageActivity\"\n            android:configChanges=\"orientation|keyboardHidden|navigation|screenSize\"\n            android:screenOrientation=\"portrait\"\n            android:theme=\"@android:style/Theme.Translucent\"\n            android:windowSoftInputMode=\"stateHidden|adjustPan\" />\n\n        <provider\n            android:name=\"android.support.v4.content.FileProvider\"\n            android:authorities=\"com.chatui.fileprovider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/file_paths\" />\n        </provider>\n\n        <activity android:name=\".ui.activity.ContactActivity\"\n            android:screenOrientation=\"portrait\"/>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/ChatAdapter.java",
    "content": "package com.rance.chatui.adapter;\n\nimport android.os.Handler;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\n\nimport com.rance.chatui.adapter.holder.BaseViewHolder;\nimport com.rance.chatui.adapter.holder.ChatAcceptViewHolder;\nimport com.rance.chatui.adapter.holder.ChatSendViewHolder;\nimport com.rance.chatui.enity.MessageInfo;\nimport com.rance.chatui.util.Constants;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 作者：Rance on 2016/11/29 10:46\n * 邮箱：rance935@163.com\n */\npublic class ChatAdapter extends RecyclerView.Adapter<BaseViewHolder> {\n\n    private onItemClickListener onItemClickListener;\n    public Handler handler;\n    private List<MessageInfo> messageInfoList;\n\n    public ChatAdapter(List<MessageInfo> messageInfoList) {\n        handler = new Handler();\n        this.messageInfoList = messageInfoList;\n    }\n\n//    @Override\n//    public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {\n//        BaseViewHolder viewHolder = null;\n//        switch (viewType) {\n//            case Constants.CHAT_ITEM_TYPE_LEFT:\n//                viewHolder = new ChatAcceptViewHolder(parent, onItemClickListener, handler);\n//                break;\n//            case Constants.CHAT_ITEM_TYPE_RIGHT:\n//                viewHolder = new ChatSendViewHolder(parent, onItemClickListener, handler);\n//                break;\n//        }\n//        return viewHolder;\n//    }\n//\n//    @Override\n//    public int getViewType(int position) {\n//        return getAllData().get(position).getType();\n//    }\n\n    public void addItemClickListener(onItemClickListener onItemClickListener) {\n        this.onItemClickListener = onItemClickListener;\n    }\n\n    @Override\n    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        BaseViewHolder viewHolder = null;\n        switch (viewType) {\n            case Constants.CHAT_ITEM_TYPE_LEFT:\n                viewHolder = new ChatAcceptViewHolder(parent, onItemClickListener, handler);\n                break;\n            case Constants.CHAT_ITEM_TYPE_RIGHT:\n                viewHolder = new ChatSendViewHolder(parent, onItemClickListener, handler);\n                break;\n        }\n        return viewHolder;\n    }\n\n    @Override\n    public void onBindViewHolder(BaseViewHolder holder, int position) {\n        holder.itemView.setTag(position);\n        holder.setData(messageInfoList.get(position));\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        return messageInfoList.get(position).getType();\n    }\n\n    @Override\n    public int getItemCount() {\n        if (messageInfoList == null) {\n            return 0;\n        } else {\n            return messageInfoList.size();\n        }\n    }\n\n    public void addAll(List<MessageInfo> messageInfos) {\n        if (messageInfoList == null) {\n            messageInfoList = messageInfos;\n        } else {\n            messageInfoList.addAll(messageInfos);\n        }\n\n        notifyDataSetChanged();\n    }\n\n    public void add(MessageInfo messageInfo) {\n        if (messageInfoList == null) {\n            messageInfoList = new ArrayList<>();\n        }\n\n        messageInfoList.add(messageInfo);\n\n        notifyDataSetChanged();\n    }\n\n    public interface onItemClickListener {\n        void onHeaderClick(int position);\n\n        void onImageClick(View view, int position);\n\n        void onVoiceClick(ImageView imageView, int position);\n\n        void onFileClick(View view, int position);\n\n        void onLinkClick(View view, int position);\n\n        void onLongClickImage(View view, int position);\n\n        void onLongClickText(View view, int position);\n\n        void onLongClickItem(View view, int position);\n\n        void onLongClickFile(View view, int position);\n\n        void onLongClickLink(View view, int position);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/CommonFragmentPagerAdapter.java",
    "content": "package com.rance.chatui.adapter;\n\nimport android.support.v4.app.Fragment;\nimport android.support.v4.app.FragmentManager;\nimport android.support.v4.app.FragmentPagerAdapter;\n\nimport java.util.ArrayList;\n\n/**\n * 作者：Rance on 2016/11/25 16:36\n * 邮箱：rance935@163.com\n */\npublic class CommonFragmentPagerAdapter extends FragmentPagerAdapter {\n    ArrayList<Fragment> list;\n\n    public CommonFragmentPagerAdapter(FragmentManager fm, ArrayList<Fragment> list) {\n        super(fm);\n        this.list = list;\n    }\n\n    @Override\n    public Fragment getItem(int position) {\n        return list.get(position);\n    }\n\n    @Override\n    public int getCount() {\n        return list != null ? list.size() : 0;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/ContactAdapter.java",
    "content": "package com.rance.chatui.adapter;\n\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.rance.chatui.R;\nimport com.rance.chatui.enity.IMContact;\n\nimport java.util.List;\n\n\n/**\n * Created by chengz\n *\n * @date 2017/8/3.\n */\n\npublic class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ViewHolder>\n        implements View.OnClickListener {\n    private List<IMContact> imContactList;\n    private OnContactClickListener mOnContactClickListener;\n\n    public ContactAdapter(List<IMContact> imContactList) {\n        this.imContactList = imContactList;\n    }\n    @Override\n    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, false);\n        ViewHolder viewHolder = new ViewHolder(view);\n        viewHolder.itemView.setOnClickListener(this);\n        return viewHolder;\n    }\n\n    @Override\n    public void onBindViewHolder(ViewHolder holder, int position) {\n        IMContact imContact = imContactList.get(position);\n        holder.tvName.setText(imContact.getName());\n        holder.tvPhone.setText(imContact.getPhonenumber());\n\n        holder.itemView.setTag(imContact);\n    }\n\n    @Override\n    public int getItemCount() {\n        return imContactList.size();\n    }\n\n    @Override\n    public void onClick(View v) {\n        if (mOnContactClickListener == null) {\n            return;\n        }\n\n        mOnContactClickListener.onContactClick(v, (IMContact) v.getTag());\n    }\n\n    public class ViewHolder extends RecyclerView.ViewHolder {\n        TextView tvName;\n        TextView tvPhone;\n        public ViewHolder(View itemView) {\n            super(itemView);\n            tvName = (TextView) itemView.findViewById(R.id.tv_name);\n            tvPhone = (TextView) itemView.findViewById(R.id.tv_phone);\n        }\n    }\n\n    public interface OnContactClickListener {\n        void onContactClick(View view, IMContact imContact);\n    }\n\n    public void setOnContactClickListener(OnContactClickListener onContactClickListener) {\n        this.mOnContactClickListener = onContactClickListener;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/EmotionGridViewAdapter.java",
    "content": "package com.rance.chatui.adapter;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AbsListView.LayoutParams;\nimport android.widget.BaseAdapter;\nimport android.widget.ImageView;\n\nimport com.rance.chatui.R;\nimport com.rance.chatui.util.EmotionUtils;\n\nimport java.util.List;\n/**\n * 作者：Rance on 2016/11/29 10:47\n * 邮箱：rance935@163.com\n */\npublic class EmotionGridViewAdapter extends BaseAdapter {\n\n    private Context context;\n    private List<String> emotionNames;\n    private int itemWidth;\n\n    public EmotionGridViewAdapter(Context context, List<String> emotionNames, int itemWidth) {\n        this.context = context;\n        this.emotionNames = emotionNames;\n        this.itemWidth = itemWidth;\n    }\n\n    @Override\n    public int getCount() {\n        // +1 最后一个为删除按钮\n        return emotionNames.size() + 1;\n    }\n\n    @Override\n    public String getItem(int position) {\n        return emotionNames.get(position);\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return position;\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        ImageView iv_emotion = new ImageView(context);\n        // 设置内边距\n        iv_emotion.setPadding(itemWidth / 8, itemWidth / 8, itemWidth / 8, itemWidth / 8);\n        LayoutParams params = new LayoutParams(itemWidth, itemWidth);\n        iv_emotion.setLayoutParams(params);\n\n        //判断是否为最后一个item\n        if (position == getCount() - 1) {\n            iv_emotion.setImageResource(R.drawable.compose_emotion_delete);\n        } else {\n            String emotionName = emotionNames.get(position);\n            iv_emotion.setImageResource(EmotionUtils.EMOTION_STATIC_MAP.get(emotionName));\n        }\n\n        return iv_emotion;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/EmotionPagerAdapter.java",
    "content": "package com.rance.chatui.adapter;\n\nimport android.support.v4.view.PagerAdapter;\nimport android.support.v4.view.ViewPager;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.GridView;\n\nimport java.util.List;\n\n/**\n * 作者：Rance on 2016/11/29 10:47\n * 邮箱：rance935@163.com\n */\npublic class EmotionPagerAdapter extends PagerAdapter {\n\n\tprivate List<GridView> gvs;\n\n\tpublic EmotionPagerAdapter(List<GridView> gvs) {\n\t\tthis.gvs = gvs;\n\t}\n\n\t@Override\n\tpublic int getCount() {\n\t\treturn gvs.size();\n\t}\n\n\t@Override\n\tpublic boolean isViewFromObject(View arg0, Object arg1) {\n\t\treturn arg0 == arg1;\n\t}\n\n\t@Override\n\tpublic void destroyItem(ViewGroup container, int position, Object object) {\n\t\t((ViewPager) container).removeView(gvs.get(position));\n\t}\n\n\t@Override\n\tpublic Object instantiateItem(ViewGroup container, int position) {\n\t\t((ViewPager) container).addView(gvs.get(position));\n\t\treturn gvs.get(position);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/holder/BaseViewHolder.java",
    "content": "package com.rance.chatui.adapter.holder;\n\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\n\n/**\n * Created by chengz\n *\n * @date 2017/8/3.\n */\n\npublic class BaseViewHolder<M> extends RecyclerView.ViewHolder {\n\n    public BaseViewHolder(View itemView) {\n        super(itemView);\n    }\n\n    public void setData(M data) {\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/holder/ChatAcceptViewHolder.java",
    "content": "package com.rance.chatui.adapter.holder;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.text.TextPaint;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\n\nimport com.bumptech.glide.Glide;\nimport com.rance.chatui.R;\nimport com.rance.chatui.adapter.ChatAdapter;\nimport com.rance.chatui.enity.IMContact;\nimport com.rance.chatui.enity.Link;\nimport com.rance.chatui.enity.MessageInfo;\nimport com.rance.chatui.util.Constants;\nimport com.rance.chatui.util.FileUtils;\nimport com.rance.chatui.util.Utils;\nimport com.rance.chatui.widget.BubbleImageView;\nimport com.rance.chatui.widget.BubbleLinearLayout;\nimport com.rance.chatui.widget.GifTextView;\n\n/**\n * 作者：Rance on 2016/11/29 10:47\n * 邮箱：rance935@163.com\n */\npublic class ChatAcceptViewHolder extends BaseViewHolder<MessageInfo> {\n    private static final String TAG = \"ChatAcceptViewHolder\";\n    TextView chatItemDate;\n    ImageView chatItemHeader;\n    GifTextView chatItemContentText;\n    BubbleImageView chatItemContentImage;\n    ImageView chatItemVoice;\n    BubbleLinearLayout chatItemLayoutContent;\n    TextView chatItemVoiceTime;\n    BubbleLinearLayout chatItemLayoutFile;\n    ImageView ivFileType;\n    TextView tvFileName;\n    TextView tvFileSize;\n\n    BubbleLinearLayout chatItemLayoutContact;\n    TextView tvContactSurname;\n    TextView tvContactName;\n    TextView tvContactPhone;\n\n    BubbleLinearLayout chatItemLayoutLink;\n    TextView tvLinkSubject;\n    TextView tvLinkText;\n    ImageView ivLinkPicture;\n    private ChatAdapter.onItemClickListener onItemClickListener;\n    private Handler handler;\n    private RelativeLayout.LayoutParams layoutParams;\n    private Context mContext;\n\n    public ChatAcceptViewHolder(ViewGroup parent, ChatAdapter.onItemClickListener onItemClickListener, Handler handler) {\n        super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_accept, parent, false));\n        findViewByIds(itemView);\n        setItemLongClick();\n        setItemClick();\n        this.mContext = parent.getContext();\n        this.onItemClickListener = onItemClickListener;\n        this.handler = handler;\n        layoutParams = (RelativeLayout.LayoutParams) chatItemLayoutContent.getLayoutParams();\n    }\n\n    private void findViewByIds(View itemView) {\n        chatItemDate = (TextView) itemView.findViewById(R.id.chat_item_date);\n        chatItemHeader = (ImageView) itemView.findViewById(R.id.chat_item_header);\n        chatItemContentText = (GifTextView) itemView.findViewById(R.id.chat_item_content_text);\n        chatItemContentImage = (BubbleImageView) itemView.findViewById(R.id.chat_item_content_image);\n        chatItemVoice = (ImageView) itemView.findViewById(R.id.chat_item_voice);\n        chatItemLayoutContent = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_content);\n        chatItemVoiceTime = (TextView) itemView.findViewById(R.id.chat_item_voice_time);\n        chatItemLayoutFile = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_file);\n        ivFileType = (ImageView) itemView.findViewById(R.id.iv_file_type);\n        tvFileName = (TextView) itemView.findViewById(R.id.tv_file_name);\n        tvFileSize = (TextView) itemView.findViewById(R.id.tv_file_size);\n        chatItemLayoutContact = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_contact);\n        tvContactSurname = (TextView) itemView.findViewById(R.id.tv_contact_surname);\n        tvContactPhone = (TextView) itemView.findViewById(R.id.tv_contact_phone);\n        chatItemLayoutLink = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_link);\n        tvLinkSubject = (TextView) itemView.findViewById(R.id.tv_link_subject);\n        tvLinkText = (TextView) itemView.findViewById(R.id.tv_link_text);\n        ivLinkPicture = (ImageView) itemView.findViewById(R.id.iv_link_picture);\n    }\n\n    @Override\n    public void setData(MessageInfo data) {\n        chatItemDate.setText(data.getTime() != null ? data.getTime() : \"\");\n        Glide.with(mContext).load(data.getHeader()).into(chatItemHeader);\n        chatItemHeader.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onItemClickListener.onHeaderClick((Integer) itemView.getTag());\n            }\n        });\n        switch (data.getFileType()) {\n            case Constants.CHAT_FILE_TYPE_TEXT:\n                chatItemContentText.setSpanText(handler, data.getContent(), true);\n                chatItemVoice.setVisibility(View.GONE);\n                chatItemContentText.setVisibility(View.VISIBLE);\n                chatItemLayoutContent.setVisibility(View.VISIBLE);\n                chatItemVoiceTime.setVisibility(View.GONE);\n                chatItemContentImage.setVisibility(View.GONE);\n                chatItemLayoutContact.setVisibility(View.GONE);\n\n                TextPaint paint = chatItemContentText.getPaint();\n                // 计算textview在屏幕上占多宽\n                int len = (int) paint.measureText(chatItemContentText.getText().toString().trim());\n                if (len < Utils.dp2px(mContext, 200)){\n                    layoutParams.width = len + Utils.dp2px(mContext, 30);\n                } else {\n                    layoutParams.width = LinearLayout.LayoutParams.MATCH_PARENT;\n                }\n                layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;\n                chatItemLayoutContent.setLayoutParams(layoutParams);\n                break;\n            case Constants.CHAT_FILE_TYPE_IMAGE:\n                chatItemVoice.setVisibility(View.GONE);\n                chatItemLayoutContent.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.GONE);\n                chatItemContentText.setVisibility(View.GONE);\n                chatItemContentImage.setVisibility(View.VISIBLE);\n                chatItemLayoutContact.setVisibility(View.GONE);\n\n                Glide.with(mContext).load(data.getFilepath()).into(chatItemContentImage);\n                chatItemContentImage.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        onItemClickListener.onImageClick(chatItemContentImage, (Integer) itemView.getTag());\n                    }\n                });\n                layoutParams.width = Utils.dp2px(mContext, 120);\n                layoutParams.height = Utils.dp2px(mContext, 48);\n                chatItemLayoutContent.setLayoutParams(layoutParams);\n                break;\n            case Constants.CHAT_FILE_TYPE_VOICE:\n                chatItemVoice.setVisibility(View.VISIBLE);\n                chatItemLayoutContent.setVisibility(View.VISIBLE);\n                chatItemContentText.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.VISIBLE);\n                chatItemContentImage.setVisibility(View.GONE);\n                chatItemLayoutContact.setVisibility(View.GONE);\n\n                chatItemVoiceTime.setText(Utils.formatTime(data.getVoiceTime()));\n                chatItemLayoutContent.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        onItemClickListener.onVoiceClick(chatItemVoice, (Integer) itemView.getTag());\n                    }\n                });\n                layoutParams.width = Utils.dp2px(mContext, 120);\n                layoutParams.height = Utils.dp2px(mContext, 48);\n                chatItemLayoutContent.setLayoutParams(layoutParams);\n                break;\n            case Constants.CHAT_FILE_TYPE_FILE:\n                chatItemVoice.setVisibility(View.GONE);\n                chatItemContentText.setVisibility(View.GONE);\n                chatItemContentImage.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.GONE);\n                chatItemLayoutContent.setVisibility(View.GONE);\n                chatItemLayoutContact.setVisibility(View.GONE);\n\n//                chatItemLayoutContent.setVisibility(View.VISIBLE);\n                chatItemLayoutFile.setVisibility(View.VISIBLE);\n                tvFileName.setText(FileUtils.getFileName(data.getFilepath()));\n                try {\n                    tvFileSize.setText(FileUtils.getFileSize(data.getFilepath()));\n                    switch (FileUtils.getExtensionName(data.getFilepath())) {\n                        case \"doc\":\n                        case \"docx\":\n                            ivFileType.setImageResource(R.mipmap.icon_file_word);\n                            break;\n                        case \"ppt\":\n                        case \"pptx\":\n                            ivFileType.setImageResource(R.mipmap.icon_file_ppt);\n                            break;\n                        case \"xls\":\n                        case \"xlsx\":\n                            ivFileType.setImageResource(R.mipmap.icon_file_excel);\n                            break;\n                        case \"pdf\":\n                            ivFileType.setImageResource(R.mipmap.icon_file_pdf);\n                            break;\n                        default:\n                            ivFileType.setImageResource(R.mipmap.icon_file_other);\n                            break;\n                    }\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n                break;\n            case Constants.CHAT_FILE_TYPE_CONTACT:\n                chatItemVoice.setVisibility(View.GONE);\n                chatItemContentText.setVisibility(View.GONE);\n                chatItemContentImage.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.GONE);\n                chatItemLayoutContent.setVisibility(View.GONE);\n                chatItemLayoutFile.setVisibility(View.GONE);\n\n                chatItemLayoutContact.setVisibility(View.VISIBLE);\n\n                IMContact imContact = (IMContact) data.getObject();\n                tvContactSurname.setText(imContact.getSurname());\n                tvContactName.setText(imContact.getName());\n                tvContactPhone.setText(imContact.getPhonenumber());\n                break;\n            case Constants.CHAT_FILE_TYPE_LINK:\n                chatItemVoice.setVisibility(View.GONE);\n                chatItemContentText.setVisibility(View.GONE);\n                chatItemContentImage.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.GONE);\n                chatItemLayoutContent.setVisibility(View.GONE);\n                chatItemLayoutFile.setVisibility(View.GONE);\n                chatItemLayoutContact.setVisibility(View.GONE);\n\n                chatItemLayoutLink.setVisibility(View.VISIBLE);\n                Link link = (Link) data.getObject();\n\n                tvLinkSubject.setText(link.getSubject());\n                tvLinkText.setText(link.getText());\n                Glide.with(mContext).load(link.getStream()).into(ivLinkPicture);\n                break;\n        }\n    }\n\n    public void setItemLongClick() {\n        chatItemContentImage.setOnLongClickListener(new View.OnLongClickListener() {\n            @Override\n            public boolean onLongClick(View v) {\n                onItemClickListener.onLongClickImage(v, (Integer) itemView.getTag());\n                return true;\n            }\n        });\n        chatItemContentText.setOnLongClickListener(new View.OnLongClickListener() {\n            @Override\n            public boolean onLongClick(View v) {\n                onItemClickListener.onLongClickText(v, (Integer) itemView.getTag());\n                return true;\n            }\n        });\n        chatItemLayoutContent.setOnLongClickListener(new View.OnLongClickListener() {\n            @Override\n            public boolean onLongClick(View v) {\n                onItemClickListener.onLongClickItem(v, (Integer) itemView.getTag());\n                return true;\n            }\n        });\n        chatItemLayoutFile.setOnLongClickListener(new View.OnLongClickListener() {\n            @Override\n            public boolean onLongClick(View v) {\n                onItemClickListener.onLongClickFile(v, (Integer) itemView.getTag());\n                return true;\n            }\n        });\n    }\n\n    public void setItemClick() {\n        chatItemContentImage.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onItemClickListener.onImageClick(chatItemContentImage, (Integer) itemView.getTag());\n            }\n        });\n\n        chatItemLayoutFile.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onItemClickListener.onVoiceClick(chatItemVoice, (Integer) itemView.getTag());\n            }\n        });\n\n        chatItemLayoutFile.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onItemClickListener.onFileClick(v, (Integer) itemView.getTag());\n            }\n        });\n\n        chatItemLayoutLink.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onItemClickListener.onLinkClick(v, (Integer) itemView.getTag());\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/adapter/holder/ChatSendViewHolder.java",
    "content": "package com.rance.chatui.adapter.holder;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.support.v4.content.ContextCompat;\nimport android.text.TextPaint;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.ProgressBar;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\n\nimport com.bumptech.glide.Glide;\nimport com.rance.chatui.R;\nimport com.rance.chatui.adapter.ChatAdapter;\nimport com.rance.chatui.enity.IMContact;\nimport com.rance.chatui.enity.Link;\nimport com.rance.chatui.enity.MessageInfo;\nimport com.rance.chatui.util.Constants;\nimport com.rance.chatui.util.FileUtils;\nimport com.rance.chatui.util.Utils;\nimport com.rance.chatui.widget.BubbleImageView;\nimport com.rance.chatui.widget.BubbleLinearLayout;\nimport com.rance.chatui.widget.GifTextView;\n\n/**\n * 作者：Rance on 2016/11/29 10:47\n * 邮箱：rance935@163.com\n */\npublic class ChatSendViewHolder extends BaseViewHolder<MessageInfo> {\n    private static final String TAG = \"ChatSendViewHolder\";\n    TextView chatItemDate;\n    ImageView chatItemHeader;\n    GifTextView chatItemContentText;\n    BubbleImageView chatItemContentImage;\n    ImageView chatItemFail;\n    ProgressBar chatItemProgress;\n    ImageView chatItemVoice;\n    BubbleLinearLayout chatItemLayoutContent;\n    TextView chatItemVoiceTime;\n    BubbleLinearLayout chatItemLayoutFile;\n    ImageView ivFileType;\n    TextView tvFileName;\n    TextView tvFileSize;\n\n    BubbleLinearLayout chatItemLayoutContact;\n    TextView tvContactSurname;\n    TextView tvContactName;\n    TextView tvContactPhone;\n\n    BubbleLinearLayout chatItemLayoutLink;\n    TextView tvLinkSubject;\n    TextView tvLinkText;\n    ImageView ivLinkPicture;\n    private ChatAdapter.onItemClickListener onItemClickListener;\n    private Handler handler;\n    private RelativeLayout.LayoutParams layoutParams;\n    private Context mContext;\n\n    public ChatSendViewHolder(ViewGroup parent, ChatAdapter.onItemClickListener onItemClickListener, Handler handler) {\n        super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_send, parent, false));\n        findViewByIds(itemView);\n        setItemLongClick();\n        setItemClick();\n        this.mContext = parent.getContext();\n        this.onItemClickListener = onItemClickListener;\n        this.handler = handler;\n        layoutParams = (RelativeLayout.LayoutParams) chatItemLayoutContent.getLayoutParams();\n    }\n\n    private void findViewByIds(View itemView) {\n        chatItemDate = (TextView) itemView.findViewById(R.id.chat_item_date);\n        chatItemHeader = (ImageView) itemView.findViewById(R.id.chat_item_header);\n        chatItemContentText = (GifTextView) itemView.findViewById(R.id.chat_item_content_text);\n        chatItemFail = (ImageView) itemView.findViewById(R.id.chat_item_fail);\n        chatItemProgress = (ProgressBar) itemView.findViewById(R.id.chat_item_progress);\n        chatItemContentImage = (BubbleImageView) itemView.findViewById(R.id.chat_item_content_image);\n        chatItemVoice = (ImageView) itemView.findViewById(R.id.chat_item_voice);\n        chatItemLayoutContent = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_content);\n        chatItemVoiceTime = (TextView) itemView.findViewById(R.id.chat_item_voice_time);\n        chatItemLayoutFile = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_file);\n        ivFileType = (ImageView) itemView.findViewById(R.id.iv_file_type);\n        tvFileName = (TextView) itemView.findViewById(R.id.tv_file_name);\n        tvFileSize = (TextView) itemView.findViewById(R.id.tv_file_size);\n        chatItemLayoutContact = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_contact);\n        tvContactSurname = (TextView) itemView.findViewById(R.id.tv_contact_surname);\n        tvContactPhone = (TextView) itemView.findViewById(R.id.tv_contact_phone);\n        chatItemLayoutLink = (BubbleLinearLayout) itemView.findViewById(R.id.chat_item_layout_link);\n        tvLinkSubject = (TextView) itemView.findViewById(R.id.tv_link_subject);\n        tvLinkText = (TextView) itemView.findViewById(R.id.tv_link_text);\n        ivLinkPicture = (ImageView) itemView.findViewById(R.id.iv_link_picture);\n    }\n\n\n    @Override\n    public void setData(MessageInfo data) {\n        chatItemDate.setText(data.getTime() != null ? data.getTime() : \"\");\n        Glide.with(mContext).load(data.getHeader()).into(chatItemHeader);\n        chatItemHeader.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onItemClickListener.onHeaderClick((Integer) itemView.getTag());\n            }\n        });\n        switch (data.getFileType()) {\n            case Constants.CHAT_FILE_TYPE_TEXT:\n                chatItemContentText.setSpanText(handler, data.getContent(), true);\n                chatItemVoice.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.GONE);\n                chatItemContentImage.setVisibility(View.GONE);\n                chatItemLayoutFile.setVisibility(View.GONE);\n                chatItemLayoutContact.setVisibility(View.GONE);\n                chatItemLayoutLink.setVisibility(View.GONE);\n\n                chatItemContentText.setVisibility(View.VISIBLE);\n                chatItemLayoutContent.setVisibility(View.VISIBLE);\n                TextPaint paint = chatItemContentText.getPaint();\n                paint.setColor(ContextCompat.getColor(mContext, R.color.chat_send_text));\n                // 计算textview在屏幕上占多宽\n                int len = (int) paint.measureText(chatItemContentText.getText().toString().trim());\n                if (len < Utils.dp2px(mContext, 200)){\n                    layoutParams.width = len + Utils.dp2px(mContext, 30);\n                } else {\n                    layoutParams.width = LinearLayout.LayoutParams.MATCH_PARENT;\n                }\n                layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;\n                chatItemLayoutContent.setLayoutParams(layoutParams);\n                break;\n            case Constants.CHAT_FILE_TYPE_IMAGE:\n                chatItemVoice.setVisibility(View.GONE);\n                chatItemLayoutContent.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.GONE);\n                chatItemContentText.setVisibility(View.GONE);\n                chatItemLayoutFile.setVisibility(View.GONE);\n                chatItemLayoutContact.setVisibility(View.GONE);\n                chatItemLayoutLink.setVisibility(View.GONE);\n\n                chatItemContentImage.setVisibility(View.VISIBLE);\n                Glide.with(mContext).load(data.getFilepath()).into(chatItemContentImage);\n                layoutParams.width = Utils.dp2px(mContext, 120);\n                layoutParams.height = Utils.dp2px(mContext, 48);\n                chatItemLayoutContent.setLayoutParams(layoutParams);\n                break;\n            case Constants.CHAT_FILE_TYPE_VOICE:\n                chatItemVoice.setVisibility(View.VISIBLE);\n                chatItemLayoutContent.setVisibility(View.VISIBLE);\n                chatItemContentText.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.VISIBLE);\n                chatItemContentImage.setVisibility(View.GONE);\n                chatItemLayoutContact.setVisibility(View.GONE);\n                chatItemLayoutLink.setVisibility(View.GONE);\n\n                chatItemLayoutFile.setVisibility(View.GONE);\n                chatItemVoiceTime.setText(Utils.formatTime(data.getVoiceTime()));\n                layoutParams.width = Utils.dp2px(mContext, 120);\n                layoutParams.height = Utils.dp2px(mContext, 48);\n                chatItemLayoutContent.setLayoutParams(layoutParams);\n                break;\n            case Constants.CHAT_FILE_TYPE_FILE:\n                chatItemVoice.setVisibility(View.GONE);\n                chatItemContentText.setVisibility(View.GONE);\n                chatItemContentImage.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.GONE);\n                chatItemLayoutContent.setVisibility(View.GONE);\n                chatItemLayoutContact.setVisibility(View.GONE);\n                chatItemLayoutLink.setVisibility(View.GONE);\n\n                chatItemLayoutFile.setVisibility(View.VISIBLE);\n                tvFileName.setText(FileUtils.getFileName(data.getFilepath()));\n                try {\n                    tvFileSize.setText(FileUtils.getFileSize(data.getFilepath()));\n                    switch (FileUtils.getExtensionName(data.getFilepath())) {\n                        case \"doc\":\n                        case \"docx\":\n                            ivFileType.setImageResource(R.mipmap.icon_file_word);\n                            break;\n                        case \"ppt\":\n                        case \"pptx\":\n                            ivFileType.setImageResource(R.mipmap.icon_file_ppt);\n                            break;\n                        case \"xls\":\n                        case \"xlsx\":\n                            ivFileType.setImageResource(R.mipmap.icon_file_excel);\n                            break;\n                        case \"pdf\":\n                            ivFileType.setImageResource(R.mipmap.icon_file_pdf);\n                            break;\n                        default:\n                            ivFileType.setImageResource(R.mipmap.icon_file_other);\n                            break;\n                    }\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n                break;\n            case Constants.CHAT_FILE_TYPE_CONTACT:\n                chatItemVoice.setVisibility(View.GONE);\n                chatItemContentText.setVisibility(View.GONE);\n                chatItemContentImage.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.GONE);\n                chatItemLayoutContent.setVisibility(View.GONE);\n                chatItemLayoutFile.setVisibility(View.GONE);\n\n                chatItemLayoutContact.setVisibility(View.VISIBLE);\n\n                IMContact imContact = (IMContact) data.getObject();\n                tvContactSurname.setText(imContact.getSurname());\n                tvContactName.setText(imContact.getName());\n                tvContactPhone.setText(imContact.getPhonenumber());\n                break;\n            case Constants.CHAT_FILE_TYPE_LINK:\n                chatItemVoice.setVisibility(View.GONE);\n                chatItemContentText.setVisibility(View.GONE);\n                chatItemContentImage.setVisibility(View.GONE);\n                chatItemVoiceTime.setVisibility(View.GONE);\n                chatItemLayoutContent.setVisibility(View.GONE);\n                chatItemLayoutFile.setVisibility(View.GONE);\n                chatItemLayoutContact.setVisibility(View.GONE);\n\n                chatItemLayoutLink.setVisibility(View.VISIBLE);\n                Link link = (Link) data.getObject();\n\n                tvLinkSubject.setText(link.getSubject());\n                tvLinkText.setText(link.getText());\n                Glide.with(mContext).load(link.getStream()).into(ivLinkPicture);\n                break;\n        }\n        switch (data.getSendState()) {\n            case Constants.CHAT_ITEM_SENDING:\n                chatItemProgress.setVisibility(View.VISIBLE);\n                chatItemFail.setVisibility(View.GONE);\n                break;\n            case Constants.CHAT_ITEM_SEND_ERROR:\n                chatItemProgress.setVisibility(View.GONE);\n                chatItemFail.setVisibility(View.VISIBLE);\n                break;\n            case Constants.CHAT_ITEM_SEND_SUCCESS:\n                chatItemProgress.setVisibility(View.GONE);\n                chatItemFail.setVisibility(View.GONE);\n                break;\n        }\n    }\n\n    public void setItemLongClick() {\n        chatItemContentImage.setOnLongClickListener(new View.OnLongClickListener() {\n            @Override\n            public boolean onLongClick(View v) {\n                onItemClickListener.onLongClickImage(v, (Integer) itemView.getTag());\n                return true;\n            }\n        });\n        chatItemContentText.setOnLongClickListener(new View.OnLongClickListener() {\n            @Override\n            public boolean onLongClick(View v) {\n                onItemClickListener.onLongClickText(v, (Integer) itemView.getTag());\n                return true;\n            }\n        });\n        chatItemLayoutContent.setOnLongClickListener(new View.OnLongClickListener() {\n            @Override\n            public boolean onLongClick(View v) {\n                onItemClickListener.onLongClickItem(v, (Integer) itemView.getTag());\n                return true;\n            }\n        });\n        chatItemLayoutFile.setOnLongClickListener(new View.OnLongClickListener() {\n            @Override\n            public boolean onLongClick(View v) {\n                onItemClickListener.onLongClickFile(v, (Integer) itemView.getTag());\n                return true;\n            }\n        });\n    }\n\n    public void setItemClick() {\n        chatItemContentImage.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onItemClickListener.onImageClick(chatItemContentImage, (Integer) itemView.getTag());\n            }\n        });\n\n        chatItemLayoutFile.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onItemClickListener.onVoiceClick(chatItemVoice, (Integer) itemView.getTag());\n            }\n        });\n\n        chatItemLayoutFile.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onItemClickListener.onFileClick(v, (Integer) itemView.getTag());\n            }\n        });\n\n        chatItemLayoutLink.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onItemClickListener.onLinkClick(v, (Integer) itemView.getTag());\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/base/BaseFragment.java",
    "content": "package com.rance.chatui.base;\n\n\nimport android.app.Activity;\nimport android.app.ProgressDialog;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\n/**\n * 作者：Rance on 2016/11/18 15:19\n * 邮箱：rance935@163.com\n */\npublic class BaseFragment extends Fragment {\n    public Activity mActivity;\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        mActivity = getActivity();\n        return super.onCreateView(inflater, container, savedInstanceState);\n    }\n\n    @Override\n    public void onViewCreated(View view, Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        mActivity = getActivity();\n    }\n\n    public void toastShow(int resId) {\n        Toast.makeText(mActivity, resId, Toast.LENGTH_SHORT).show();\n    }\n\n    public void toastShow(String resId) {\n        Toast.makeText(mActivity, resId, Toast.LENGTH_SHORT).show();\n    }\n\n    public ProgressDialog progressDialog;\n\n    public ProgressDialog showProgressDialog() {\n        progressDialog = new ProgressDialog(mActivity);\n        progressDialog.setMessage(\"加载中\");\n        progressDialog.show();\n        return progressDialog;\n    }\n\n    public ProgressDialog showProgressDialog(CharSequence message) {\n        progressDialog = new ProgressDialog(mActivity);\n        progressDialog.setMessage(message);\n        progressDialog.show();\n        return progressDialog;\n    }\n\n    public void dismissProgressDialog() {\n        if (progressDialog != null && progressDialog.isShowing()) {\n            // progressDialog.hide();会导致android.view.WindowLeaked\n            progressDialog.dismiss();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/base/MyApplication.java",
    "content": "package com.rance.chatui.base;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.util.DisplayMetrics;\n\n/**\n * 作者：Rance on 2016/12/20 16:49\n * 邮箱：rance935@163.com\n */\npublic class MyApplication extends Application {\n    private static MyApplication mInstance;\n    public static Context mContext;\n    /**\n     * 屏幕宽度\n     */\n    public static int screenWidth;\n    /**\n     * 屏幕高度\n     */\n    public static int screenHeight;\n    /**\n     * 屏幕密度\n     */\n    public static float screenDensity;\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        mContext = getApplicationContext();\n        mInstance = this;\n        initScreenSize();\n    }\n\n    public static Context getInstance() {\n        return mInstance;\n    }\n\n    /**\n     * 初始化当前设备屏幕宽高\n     */\n    private void initScreenSize() {\n        DisplayMetrics curMetrics = getApplicationContext().getResources().getDisplayMetrics();\n        screenWidth = curMetrics.widthPixels;\n        screenHeight = curMetrics.heightPixels;\n        screenDensity = curMetrics.density;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/enity/FullImageInfo.java",
    "content": "package com.rance.chatui.enity;\n\n/**\n * 作者：Rance on 2016/12/21 16:22\n * 邮箱：rance935@163.com\n */\npublic class FullImageInfo {\n    private int locationX;\n    private int locationY;\n    private int width;\n    private int height;\n    private String imageUrl;\n\n    public int getLocationX() {\n        return locationX;\n    }\n\n    public void setLocationX(int locationX) {\n        this.locationX = locationX;\n    }\n\n    public int getLocationY() {\n        return locationY;\n    }\n\n    public void setLocationY(int locationY) {\n        this.locationY = locationY;\n    }\n\n    public int getWidth() {\n        return width;\n    }\n\n    public void setWidth(int width) {\n        this.width = width;\n    }\n\n    public int getHeight() {\n        return height;\n    }\n\n    public void setHeight(int height) {\n        this.height = height;\n    }\n\n    public String getImageUrl() {\n        return imageUrl;\n    }\n\n    public void setImageUrl(String imageUrl) {\n        this.imageUrl = imageUrl;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/enity/IMContact.java",
    "content": "package com.rance.chatui.enity;\n\n/**\n * Created by chengz\n *\n * @date 2017/8/3.\n */\n\npublic class IMContact {\n    String name, phonenumber, surname;\n\n    public IMContact(String name, String phonenumber) {\n        this.name = name;\n        this.phonenumber = phonenumber;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getPhonenumber() {\n        return phonenumber;\n    }\n\n    public void setPhonenumber(String phonenumber) {\n        this.phonenumber = phonenumber;\n    }\n\n    public String getSurname() {\n        return surname;\n    }\n\n    public void setSurname(String surname) {\n        this.surname = surname;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/enity/Link.java",
    "content": "package com.rance.chatui.enity;\n\n/**\n * Created by chengz\n *\n * @date 2017/8/4.\n */\n\npublic class Link {\n    String subject, text, stream, url;\n\n    public String getSubject() {\n        return subject;\n    }\n\n    public void setSubject(String subject) {\n        this.subject = subject;\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    public void setText(String text) {\n        this.text = text;\n    }\n\n    public String getStream() {\n        return stream;\n    }\n\n    public void setStream(String stream) {\n        this.stream = stream;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/enity/MessageInfo.java",
    "content": "package com.rance.chatui.enity;\n\n/**\n * 作者：Rance on 2016/12/14 14:13\n * 邮箱：rance935@163.com\n */\npublic class MessageInfo {\n    private int type;\n    private String content;\n    private String filepath;\n    private int sendState;\n    private String time;\n    private String header;\n    private long voiceTime;\n    private String msgId;\n    private String fileType;\n    private Object object;\n    private String mimeType;\n\n    public Object getObject() {\n        return object;\n    }\n\n    public void setObject(Object object) {\n        this.object = object;\n    }\n\n    public String getFileType() {\n        return fileType;\n    }\n\n    public void setFileType(String fileType) {\n        this.fileType = fileType;\n    }\n\n    public int getType() {\n        return type;\n    }\n\n    public void setType(int type) {\n        this.type = type;\n    }\n\n    public String getContent() {\n        return content;\n    }\n\n    public void setContent(String content) {\n        this.content = content;\n    }\n\n    public String getFilepath() {\n        return filepath;\n    }\n\n    public void setFilepath(String filepath) {\n        this.filepath = filepath;\n    }\n\n    public int getSendState() {\n        return sendState;\n    }\n\n    public void setSendState(int sendState) {\n        this.sendState = sendState;\n    }\n\n    public String getTime() {\n        return time;\n    }\n\n    public void setTime(String time) {\n        this.time = time;\n    }\n\n    public String getHeader() {\n        return header;\n    }\n\n    public void setHeader(String header) {\n        this.header = header;\n    }\n\n    public long getVoiceTime() {\n        return voiceTime;\n    }\n\n    public void setVoiceTime(long voiceTime) {\n        this.voiceTime = voiceTime;\n    }\n\n    public String getMsgId() {\n        return msgId;\n    }\n\n    public void setMsgId(String msgId) {\n        this.msgId = msgId;\n    }\n\n    public String getMimeType() {\n        return mimeType;\n    }\n\n    public void setMimeType(String mimeType) {\n        this.mimeType = mimeType;\n    }\n\n    @Override\n    public String toString() {\n        return \"MessageInfo{\" +\n                \"type=\" + type +\n                \", content='\" + content + '\\'' +\n                \", filepath='\" + filepath + '\\'' +\n                \", sendState=\" + sendState +\n                \", time='\" + time + '\\'' +\n                \", header='\" + header + '\\'' +\n                \", mimeType='\" + mimeType + '\\'' +\n                \", voiceTime=\" + voiceTime +\n                \", msgId='\" + msgId + '\\'' +\n                \", fileType='\" + fileType + '\\'' +\n                \", object=\" + object +\n                '}';\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/ui/activity/ContactActivity.java",
    "content": "package com.rance.chatui.ui.activity;\n\nimport android.Manifest;\nimport android.app.AlertDialog;\nimport android.content.DialogInterface;\nimport android.content.pm.PackageManager;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.provider.ContactsContract;\nimport android.support.annotation.NonNull;\nimport android.support.v4.app.ActivityCompat;\nimport android.support.v4.content.ContextCompat;\nimport android.support.v7.app.AppCompatActivity;\nimport android.os.Bundle;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.rance.chatui.R;\nimport com.rance.chatui.adapter.ContactAdapter;\nimport com.rance.chatui.enity.IMContact;\nimport com.rance.chatui.enity.MessageInfo;\nimport com.rance.chatui.util.Constants;\n\nimport org.greenrobot.eventbus.EventBus;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\npublic class ContactActivity extends AppCompatActivity implements ContactAdapter.OnContactClickListener {\n    RecyclerView rvContact;\n\n    private List<IMContact> imContactList;\n    private final static int CODE_REQUEST_CONTACT = 0x222;\n    private ContactAdapter mContactAdapter;\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_contact);\n        rvContact = (RecyclerView) findViewById(R.id.rv_contact);\n        setup();\n        checkPermission();\n    }\n\n    private void setup() {\n        rvContact.setLayoutManager(new LinearLayoutManager(this));\n    }\n\n    private void checkPermission() {\n        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)\n                != PackageManager.PERMISSION_GRANTED) {\n            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}\n                    , CODE_REQUEST_CONTACT);\n        } else {\n            readContacts();\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        switch (requestCode) {\n            case CODE_REQUEST_CONTACT:\n                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n                    readContacts();\n                } else {\n                    Toast.makeText(this, \"未设置读取联系人权限\", Toast.LENGTH_SHORT).show();\n                }\n                break;\n        }\n    }\n\n    private void readContacts() {\n        imContactList = new ArrayList<>();\n        Cursor cursor = null;\n        try {\n            Uri contactUri =ContactsContract.CommonDataKinds.Phone.CONTENT_URI;\n            cursor = this.getContentResolver().query(contactUri,\n                    new String[]{\"display_name\", \"sort_key\", \"contact_id\",\"data1\"},\n                    null, null, \"sort_key\");\n            String contactName;\n            String contactNumber;\n            while (cursor.moveToNext()) {\n                contactName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));\n                contactNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));\n                IMContact imContact = new IMContact(contactName, contactNumber);\n                imContact.setSurname(contactName.substring(0, 1));\n                if (contactName!=null)\n                    imContactList.add(imContact);\n            }\n            cursor.close();\n\n        }catch (Exception e){\n            e.printStackTrace();\n        }finally {\n        }\n\n        loadData();\n    }\n\n    private void loadData() {\n        mContactAdapter = new ContactAdapter(imContactList);\n        mContactAdapter.setOnContactClickListener(this);\n        rvContact.setAdapter(mContactAdapter);\n    }\n\n    @Override\n    public void onContactClick(View view, final IMContact imContact) {\n        AlertDialog.Builder builder = new AlertDialog.Builder(this);\n        builder.setTitle(\"Send to John Snow\");\n\n        View contentView = LayoutInflater.from(this).inflate(R.layout.dialog_contact, null);\n\n        TextView tvSurname = (TextView) contentView.findViewById(R.id.tv_surname);\n        tvSurname.setText(imContact.getSurname());\n//        ((TextView)contentView.findViewById(R.id.tv_surname)).setText(imContact.getName().charAt(0));\n        ((TextView)contentView.findViewById(R.id.tv_name)).setText(imContact.getName());\n        ((TextView)contentView.findViewById(R.id.tv_phone)).setText(\"电话：\" + imContact.getPhonenumber());\n\n        builder.setView(contentView);\n        builder.setPositiveButton(\"Send\", new DialogInterface.OnClickListener() {\n            @Override\n            public void onClick(DialogInterface dialog, int which) {\n                MessageInfo messageInfo = new MessageInfo();\n                messageInfo.setFileType(Constants.CHAT_FILE_TYPE_CONTACT);\n                messageInfo.setObject(imContact);\n                EventBus.getDefault().post(messageInfo);\n                dialog.dismiss();\n                finish();\n            }\n        });\n\n        builder.setNegativeButton(\"Cancel\", new DialogInterface.OnClickListener() {\n            @Override\n            public void onClick(DialogInterface dialog, int which) {\n                dialog.dismiss();\n            }\n        });\n\n        builder.show();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/ui/activity/FullImageActivity.java",
    "content": "package com.rance.chatui.ui.activity;\n\nimport android.animation.ObjectAnimator;\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.ViewTreeObserver;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.view.animation.DecelerateInterpolator;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\n\nimport com.bumptech.glide.Glide;\nimport com.rance.chatui.R;\nimport com.rance.chatui.enity.FullImageInfo;\n\nimport org.greenrobot.eventbus.EventBus;\nimport org.greenrobot.eventbus.Subscribe;\nimport org.greenrobot.eventbus.ThreadMode;\n\n\n/**\n * 作者：Rance on 2016/12/15 15:56\n * 邮箱：rance935@163.com\n */\npublic class FullImageActivity extends Activity {\n\n    ImageView fullImage;\n    LinearLayout fullLay;\n    private int mLeft;\n    private int mTop;\n    private float mScaleX;\n    private float mScaleY;\n    private Drawable mBackground;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        requestWindowFeature(Window.FEATURE_NO_TITLE);\n        setContentView(R.layout.activity_full_image);\n        fullImage = (ImageView) findViewById(R.id.full_image);\n        fullLay = (LinearLayout) findViewById(R.id.full_lay);\n\n        fullImage.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                onImageClick();\n            }\n        });\n\n        EventBus.getDefault().register(this);\n    }\n\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) //在ui线程执行\n    public void onDataSynEvent(final FullImageInfo fullImageInfo) {\n        final int left = fullImageInfo.getLocationX();\n        final int top = fullImageInfo.getLocationY();\n        final int width = fullImageInfo.getWidth();\n        final int height = fullImageInfo.getHeight();\n        mBackground = new ColorDrawable(Color.BLACK);\n        fullLay.setBackground(mBackground);\n        fullImage.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {\n            @Override\n            public boolean onPreDraw() {\n                fullImage.getViewTreeObserver().removeOnPreDrawListener(this);\n                int location[] = new int[2];\n                fullImage.getLocationOnScreen(location);\n                mLeft = left - location[0];\n                mTop = top - location[1];\n                mScaleX = width * 1.0f / fullImage.getWidth();\n                mScaleY = height * 1.0f / fullImage.getHeight();\n                activityEnterAnim();\n                return true;\n            }\n        });\n        Glide.with(this).load(fullImageInfo.getImageUrl()).into(fullImage);\n    }\n\n    private void activityEnterAnim() {\n        fullImage.setPivotX(0);\n        fullImage.setPivotY(0);\n        fullImage.setScaleX(mScaleX);\n        fullImage.setScaleY(mScaleY);\n        fullImage.setTranslationX(mLeft);\n        fullImage.setTranslationY(mTop);\n        fullImage.animate().scaleX(1).scaleY(1).translationX(0).translationY(0).\n                setDuration(500).setInterpolator(new DecelerateInterpolator()).start();\n        ObjectAnimator objectAnimator = ObjectAnimator.ofInt(mBackground, \"alpha\", 0, 255);\n        objectAnimator.setInterpolator(new DecelerateInterpolator());\n        objectAnimator.setDuration(500);\n        objectAnimator.start();\n    }\n\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    private void activityExitAnim(Runnable runnable) {\n        fullImage.setPivotX(0);\n        fullImage.setPivotY(0);\n        fullImage.animate().scaleX(mScaleX).scaleY(mScaleY).translationX(mLeft).translationY(mTop).\n                withEndAction(runnable).\n                setDuration(500).setInterpolator(new DecelerateInterpolator()).start();\n        ObjectAnimator objectAnimator = ObjectAnimator.ofInt(mBackground, \"alpha\", 255, 0);\n        objectAnimator.setInterpolator(new DecelerateInterpolator());\n        objectAnimator.setDuration(500);\n        objectAnimator.start();\n    }\n\n    @Override\n    public void onBackPressed() {\n        activityExitAnim(new Runnable() {\n            @Override\n            public void run() {\n                finish();\n                overridePendingTransition(0, 0);\n            }\n        });\n    }\n\n    public void onImageClick() {\n        activityExitAnim(new Runnable() {\n            @Override\n            public void run() {\n                finish();\n                overridePendingTransition(0, 0);\n            }\n        });\n    }\n\n    @Override\n    protected void onDestroy() {\n        EventBus.getDefault().unregister(this);\n        super.onDestroy();\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/ui/activity/IMActivity.java",
    "content": "package com.rance.chatui.ui.activity;\n\nimport android.content.Intent;\nimport android.graphics.drawable.AnimationDrawable;\nimport android.media.MediaPlayer;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.support.v4.app.Fragment;\nimport android.support.v4.content.FileProvider;\nimport android.support.v7.app.AppCompatActivity;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.ImageView;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.labo.kaji.relativepopupwindow.RelativePopupWindow;\nimport com.rance.chatui.R;\nimport com.rance.chatui.adapter.ChatAdapter;\nimport com.rance.chatui.adapter.CommonFragmentPagerAdapter;\nimport com.rance.chatui.enity.FullImageInfo;\nimport com.rance.chatui.enity.Link;\nimport com.rance.chatui.enity.MessageInfo;\nimport com.rance.chatui.ui.fragment.ChatEmotionFragment;\nimport com.rance.chatui.ui.fragment.ChatFunctionFragment;\nimport com.rance.chatui.util.Constants;\nimport com.rance.chatui.util.GlobalOnItemClickManagerUtils;\nimport com.rance.chatui.util.MediaManager;\nimport com.rance.chatui.util.MessageCenter;\nimport com.rance.chatui.widget.ChatContextMenu;\nimport com.rance.chatui.widget.EmotionInputDetector;\nimport com.rance.chatui.widget.NoScrollViewPager;\nimport com.rance.chatui.widget.StateButton;\n\nimport org.greenrobot.eventbus.EventBus;\nimport org.greenrobot.eventbus.Subscribe;\nimport org.greenrobot.eventbus.ThreadMode;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * 作者：Rance on 2016/11/29 10:47\n * 邮箱：rance935@163.com\n */\npublic class IMActivity extends AppCompatActivity {\n    private static final String TAG = \"IMActivity\";\n    RecyclerView chatList;\n    ImageView emotionVoice;\n    EditText editText;\n    TextView voiceText;\n    ImageView emotionButton;\n    ImageView emotionAdd;\n    StateButton emotionSend;\n    NoScrollViewPager viewpager;\n    RelativeLayout emotionLayout;\n\n    private EmotionInputDetector mDetector;\n    private ArrayList<Fragment> fragments;\n    private ChatEmotionFragment chatEmotionFragment;\n    private ChatFunctionFragment chatFunctionFragment;\n    private CommonFragmentPagerAdapter adapter;\n\n    private ChatAdapter chatAdapter;\n    private LinearLayoutManager layoutManager;\n    private List<MessageInfo> messageInfos;\n    //录音相关\n    int animationRes = 0;\n    int res = 0;\n    AnimationDrawable animationDrawable = null;\n    private ImageView animView;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        findViewByIds();\n        EventBus.getDefault().register(this);\n        initWidget();\n        handleIncomeAction();\n    }\n\n    private void findViewByIds() {\n        chatList = (RecyclerView) findViewById(R.id.chat_list);\n        emotionVoice = (ImageView) findViewById(R.id.emotion_voice);\n        editText = (EditText) findViewById(R.id.edit_text);\n        voiceText = (TextView) findViewById(R.id.voice_text);\n        emotionButton = (ImageView) findViewById(R.id.emotion_button);\n        emotionAdd = (ImageView) findViewById(R.id.emotion_add);\n        emotionSend = (StateButton) findViewById(R.id.emotion_send);\n        emotionLayout = (RelativeLayout) findViewById(R.id.emotion_layout);\n        viewpager = (NoScrollViewPager) findViewById(R.id.viewpager);\n    }\n\n    private void handleIncomeAction() {\n        Bundle bundle = getIntent().getExtras();\n        if (bundle == null) {\n            return;\n        }\n\n        MessageCenter.handleIncoming(bundle, getIntent().getType(), this);\n    }\n\n    private void initWidget() {\n        fragments = new ArrayList<>();\n        chatEmotionFragment = new ChatEmotionFragment();\n        fragments.add(chatEmotionFragment);\n        chatFunctionFragment = new ChatFunctionFragment();\n        fragments.add(chatFunctionFragment);\n        adapter = new CommonFragmentPagerAdapter(getSupportFragmentManager(), fragments);\n        viewpager.setAdapter(adapter);\n        viewpager.setCurrentItem(0);\n\n        mDetector = EmotionInputDetector.with(this)\n                .setEmotionView(emotionLayout)\n                .setViewPager(viewpager)\n                .bindToContent(chatList)\n                .bindToEditText(editText)\n                .bindToEmotionButton(emotionButton)\n                .bindToAddButton(emotionAdd)\n                .bindToSendButton(emotionSend)\n                .bindToVoiceButton(emotionVoice)\n                .bindToVoiceText(voiceText)\n                .build();\n\n        GlobalOnItemClickManagerUtils globalOnItemClickListener = GlobalOnItemClickManagerUtils.getInstance(this);\n        globalOnItemClickListener.attachToEditText(editText);\n\n        chatAdapter = new ChatAdapter(messageInfos);\n        layoutManager = new LinearLayoutManager(this);\n        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);\n        chatList.setLayoutManager(layoutManager);\n        chatList.setAdapter(chatAdapter);\n        chatList.addOnScrollListener(new RecyclerView.OnScrollListener() {\n            @Override\n            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {\n                switch (newState) {\n                    case RecyclerView.SCROLL_STATE_IDLE:\n                        chatAdapter.handler.removeCallbacksAndMessages(null);\n                        chatAdapter.notifyDataSetChanged();\n                        break;\n                    case RecyclerView.SCROLL_STATE_DRAGGING:\n                        chatAdapter.handler.removeCallbacksAndMessages(null);\n                        mDetector.hideEmotionLayout(false);\n                        mDetector.hideSoftInput();\n                        break;\n                    default:\n                        break;\n                }\n            }\n\n            @Override\n            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n                super.onScrolled(recyclerView, dx, dy);\n            }\n        });\n        chatAdapter.addItemClickListener(itemClickListener);\n        LoadData();\n    }\n\n    /**\n     * item点击事件\n     */\n    private ChatAdapter.onItemClickListener itemClickListener = new ChatAdapter.onItemClickListener() {\n        @Override\n        public void onHeaderClick(int position) {\n            Toast.makeText(IMActivity.this, \"onHeaderClick\", Toast.LENGTH_SHORT).show();\n        }\n\n        @Override\n        public void onImageClick(View view, int position) {\n            int location[] = new int[2];\n            view.getLocationOnScreen(location);\n            FullImageInfo fullImageInfo = new FullImageInfo();\n            fullImageInfo.setLocationX(location[0]);\n            fullImageInfo.setLocationY(location[1]);\n            fullImageInfo.setWidth(view.getWidth());\n            fullImageInfo.setHeight(view.getHeight());\n            fullImageInfo.setImageUrl(messageInfos.get(position).getFilepath());\n            EventBus.getDefault().postSticky(fullImageInfo);\n            startActivity(new Intent(IMActivity.this, FullImageActivity.class));\n            overridePendingTransition(0, 0);\n        }\n\n        @Override\n        public void onVoiceClick(final ImageView imageView, final int position) {\n            if (animView != null) {\n                animView.setImageResource(res);\n                animView = null;\n            }\n            switch (messageInfos.get(position).getType()) {\n                case 1:\n                    animationRes = R.drawable.voice_left;\n                    res = R.mipmap.icon_voice_left3;\n                    break;\n                case 2:\n                    animationRes = R.drawable.voice_right;\n                    res = R.mipmap.icon_voice_right3;\n                    break;\n            }\n            animView = imageView;\n            animView.setImageResource(animationRes);\n            animationDrawable = (AnimationDrawable) imageView.getDrawable();\n            animationDrawable.start();\n            MediaManager.playSound(messageInfos.get(position).getFilepath(), new MediaPlayer.OnCompletionListener() {\n                @Override\n                public void onCompletion(MediaPlayer mp) {\n                    animView.setImageResource(res);\n                }\n            });\n        }\n\n        @Override\n        public void onFileClick(View view, int position) {\n\n            MessageInfo messageInfo = messageInfos.get(position);\n            Intent intent = new Intent(Intent.ACTION_VIEW);\n            intent.addCategory(Intent.CATEGORY_DEFAULT);\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            File file = new File(messageInfo.getFilepath());\n            Uri fileUri = FileProvider.getUriForFile(IMActivity.this, Constants.AUTHORITY, file);\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n            }\n            intent.setDataAndType(fileUri, messageInfo.getMimeType());\n            startActivity(intent);\n        }\n\n        @Override\n        public void onLinkClick(View view, int position) {\n            MessageInfo messageInfo = messageInfos.get(position);\n            Link link = (Link) messageInfo.getObject();\n            Uri uri = Uri.parse(link.getUrl());\n            Intent intent = new Intent(Intent.ACTION_VIEW, uri);\n            startActivity(intent);\n        }\n\n        @Override\n        public void onLongClickImage(View view, int position) {\n\n            ChatContextMenu chatContextMenu = new ChatContextMenu(view.getContext(),messageInfos.get(position));\n//            chatContextMenu.setAnimationStyle();\n            chatContextMenu.showOnAnchor(view, RelativePopupWindow.VerticalPosition.ABOVE,\n                    RelativePopupWindow.HorizontalPosition.CENTER);\n\n        }\n\n        @Override\n        public void onLongClickText(View view, int position) {\n            ChatContextMenu chatContextMenu = new ChatContextMenu(view.getContext(),messageInfos.get(position));\n            chatContextMenu.showOnAnchor(view, RelativePopupWindow.VerticalPosition.ABOVE,\n                    RelativePopupWindow.HorizontalPosition.CENTER);\n        }\n\n        @Override\n        public void onLongClickItem(View view, int position) {\n            ChatContextMenu chatContextMenu = new ChatContextMenu(view.getContext(),messageInfos.get(position));\n            chatContextMenu.showOnAnchor(view, RelativePopupWindow.VerticalPosition.ABOVE,\n                    RelativePopupWindow.HorizontalPosition.CENTER);\n        }\n\n        @Override\n        public void onLongClickFile(View view, int position) {\n            ChatContextMenu chatContextMenu = new ChatContextMenu(view.getContext(),messageInfos.get(position));\n            chatContextMenu.showOnAnchor(view, RelativePopupWindow.VerticalPosition.ABOVE,\n                    RelativePopupWindow.HorizontalPosition.CENTER);\n        }\n\n        @Override\n        public void onLongClickLink(View view, int position) {\n            ChatContextMenu chatContextMenu = new ChatContextMenu(view.getContext(),messageInfos.get(position));\n            chatContextMenu.showOnAnchor(view, RelativePopupWindow.VerticalPosition.ABOVE,\n                    RelativePopupWindow.HorizontalPosition.CENTER);\n        }\n    };\n\n    /**\n     * 构造聊天数据\n     */\n    private void LoadData() {\n        messageInfos = new ArrayList<>();\n\n        MessageInfo messageInfo = new MessageInfo();\n        messageInfo.setContent(\"你好，欢迎使用Rance的聊天界面框架\");\n        messageInfo.setFileType(Constants.CHAT_FILE_TYPE_TEXT);\n        messageInfo.setType(Constants.CHAT_ITEM_TYPE_LEFT);\n        messageInfo.setHeader(\"http://img0.imgtn.bdimg.com/it/u=401967138,750679164&fm=26&gp=0.jpg\");\n        messageInfos.add(messageInfo);\n\n        MessageInfo messageInfo1 = new MessageInfo();\n        messageInfo1.setFilepath(\"http://www.trueme.net/bb_midi/welcome.wav\");\n        messageInfo1.setVoiceTime(3000);\n        messageInfo1.setFileType(Constants.CHAT_FILE_TYPE_VOICE);\n        messageInfo1.setType(Constants.CHAT_ITEM_TYPE_RIGHT);\n        messageInfo1.setSendState(Constants.CHAT_ITEM_SEND_SUCCESS);\n        messageInfo1.setHeader(\"http://img.dongqiudi.com/uploads/avatar/2014/10/20/8MCTb0WBFG_thumb_1413805282863.jpg\");\n        messageInfos.add(messageInfo1);\n\n        MessageInfo messageInfo2 = new MessageInfo();\n        messageInfo2.setFilepath(\"http://img4.imgtn.bdimg.com/it/u=1800788429,176707229&fm=21&gp=0.jpg\");\n        messageInfo2.setFileType(Constants.CHAT_FILE_TYPE_IMAGE);\n        messageInfo2.setType(Constants.CHAT_ITEM_TYPE_LEFT);\n        messageInfo2.setHeader(\"http://img0.imgtn.bdimg.com/it/u=401967138,750679164&fm=26&gp=0.jpg\");\n        messageInfos.add(messageInfo2);\n\n        MessageInfo messageInfo3 = new MessageInfo();\n        messageInfo3.setContent(\"[微笑][色][色][色]\");\n        messageInfo3.setFileType(Constants.CHAT_FILE_TYPE_TEXT);\n        messageInfo3.setType(Constants.CHAT_ITEM_TYPE_RIGHT);\n        messageInfo3.setSendState(Constants.CHAT_ITEM_SEND_ERROR);\n        messageInfo3.setHeader(\"http://img.dongqiudi.com/uploads/avatar/2014/10/20/8MCTb0WBFG_thumb_1413805282863.jpg\");\n        messageInfos.add(messageInfo3);\n\n        chatAdapter.addAll(messageInfos);\n    }\n\n    @Subscribe(threadMode = ThreadMode.MAIN)\n    public void MessageEventBus(final MessageInfo messageInfo) {\n        messageInfo.setHeader(\"http://img.dongqiudi.com/uploads/avatar/2014/10/20/8MCTb0WBFG_thumb_1413805282863.jpg\");\n        messageInfo.setType(Constants.CHAT_ITEM_TYPE_RIGHT);\n        messageInfo.setSendState(Constants.CHAT_ITEM_SENDING);\n        messageInfos.add(messageInfo);\n        chatAdapter.notifyItemInserted(messageInfos.size() - 1);\n//        chatAdapter.add(messageInfo);\n        chatList.scrollToPosition(chatAdapter.getItemCount() - 1);\n        new Handler().postDelayed(new Runnable() {\n            public void run() {\n                messageInfo.setSendState(Constants.CHAT_ITEM_SEND_SUCCESS);\n                chatAdapter.notifyDataSetChanged();\n            }\n        }, 2000);\n        new Handler().postDelayed(new Runnable() {\n            public void run() {\n                MessageInfo message = new MessageInfo();\n                message.setContent(\"这是模拟消息回复\");\n                message.setType(Constants.CHAT_ITEM_TYPE_LEFT);\n                message.setFileType(Constants.CHAT_FILE_TYPE_TEXT);\n                message.setHeader(\"http://img0.imgtn.bdimg.com/it/u=401967138,750679164&fm=26&gp=0.jpg\");\n                messageInfos.add(message);\n                chatAdapter.notifyItemInserted(messageInfos.size() - 1);\n                chatList.scrollToPosition(chatAdapter.getItemCount() - 1);\n            }\n        }, 3000);\n    }\n\n    @Override\n    public void onBackPressed() {\n        if (!mDetector.interceptBackPress()) {\n            super.onBackPressed();\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        EventBus.getDefault().removeStickyEvent(this);\n        EventBus.getDefault().unregister(this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/ui/fragment/ChatEmotionFragment.java",
    "content": "package com.rance.chatui.ui.fragment;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.view.ViewPager;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.GridView;\nimport android.widget.LinearLayout;\n\nimport com.rance.chatui.R;\nimport com.rance.chatui.adapter.EmotionGridViewAdapter;\nimport com.rance.chatui.adapter.EmotionPagerAdapter;\nimport com.rance.chatui.base.BaseFragment;\nimport com.rance.chatui.base.MyApplication;\nimport com.rance.chatui.util.Utils;\nimport com.rance.chatui.util.EmotionUtils;\nimport com.rance.chatui.util.GlobalOnItemClickManagerUtils;\nimport com.rance.chatui.widget.IndicatorView;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * 作者：Rance on 2016/12/13 16:01\n * 邮箱：rance935@163.com\n */\npublic class ChatEmotionFragment extends BaseFragment {\n    ViewPager fragmentChatVp;\n    IndicatorView fragmentChatGroup;\n    private View rootView;\n    private EmotionPagerAdapter emotionPagerAdapter;\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        if (rootView == null) {\n            rootView = inflater.inflate(R.layout.fragment_chat_emotion, container, false);\n            fragmentChatVp = (ViewPager) rootView.findViewById(R.id.fragment_chat_vp);\n            fragmentChatGroup = (IndicatorView) rootView.findViewById(R.id.fragment_chat_group);\n            initWidget();\n        }\n        return rootView;\n    }\n\n    private void initWidget() {\n        fragmentChatVp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {\n            int oldPagerPos = 0;\n\n            @Override\n            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {\n\n            }\n\n            @Override\n            public void onPageSelected(int position) {\n                fragmentChatGroup.playByStartPointToNext(oldPagerPos, position);\n                oldPagerPos = position;\n            }\n\n            @Override\n            public void onPageScrollStateChanged(int state) {\n\n            }\n        });\n        initEmotion();\n    }\n\n    /**\n     * 初始化表情面板\n     * 思路：获取表情的总数，按每行存放7个表情，动态计算出每个表情所占的宽度大小（包含间距），\n     *      而每个表情的高与宽应该是相等的，这里我们约定只存放3行\n     *      每个面板最多存放7*3=21个表情，再减去一个删除键，即每个面板包含20个表情\n     *      根据表情总数，循环创建多个容量为20的List，存放表情，对于大小不满20进行特殊\n     *      处理即可。\n     */\n    private void initEmotion() {\n        // 获取屏幕宽度\n        int screenWidth = MyApplication.screenWidth;\n        // item的间距\n        int spacing = Utils.dp2px(getActivity(), 12);\n        // 动态计算item的宽度和高度\n        int itemWidth = (screenWidth - spacing * 8) / 7;\n        //动态计算gridview的总高度\n        int gvHeight = itemWidth * 3 + spacing * 6;\n\n        List<GridView> emotionViews = new ArrayList<>();\n        List<String> emotionNames = new ArrayList<>();\n        // 遍历所有的表情的key\n        for (String emojiName : EmotionUtils.EMOTION_STATIC_MAP.keySet()) {\n            emotionNames.add(emojiName);\n            // 每20个表情作为一组,同时添加到ViewPager对应的view集合中\n            if (emotionNames.size() == 23) {\n                GridView gv = createEmotionGridView(emotionNames, screenWidth, spacing, itemWidth, gvHeight);\n                emotionViews.add(gv);\n                // 添加完一组表情,重新创建一个表情名字集合\n                emotionNames = new ArrayList<>();\n            }\n        }\n\n        // 判断最后是否有不足23个表情的剩余情况\n        if (emotionNames.size() > 0) {\n            GridView gv = createEmotionGridView(emotionNames, screenWidth, spacing, itemWidth, gvHeight);\n            emotionViews.add(gv);\n        }\n\n        //初始化指示器\n        fragmentChatGroup.initIndicator(emotionViews.size());\n        // 将多个GridView添加显示到ViewPager中\n        emotionPagerAdapter = new EmotionPagerAdapter(emotionViews);\n        fragmentChatVp.setAdapter(emotionPagerAdapter);\n        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(screenWidth, gvHeight);\n        fragmentChatVp.setLayoutParams(params);\n\n\n    }\n\n    /**\n     * 创建显示表情的GridView\n     */\n    private GridView createEmotionGridView(List<String> emotionNames, int gvWidth, int padding, int itemWidth, int gvHeight) {\n        // 创建GridView\n        GridView gv = new GridView(getActivity());\n        //设置点击背景透明\n        gv.setSelector(android.R.color.transparent);\n        //设置7列\n        gv.setNumColumns(8);\n        gv.setPadding(padding, padding, padding, padding);\n        gv.setHorizontalSpacing(padding);\n        gv.setVerticalSpacing(padding * 2);\n        //设置GridView的宽高\n        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(gvWidth, gvHeight);\n        gv.setLayoutParams(params);\n        // 给GridView设置表情图片\n        EmotionGridViewAdapter adapter = new EmotionGridViewAdapter(getActivity(), emotionNames, itemWidth);\n        gv.setAdapter(adapter);\n        //设置全局点击事件\n        gv.setOnItemClickListener(GlobalOnItemClickManagerUtils.getInstance(getActivity()).getOnItemClickListener());\n        return gv;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/ui/fragment/ChatFunctionFragment.java",
    "content": "package com.rance.chatui.ui.fragment;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Environment;\nimport android.provider.MediaStore;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.ActivityCompat;\nimport android.support.v4.content.ContextCompat;\nimport android.support.v4.content.FileProvider;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.rance.chatui.R;\nimport com.rance.chatui.base.BaseFragment;\nimport com.rance.chatui.enity.MessageInfo;\nimport com.rance.chatui.ui.activity.ContactActivity;\nimport com.rance.chatui.util.Constants;\nimport com.rance.chatui.util.FileUtils;\nimport com.rance.chatui.util.PhotoUtils;\n\nimport org.greenrobot.eventbus.EventBus;\n\nimport java.io.File;\n\n\n/**\n * 作者：Rance on 2016/12/13 16:01\n * 邮箱：rance935@163.com\n */\npublic class ChatFunctionFragment extends BaseFragment {\n    private static final String TAG = \"ChatFunctionFragment\";\n    private View rootView;\n    private static final int CODE_TAKE_PHOTO = 0x111;\n    private static final int CODE_CROP_PHOTO = 0xa2;\n    private static final int REQUEST_CODE_PICK_IMAGE = 0xa3;\n    private static final int REQUEST_CODE_PICK_FILE = 0xa4;\n    private static final int CODE_REQUEST_CAMERA = 0xa5;\n    private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = 0xa6;\n    private static final int MY_PERMISSIONS_REQUEST_WRITE_STORAGE_CODE = 0xa7;\n    private static final int MY_PERMISSIONS_REQUEST_CAMERACODE = 0xa8;\n\n    private int output_X = 480;\n    private int output_Y = 480;\n    //    private File output;\n//    private Uri imageUri;\n    private File fileUri = new File(Environment.getExternalStorageDirectory().getPath() + \"/photo.jpg\");\n    private File fileCropUri = new File(Environment.getExternalStorageDirectory().getPath() + \"/crop_photo.jpg\");\n    private Uri imageUri;\n    private Uri cropImageUri;\n    TextView tvCapture, tvAlbum, tvContact, tvCloud, tvFile, tvLocation;\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        if (rootView == null) {\n            rootView = inflater.inflate(R.layout.fragment_chat_function, container, false);\n            findViewByIds(rootView);\n            setItemClick();\n        }\n        return rootView;\n    }\n\n    private void findViewByIds(View rootView) {\n        tvCapture = (TextView) rootView.findViewById(R.id.chat_function_capture);\n        tvAlbum = (TextView) rootView.findViewById(R.id.chat_function_album);\n        tvContact = (TextView) rootView.findViewById(R.id.chat_function_contact);\n        tvCloud = (TextView) rootView.findViewById(R.id.chat_function_cloud);\n        tvFile = (TextView) rootView.findViewById(R.id.chat_function_file);\n        tvLocation = (TextView) rootView.findViewById(R.id.chat_function_location);\n    }\n\n    private void autoObtainCameraPermission() {\n\n        if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED\n                || ContextCompat.checkSelfPermission(mActivity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {\n\n            if (ActivityCompat.shouldShowRequestPermissionRationale(mActivity, Manifest.permission.CAMERA)) {\n                Toast.makeText(mActivity, \"您已拒绝过一次\", Toast.LENGTH_SHORT).show();\n            }\n            ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_CAMERACODE);\n        } else {//有权限直接调用系统相机拍照\n            imageUri = Uri.fromFile(fileUri);\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)\n                imageUri = FileProvider.getUriForFile(mActivity, Constants.AUTHORITY, fileUri);//通过FileProvider创建一个content类型的Uri\n            PhotoUtils.takePicture(this, imageUri, CODE_TAKE_PHOTO);\n        }\n    }\n\n    public void setItemClick() {\n        tvCapture.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                autoObtainCameraPermission();\n            }\n        });\n        tvAlbum.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (ContextCompat.checkSelfPermission(getActivity(),\n                        Manifest.permission.WRITE_EXTERNAL_STORAGE)\n                        != PackageManager.PERMISSION_GRANTED) {\n                    ActivityCompat.requestPermissions(getActivity(),\n                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},\n                            MY_PERMISSIONS_REQUEST_WRITE_STORAGE_CODE);\n\n                } else {\n                    choosePhoto();\n                }\n            }\n        });\n\n        tvFile.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (ContextCompat.checkSelfPermission(getActivity(),\n                        Manifest.permission.WRITE_EXTERNAL_STORAGE)\n                        != PackageManager.PERMISSION_GRANTED) {\n                    ActivityCompat.requestPermissions(getActivity(),\n                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},\n                            MY_PERMISSIONS_REQUEST_WRITE_STORAGE_CODE);\n                } else {\n                    chooseFile();\n                }\n            }\n        });\n\n        tvCloud.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n\n            }\n        });\n\n        tvLocation.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n\n            }\n        });\n\n        tvContact.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                showContact();\n            }\n        });\n    }\n\n    private void showContact() {\n        Intent intent = new Intent(mActivity, ContactActivity.class);\n        startActivity(intent);\n    }\n\n    private void chooseFile() {\n        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);\n        intent.setType(\"file/*\");\n        intent.addCategory(Intent.CATEGORY_OPENABLE);\n        startActivityForResult(intent, REQUEST_CODE_PICK_FILE);\n    }\n\n    /**\n     * 拍照\n     */\n    private void takePhoto() {\n        imageUri = Uri.fromFile(fileUri);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            imageUri = FileProvider.getUriForFile(mActivity, Constants.AUTHORITY, fileUri);\n            PhotoUtils.takePicture(this, imageUri, CODE_TAKE_PHOTO);\n        }\n    }\n\n    /**\n     * 从相册选取图片\n     */\n    private void choosePhoto() {\n        /**\n         * 打开选择图片的界面\n         */\n        Intent intent = new Intent(Intent.ACTION_PICK);\n        intent.setType(\"image/*\");//相片类型\n        startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);\n\n    }\n\n    public void onActivityResult(int req, int res, Intent data) {\n        switch (req) {\n            case CODE_TAKE_PHOTO:\n                if (res == Activity.RESULT_OK) {\n                    MessageInfo messageInfo = new MessageInfo();\n                    messageInfo.setFilepath(fileUri.getAbsolutePath());\n                    messageInfo.setFileType(Constants.CHAT_FILE_TYPE_IMAGE);\n                    EventBus.getDefault().post(messageInfo);\n                }\n\n                break;\n            case CODE_CROP_PHOTO:\n                if (res == Activity.RESULT_OK) {\n                    try {\n                        MessageInfo messageInfo = new MessageInfo();\n                        messageInfo.setFilepath(cropImageUri.getPath());\n                        messageInfo.setFileType(Constants.CHAT_FILE_TYPE_IMAGE);\n                        EventBus.getDefault().post(messageInfo);\n                    } catch (Exception e) {\n                    }\n                } else {\n                    Log.d(Constants.TAG, \"失败\");\n                }\n\n                break;\n            case REQUEST_CODE_PICK_IMAGE:\n                if (res == Activity.RESULT_OK) {\n                    try {\n\n                        Uri uri = data.getData();\n                        MessageInfo messageInfo = new MessageInfo();\n                        messageInfo.setFilepath(getImageRealPathFromURI(uri));\n                        messageInfo.setFileType(Constants.CHAT_FILE_TYPE_IMAGE);\n                        EventBus.getDefault().post(messageInfo);\n                    } catch (Exception e) {\n                        e.printStackTrace();\n                        Log.d(Constants.TAG, e.getMessage());\n                    }\n                } else {\n                    Log.d(Constants.TAG, \"失败\");\n                }\n\n                break;\n            case REQUEST_CODE_PICK_FILE:\n                if (res == Activity.RESULT_OK) {\n                    try {\n                        Uri uri = data.getData();\n                        Log.e(TAG, \"onActivityResult: ->\" + uri.getPath());\n                        MessageInfo messageInfo = new MessageInfo();\n                        messageInfo.setFilepath(FileUtils.getFileAbsolutePath(mActivity, uri));\n                        messageInfo.setFileType(Constants.CHAT_FILE_TYPE_FILE);\n                        EventBus.getDefault().post(messageInfo);\n                    } catch (Exception e) {\n                        e.printStackTrace();\n                        Log.d(Constants.TAG, e.getMessage());\n                    }\n                } else {\n                    Log.d(Constants.TAG, \"失败\");\n                }\n                break;\n\n            default:\n                break;\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        switch (requestCode) {\n            case MY_PERMISSIONS_REQUEST_CALL_PHONE:\n                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n                    takePhoto();\n                } else {\n                    toastShow(\"请同意系统权限后继续\");\n                }\n                break;\n            case MY_PERMISSIONS_REQUEST_WRITE_STORAGE_CODE:\n                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n                    choosePhoto();\n                } else {\n                    toastShow(\"请同意系统权限后继续\");\n                }\n                break;\n            case MY_PERMISSIONS_REQUEST_CAMERACODE:\n                if (grantResults.length > 0 &&\n                        grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n                    imageUri = Uri.fromFile(fileUri);\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                        PhotoUtils.takePicture(this, imageUri, CODE_CROP_PHOTO);\n                    }\n                }\n                break;\n        }\n    }\n\n    public String getImageRealPathFromURI(Uri contentUri) {\n\n        //TODO upload file、image、voice then return url;\n        String res = null;\n        String[] proj = {MediaStore.Images.Media.DATA};\n        Cursor cursor = getActivity().getContentResolver().query(contentUri, proj, null, null, null);\n        if (cursor.moveToFirst()) {\n            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);\n            res = cursor.getString(column_index);\n        }\n        cursor.close();\n        return res;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/AudioRecorderUtils.java",
    "content": "package com.rance.chatui.util;\n\nimport android.content.Context;\nimport android.media.MediaRecorder;\nimport android.os.Environment;\nimport android.os.Handler;\nimport android.util.Log;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * 作者：Rance on 2016/11/29 10:47\n * 邮箱：rance935@163.com\n */\npublic class AudioRecorderUtils {\n\n    //文件路径\n    private String filePath;\n    //文件夹路径\n    private String FolderPath;\n\n    private MediaRecorder mMediaRecorder;\n    private final String TAG = \"fan\";\n    public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大录音时长1000*60*10;\n\n    private OnAudioStatusUpdateListener audioStatusUpdateListener;\n\n    /**\n     * 文件存储默认sdcard/cadyd/record\n     */\n    public AudioRecorderUtils() {\n\n        //默认保存路径为/sdcard/record/下\n        this(Environment.getExternalStorageDirectory() + \"/cadyd/record/\");\n    }\n\n    public AudioRecorderUtils(String filePath) {\n\n        File path = new File(filePath);\n        if (!path.exists())\n            path.mkdirs();\n\n        this.FolderPath = filePath;\n    }\n\n    private long startTime;\n    private long endTime;\n\n    /**\n     * 开始录音 使用amr格式\n     * 录音文件\n     *\n     * @return\n     */\n    public void startRecord(Context context) {\n        if (!CheckPermissionUtils.isHasPermission(context)) {\n            audioStatusUpdateListener.onError();\n            return;\n        }\n        // 开始录音\n        /* ①Initial：实例化MediaRecorder对象 */\n        if (mMediaRecorder == null)\n            mMediaRecorder = new MediaRecorder();\n        try {\n            /* ②setAudioSource/setVedioSource */\n            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风\n            /* ②设置音频文件的编码：AAC/AMR_NB/AMR_MB/Default 声音的（波形）的采样 */\n            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);\n            /*\n             * ②设置输出文件的格式：THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式\n             * ，H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)\n             */\n            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);\n\n            filePath = FolderPath + Utils.getCurrentTime() + \".amr\";\n            /* ③准备 */\n            mMediaRecorder.setOutputFile(filePath);\n            mMediaRecorder.setMaxDuration(MAX_LENGTH);\n            mMediaRecorder.prepare();\n            /* ④开始 */\n            mMediaRecorder.start();\n            // AudioRecord audioRecord.\n            /* 获取开始时间* */\n            startTime = System.currentTimeMillis();\n            updateMicStatus();\n            Log.e(\"fan\", \"startTime\" + startTime);\n        } catch (IllegalStateException e) {\n            audioStatusUpdateListener.onError();\n            Log.i(TAG, \"call startAmr(File mRecAudioFile) failed!\" + e.getMessage());\n        } catch (IOException e) {\n            audioStatusUpdateListener.onError();\n            Log.i(TAG, \"call startAmr(File mRecAudioFile) failed!\" + e.getMessage());\n        }\n    }\n\n    /**\n     * 停止录音\n     */\n    public long stopRecord() {\n        if (mMediaRecorder == null)\n            return 0L;\n        endTime = System.currentTimeMillis();\n        //设置后不会崩\n        mMediaRecorder.setOnErrorListener(null);\n        mMediaRecorder.setPreviewDisplay(null);\n        try {\n            mMediaRecorder.stop();\n        } catch (IllegalStateException e) {\n            Log.d(\"stopRecord\", e.getMessage());\n        } catch (RuntimeException e) {\n            Log.d(\"stopRecord\", e.getMessage());\n        } catch (Exception e) {\n            Log.d(\"stopRecord\", e.getMessage());\n        }\n        mMediaRecorder.reset();\n        mMediaRecorder.release();\n        mMediaRecorder = null;\n        long time = endTime - startTime;\n        audioStatusUpdateListener.onStop(time, filePath);\n        filePath = \"\";\n        return endTime - startTime;\n    }\n\n    /**\n     * 取消录音\n     */\n    public void cancelRecord() {\n        if (mMediaRecorder != null) {\n            mMediaRecorder.stop();\n            mMediaRecorder.reset();\n            mMediaRecorder.release();\n            mMediaRecorder = null;\n        }\n        File file = new File(filePath);\n        if (file.exists())\n            file.delete();\n        filePath = \"\";\n\n    }\n\n    private final Handler mHandler = new Handler();\n    private Runnable mUpdateMicStatusTimer = new Runnable() {\n        public void run() {\n            updateMicStatus();\n        }\n    };\n\n\n    private int BASE = 1;\n    private int SPACE = 100;// 间隔取样时间\n\n    public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {\n        this.audioStatusUpdateListener = audioStatusUpdateListener;\n    }\n\n    /**\n     * 更新麦克状态\n     */\n    private void updateMicStatus() {\n\n        if (mMediaRecorder != null) {\n            double ratio = (double) mMediaRecorder.getMaxAmplitude() / BASE;\n            double db = 0;// 分贝\n            if (ratio > 1) {\n                db = 20 * Math.log10(ratio);\n                if (null != audioStatusUpdateListener) {\n                    audioStatusUpdateListener.onUpdate(db, System.currentTimeMillis() - startTime);\n                }\n            }\n            mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);\n        }\n    }\n\n    public interface OnAudioStatusUpdateListener {\n        /**\n         * 录音中...\n         *\n         * @param db   当前声音分贝\n         * @param time 录音时长\n         */\n        public void onUpdate(double db, long time);\n\n        /**\n         * 停止录音\n         *\n         * @param time     录音时长\n         * @param filePath 保存路径\n         */\n        public void onStop(long time, String filePath);\n\n        /**\n         * 录音失败\n         */\n        public void onError();\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/CheckPermissionUtils.java",
    "content": "package com.rance.chatui.util;\n\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.media.AudioFormat;\nimport android.media.AudioRecord;\nimport android.media.MediaRecorder;\nimport android.provider.Settings;\nimport android.support.v7.app.AlertDialog;\n\n/**\n * 作者：Rance on 2016/12/14 11:09\n * 邮箱：rance935@163.com\n */\npublic class CheckPermissionUtils {\n    // 音频获取源\n    public static int audioSource = MediaRecorder.AudioSource.MIC;\n    // 设置音频采样率，44100是目前的标准，但是某些设备仍然支持22050，16000，11025\n    public static int sampleRateInHz = 44100;\n    // 设置音频的录制的声道CHANNEL_IN_STEREO为双声道，CHANNEL_CONFIGURATION_MONO为单声道\n    public static int channelConfig = AudioFormat.CHANNEL_IN_STEREO;\n    // 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。\n    public static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n    // 缓冲区字节大小\n    public static int bufferSizeInBytes = 0;\n\n    /**\n     * 判断是是否有录音权限\n     */\n    public static boolean isHasPermission(final Context context){\n        bufferSizeInBytes = 0;\n        bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,\n                channelConfig, audioFormat);\n        AudioRecord audioRecord =  new AudioRecord(audioSource, sampleRateInHz,\n                channelConfig, audioFormat, bufferSizeInBytes);\n        //开始录制音频\n        try{\n            // 防止某些手机崩溃，例如联想\n            audioRecord.startRecording();\n        }catch (IllegalStateException e){\n            e.printStackTrace();\n        }\n        /**\n         * 根据开始录音判断是否有录音权限\n         */\n        if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {\n            new AlertDialog.Builder(context)\n                    .setTitle(\"提示\")\n                    .setMessage(\"经检测语音权限未开启，设置方法：三方手机管理(应用宝、360)->安全->权限管理程序->应用程序->勾选语音权限。或去设置中心找到应用设置权限\")\n                    .setNegativeButton(\"取消\", new DialogInterface.OnClickListener() {\n                        @Override\n                        public void onClick(DialogInterface dialog, int which) {\n                            dialog.dismiss();\n                        }\n                    })\n                    .setPositiveButton(\"去设置\", new DialogInterface.OnClickListener() {\n                        @Override\n                        public void onClick(DialogInterface dialog, int which) {\n                            dialog.dismiss();\n                            context.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));\n                        }\n                    })\n                    .show();\n            return false;\n        }\n        audioRecord.stop();\n        audioRecord.release();\n        audioRecord = null;\n        return true;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/Constants.java",
    "content": "package com.rance.chatui.util;\n\n/**\n * 作者：Rance on 2016/12/20 16:51\n * 邮箱：rance935@163.com\n */\npublic class Constants {\n    public static final String TAG = \"rance\";\n    public static final String AUTHORITY = \"com.chatui.fileprovider\";\n    /** 0x001-接受消息  0x002-发送消息**/\n    public static final int CHAT_ITEM_TYPE_LEFT = 0x001;\n    public static final int CHAT_ITEM_TYPE_RIGHT = 0x002;\n    /** 0x003-发送中  0x004-发送失败  0x005-发送成功**/\n    public static final int CHAT_ITEM_SENDING = 0x003;\n    public static final int CHAT_ITEM_SEND_ERROR = 0x004;\n    public static final int CHAT_ITEM_SEND_SUCCESS = 0x005;\n\n    public static final String CHAT_FILE_TYPE_TEXT = \"text\";\n    public static final String CHAT_FILE_TYPE_FILE = \"file\";\n    public static final String CHAT_FILE_TYPE_IMAGE = \"image\";\n    public static final String CHAT_FILE_TYPE_VOICE = \"voice\";\n    public static final String CHAT_FILE_TYPE_CONTACT = \"contact\";\n    public static final String CHAT_FILE_TYPE_LINK = \"LINK\";\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/EmotionUtils.java",
    "content": "\npackage com.rance.chatui.util;\n\nimport com.rance.chatui.R;\n\nimport java.util.LinkedHashMap;\n\n/**\n * 作者：Rance on 2016/11/29 10:47\n * 邮箱：rance935@163.com\n * 表情加载类,可自己添加多种表情，分别建立不同的map存放和不同的标志符即可\n */\npublic class EmotionUtils {\n\n    /**\n     * key-表情文字;\n     * value-表情图片资源\n     */\n    public static LinkedHashMap<String, Integer> EMPTY_GIF_MAP;\n    public static LinkedHashMap<String, Integer> EMOTION_STATIC_MAP;\n\n\n    static {\n        EMPTY_GIF_MAP = new LinkedHashMap<>();\n\n        EMPTY_GIF_MAP.put(\"[微笑]\", R.drawable.emotion_weixiao_gif);\n        EMPTY_GIF_MAP.put(\"[撇嘴]\", R.drawable.emotion_biezui_gif);\n        EMPTY_GIF_MAP.put(\"[色]\", R.drawable.emotion_se_gif);\n        EMPTY_GIF_MAP.put(\"[发呆]\", R.drawable.emotion_fadai_gif);\n        EMPTY_GIF_MAP.put(\"[得意]\", R.drawable.emotion_deyi_gif);\n        EMPTY_GIF_MAP.put(\"[流泪]\", R.drawable.emotion_liulei_gif);\n        EMPTY_GIF_MAP.put(\"[害羞]\", R.drawable.emotion_haixiu_gif);\n        EMPTY_GIF_MAP.put(\"[闭嘴]\", R.drawable.emotion_bizui_gif);\n        EMPTY_GIF_MAP.put(\"[睡]\", R.drawable.emotion_shui_gif);\n        EMPTY_GIF_MAP.put(\"[大哭]\", R.drawable.emotion_daku_gif);\n        EMPTY_GIF_MAP.put(\"[尴尬]\", R.drawable.emotion_ganga_gif);\n        EMPTY_GIF_MAP.put(\"[发怒]\", R.drawable.emotion_fanu_gif);\n        EMPTY_GIF_MAP.put(\"[调皮]\", R.drawable.emotion_tiaopi_gif);\n        EMPTY_GIF_MAP.put(\"[呲牙]\", R.drawable.emotion_ciya_gif);\n        EMPTY_GIF_MAP.put(\"[惊讶]\", R.drawable.emotion_jingya_gif);\n        EMPTY_GIF_MAP.put(\"[难过]\", R.drawable.emotion_nanguo_gif);\n        EMPTY_GIF_MAP.put(\"[酷]\", R.drawable.emotion_ku_gif);\n        EMPTY_GIF_MAP.put(\"[冷汗]\", R.drawable.emotion_lenghan_gif);\n        EMPTY_GIF_MAP.put(\"[抓狂]\", R.drawable.emotion_zhuakuang_gif);\n        EMPTY_GIF_MAP.put(\"[吐]\", R.drawable.emotion_tu_gif);\n        EMPTY_GIF_MAP.put(\"[偷笑]\", R.drawable.emotion_touxiao_gif);\n        EMPTY_GIF_MAP.put(\"[可爱]\", R.drawable.emotion_keai_gif);\n        EMPTY_GIF_MAP.put(\"[白眼]\", R.drawable.emotion_baiyan_gif);\n        EMPTY_GIF_MAP.put(\"[傲慢]\", R.drawable.emotion_aoman_gif);\n        EMPTY_GIF_MAP.put(\"[饥饿]\", R.drawable.emotion_jie_gif);\n        EMPTY_GIF_MAP.put(\"[困]\", R.drawable.emotion_kun_gif);\n        EMPTY_GIF_MAP.put(\"[惊恐]\", R.drawable.emotion_jingkong_gif);\n        EMPTY_GIF_MAP.put(\"[流汗]\", R.drawable.emotion_liuhan_gif);\n        EMPTY_GIF_MAP.put(\"[憨笑]\", R.drawable.emotion_hanxiao_gif);\n        EMPTY_GIF_MAP.put(\"[大兵]\", R.drawable.emotion_dabing_gif);\n        EMPTY_GIF_MAP.put(\"[奋斗]\", R.drawable.emotion_fendou_gif);\n        EMPTY_GIF_MAP.put(\"[咒骂]\", R.drawable.emotion_zouma_gif);\n        EMPTY_GIF_MAP.put(\"[疑问]\", R.drawable.emotion_yiwen_gif);\n        EMPTY_GIF_MAP.put(\"[嘘]\", R.drawable.emotion_xu_gif);\n        EMPTY_GIF_MAP.put(\"[晕]\", R.drawable.emotion_yun_gif);\n        EMPTY_GIF_MAP.put(\"[折磨]\", R.drawable.emotion_fakuang_gif);\n        EMPTY_GIF_MAP.put(\"[衰]\", R.drawable.emotion_shuai_gif);\n        EMPTY_GIF_MAP.put(\"[骷髅]\", R.drawable.emotion_kulou_gif);\n        EMPTY_GIF_MAP.put(\"[敲打]\", R.drawable.emotion_qiaoda_gif);\n        EMPTY_GIF_MAP.put(\"[再见]\", R.drawable.emotion_zaijian_gif);\n        EMPTY_GIF_MAP.put(\"[擦汗]\", R.drawable.emotion_cahan_gif);\n        EMPTY_GIF_MAP.put(\"[抠鼻]\", R.drawable.emotion_koubi_gif);\n        EMPTY_GIF_MAP.put(\"[鼓掌]\", R.drawable.emotion_guzhang_gif);\n        EMPTY_GIF_MAP.put(\"[糗大了]\", R.drawable.emotion_qiudale_gif);\n        EMPTY_GIF_MAP.put(\"[坏笑]\", R.drawable.emotion_huaixiao_gif);\n        EMPTY_GIF_MAP.put(\"[左哼哼]\", R.drawable.emotion_zuohengheng_gif);\n        EMPTY_GIF_MAP.put(\"[右哼哼]\", R.drawable.emotion_youhengheng_gif);\n        EMPTY_GIF_MAP.put(\"[哈欠]\", R.drawable.emotion_haqian_gif);\n        EMPTY_GIF_MAP.put(\"[鄙视]\", R.drawable.emotion_bishi_gif);\n        EMPTY_GIF_MAP.put(\"[委屈]\", R.drawable.emotion_weiqu_gif);\n        EMPTY_GIF_MAP.put(\"[快哭了]\", R.drawable.emotion_kuaikule_gif);\n        EMPTY_GIF_MAP.put(\"[阴险]\", R.drawable.emotion_yingxian_gif);\n        EMPTY_GIF_MAP.put(\"[亲亲]\", R.drawable.emotion_qinqin_gif);\n        EMPTY_GIF_MAP.put(\"[吓]\", R.drawable.emotion_xia_gif);\n        EMPTY_GIF_MAP.put(\"[可怜]\", R.drawable.emotion_kelian_gif);\n        EMPTY_GIF_MAP.put(\"[菜刀]\", R.drawable.emotion_caidao_gif);\n        EMPTY_GIF_MAP.put(\"[西瓜]\", R.drawable.emotion_xigua_gif);\n        EMPTY_GIF_MAP.put(\"[啤酒]\", R.drawable.emotion_pijiu_gif);\n        EMPTY_GIF_MAP.put(\"[篮球]\", R.drawable.emotion_lanqiu_gif);\n        EMPTY_GIF_MAP.put(\"[乒乓]\", R.drawable.emotion_pingpang_gif);\n        EMPTY_GIF_MAP.put(\"[咖啡]\", R.drawable.emotion_kafei_gif);\n        EMPTY_GIF_MAP.put(\"[饭]\", R.drawable.emotion_fan_gif);\n        EMPTY_GIF_MAP.put(\"[猪头]\", R.drawable.emotion_zhutou_gif);\n        EMPTY_GIF_MAP.put(\"[玫瑰]\", R.drawable.emotion_meigui_gif);\n        EMPTY_GIF_MAP.put(\"[凋谢]\", R.drawable.emotion_diaoxie_gif);\n        EMPTY_GIF_MAP.put(\"[示爱]\", R.drawable.emotion_shiai_gif);\n        EMPTY_GIF_MAP.put(\"[爱心]\", R.drawable.emotion_aixin_gif);\n        EMPTY_GIF_MAP.put(\"[心碎]\", R.drawable.emotion_xinsui_gif);\n        EMPTY_GIF_MAP.put(\"[蛋糕]\", R.drawable.emotion_dangao_gif);\n        EMPTY_GIF_MAP.put(\"[闪电]\", R.drawable.emotion_shandian_gif);\n        EMPTY_GIF_MAP.put(\"[炸弹]\", R.drawable.emotion_zhadan_gif);\n        EMPTY_GIF_MAP.put(\"[刀]\", R.drawable.emotion_dao_gif);\n        EMPTY_GIF_MAP.put(\"[足球]\", R.drawable.emotion_zhuqiu_gif);\n        EMPTY_GIF_MAP.put(\"[瓢虫]\", R.drawable.emotion_pachong_gif);\n        EMPTY_GIF_MAP.put(\"[便便]\", R.drawable.emotion_bianbian_gif);\n        EMPTY_GIF_MAP.put(\"[月亮]\", R.drawable.emotion_yueliang_gif);\n        EMPTY_GIF_MAP.put(\"[太阳]\", R.drawable.emotion_taiyang_gif);\n        EMPTY_GIF_MAP.put(\"[礼物]\", R.drawable.emotion_liwu_gif);\n        EMPTY_GIF_MAP.put(\"[拥抱]\", R.drawable.emotion_baobao_gif);\n        EMPTY_GIF_MAP.put(\"[强]\", R.drawable.emotion_qiang_gif);\n        EMPTY_GIF_MAP.put(\"[弱]\", R.drawable.emotion_ruo_gif);\n        EMPTY_GIF_MAP.put(\"[握手]\", R.drawable.emotion_woshou_gif);\n        EMPTY_GIF_MAP.put(\"[胜利]\", R.drawable.emotion_shengli_gif);\n        EMPTY_GIF_MAP.put(\"[抱拳]\", R.drawable.emotion_baoquan_gif);\n        EMPTY_GIF_MAP.put(\"[勾引]\", R.drawable.emotion_gouying_gif);\n        EMPTY_GIF_MAP.put(\"[拳头]\", R.drawable.emotion_quantou_gif);\n        EMPTY_GIF_MAP.put(\"[差劲]\", R.drawable.emotion_chajing_gif);\n        EMPTY_GIF_MAP.put(\"[爱你]\", R.drawable.emotion_aini_gif);\n        EMPTY_GIF_MAP.put(\"[NO]\", R.drawable.emotion_no_gif);\n        EMPTY_GIF_MAP.put(\"[OK]\", R.drawable.emotion_ok_gif);\n        EMPTY_GIF_MAP.put(\"[爱情]\", R.drawable.emotion_aiqing_gif);\n        EMPTY_GIF_MAP.put(\"[飞吻]\", R.drawable.emotion_feiwen_gif);\n        EMPTY_GIF_MAP.put(\"[跳跳]\", R.drawable.emotion_tiaotiao_gif);\n        EMPTY_GIF_MAP.put(\"[发抖]\", R.drawable.emotion_fadou_gif);\n        EMPTY_GIF_MAP.put(\"[怄火]\", R.drawable.emotion_ouhuo_gif);\n        EMPTY_GIF_MAP.put(\"[转圈]\", R.drawable.emotion_zhuanquan_gif);\n        EMPTY_GIF_MAP.put(\"[磕头]\", R.drawable.emotion_ketou_gif);\n        EMPTY_GIF_MAP.put(\"[回头]\", R.drawable.emotion_huitou_gif);\n        EMPTY_GIF_MAP.put(\"[跳绳]\", R.drawable.emotion_tiaosheng_gif);\n        EMPTY_GIF_MAP.put(\"[挥手]\", R.drawable.emotion_huishou_gif);\n        EMPTY_GIF_MAP.put(\"[激动]\", R.drawable.emotion_jidong_gif);\n        EMPTY_GIF_MAP.put(\"[街舞]\", R.drawable.emotion_jiewu_gif);\n        EMPTY_GIF_MAP.put(\"[献吻]\", R.drawable.emotion_xianwen_gif);\n        EMPTY_GIF_MAP.put(\"[左太极]\", R.drawable.emotion_zuotaiji_gif);\n        EMPTY_GIF_MAP.put(\"[右太极]\", R.drawable.emotion_youtaiji_gif);\n        EMPTY_GIF_MAP.put(\"[双喜]\", R.drawable.emotion_shuangxi_gif);\n        EMPTY_GIF_MAP.put(\"[鞭炮]\", R.drawable.emotion_bianpao_gif);\n        EMPTY_GIF_MAP.put(\"[灯笼]\", R.drawable.emotion_denglong_gif);\n        EMPTY_GIF_MAP.put(\"[发财]\", R.drawable.emotion_facai_gif);\n        EMPTY_GIF_MAP.put(\"[K歌]\", R.drawable.emotion_kge_gif);\n        EMPTY_GIF_MAP.put(\"[购物]\", R.drawable.emotion_gouwu_gif);\n        EMPTY_GIF_MAP.put(\"[邮件]\", R.drawable.emotion_youjian_gif);\n        EMPTY_GIF_MAP.put(\"[帅]\", R.drawable.emotion_dashuai_gif);\n        EMPTY_GIF_MAP.put(\"[喝彩]\", R.drawable.emotion_hecai_gif);\n        EMPTY_GIF_MAP.put(\"[祈祷]\", R.drawable.emotion_qidao_gif);\n        EMPTY_GIF_MAP.put(\"[爆筋]\", R.drawable.emotion_baojing_gif);\n        EMPTY_GIF_MAP.put(\"[棒棒糖]\", R.drawable.emotion_bangbangtang_gif);\n        EMPTY_GIF_MAP.put(\"[喝奶]\", R.drawable.emotion_henai_gif);\n        EMPTY_GIF_MAP.put(\"[下面]\", R.drawable.emotion_xiamian_gif);\n        EMPTY_GIF_MAP.put(\"[香蕉]\", R.drawable.emotion_xiangjiao_gif);\n        EMPTY_GIF_MAP.put(\"[飞机]\", R.drawable.emotion_feiji_gif);\n        EMPTY_GIF_MAP.put(\"[开车]\", R.drawable.emotion_kaiche_gif);\n        EMPTY_GIF_MAP.put(\"[左车头]\", R.drawable.emotion_zuochetou_gif);\n        EMPTY_GIF_MAP.put(\"[车厢]\", R.drawable.emotion_chexiang_gif);\n        EMPTY_GIF_MAP.put(\"[右车头]\", R.drawable.emotion_youchexiang_gif);\n        EMPTY_GIF_MAP.put(\"[多云]\", R.drawable.emotion_duoyun_gif);\n        EMPTY_GIF_MAP.put(\"[下雨]\", R.drawable.emotion_xiayu_gif);\n        EMPTY_GIF_MAP.put(\"[钞票]\", R.drawable.emotion_chaopiao_gif);\n        EMPTY_GIF_MAP.put(\"[熊猫]\", R.drawable.emotion_xiongmao_gif);\n        EMPTY_GIF_MAP.put(\"[灯泡]\", R.drawable.emotion_dengpao_gif);\n        EMPTY_GIF_MAP.put(\"[风车]\", R.drawable.emotion_fengche_gif);\n        EMPTY_GIF_MAP.put(\"[闹钟]\", R.drawable.emotion_naozhong_gif);\n        EMPTY_GIF_MAP.put(\"[打伞]\", R.drawable.emotion_dashan_gif);\n        EMPTY_GIF_MAP.put(\"[彩球]\", R.drawable.emotion_caiqiu_gif);\n        EMPTY_GIF_MAP.put(\"[钻戒]\", R.drawable.emotion_zhuanjie_gif);\n        EMPTY_GIF_MAP.put(\"[沙发]\", R.drawable.emotion_shafa_gif);\n        EMPTY_GIF_MAP.put(\"[纸巾]\", R.drawable.emotion_zhijing_gif);\n        EMPTY_GIF_MAP.put(\"[药]\", R.drawable.emotion_yao_gif);\n        EMPTY_GIF_MAP.put(\"[手枪]\", R.drawable.emotion_shouqiang_gif);\n        EMPTY_GIF_MAP.put(\"[青蛙]\", R.drawable.emotion_qingwa_gif);\n\n        EMOTION_STATIC_MAP = new LinkedHashMap<>();\n\n        EMOTION_STATIC_MAP.put(\"[微笑]\", R.drawable.emotion_weixiao);\n        EMOTION_STATIC_MAP.put(\"[撇嘴]\", R.drawable.emotion_biezui);\n        EMOTION_STATIC_MAP.put(\"[色]\", R.drawable.emotion_se);\n        EMOTION_STATIC_MAP.put(\"[发呆]\", R.drawable.emotion_fadai);\n        EMOTION_STATIC_MAP.put(\"[得意]\", R.drawable.emotion_deyi);\n        EMOTION_STATIC_MAP.put(\"[流泪]\", R.drawable.emotion_liulei);\n        EMOTION_STATIC_MAP.put(\"[害羞]\", R.drawable.emotion_haixiu);\n        EMOTION_STATIC_MAP.put(\"[闭嘴]\", R.drawable.emotion_bizui);\n        EMOTION_STATIC_MAP.put(\"[睡]\", R.drawable.emotion_shui);\n        EMOTION_STATIC_MAP.put(\"[大哭]\", R.drawable.emotion_daku);\n        EMOTION_STATIC_MAP.put(\"[尴尬]\", R.drawable.emotion_ganga);\n        EMOTION_STATIC_MAP.put(\"[发怒]\", R.drawable.emotion_fanu);\n        EMOTION_STATIC_MAP.put(\"[调皮]\", R.drawable.emotion_tiaopi);\n        EMOTION_STATIC_MAP.put(\"[呲牙]\", R.drawable.emotion_ciya);\n        EMOTION_STATIC_MAP.put(\"[惊讶]\", R.drawable.emotion_jingya);\n        EMOTION_STATIC_MAP.put(\"[难过]\", R.drawable.emotion_nanguo);\n        EMOTION_STATIC_MAP.put(\"[酷]\", R.drawable.emotion_ku);\n        EMOTION_STATIC_MAP.put(\"[冷汗]\", R.drawable.emotion_lenghan);\n        EMOTION_STATIC_MAP.put(\"[抓狂]\", R.drawable.emotion_zhuakuang);\n        EMOTION_STATIC_MAP.put(\"[吐]\", R.drawable.emotion_tu);\n        EMOTION_STATIC_MAP.put(\"[偷笑]\", R.drawable.emotion_touxiao);\n        EMOTION_STATIC_MAP.put(\"[可爱]\", R.drawable.emotion_keai);\n        EMOTION_STATIC_MAP.put(\"[白眼]\", R.drawable.emotion_baiyan);\n        EMOTION_STATIC_MAP.put(\"[傲慢]\", R.drawable.emotion_aoman);\n        EMOTION_STATIC_MAP.put(\"[饥饿]\", R.drawable.emotion_jie);\n        EMOTION_STATIC_MAP.put(\"[困]\", R.drawable.emotion_kun);\n        EMOTION_STATIC_MAP.put(\"[惊恐]\", R.drawable.emotion_jingkong);\n        EMOTION_STATIC_MAP.put(\"[流汗]\", R.drawable.emotion_liuhan);\n        EMOTION_STATIC_MAP.put(\"[憨笑]\", R.drawable.emotion_hanxiao);\n        EMOTION_STATIC_MAP.put(\"[大兵]\", R.drawable.emotion_dabing);\n        EMOTION_STATIC_MAP.put(\"[奋斗]\", R.drawable.emotion_fendou);\n        EMOTION_STATIC_MAP.put(\"[咒骂]\", R.drawable.emotion_zouma);\n        EMOTION_STATIC_MAP.put(\"[疑问]\", R.drawable.emotion_yiwen);\n        EMOTION_STATIC_MAP.put(\"[嘘]\", R.drawable.emotion_xu);\n        EMOTION_STATIC_MAP.put(\"[晕]\", R.drawable.emotion_yun);\n        EMOTION_STATIC_MAP.put(\"[折磨]\", R.drawable.emotion_fakuang);\n        EMOTION_STATIC_MAP.put(\"[衰]\", R.drawable.emotion_shuai);\n        EMOTION_STATIC_MAP.put(\"[骷髅]\", R.drawable.emotion_kulou);\n        EMOTION_STATIC_MAP.put(\"[敲打]\", R.drawable.emotion_qiaoda);\n        EMOTION_STATIC_MAP.put(\"[再见]\", R.drawable.emotion_zaijian);\n        EMOTION_STATIC_MAP.put(\"[擦汗]\", R.drawable.emotion_cahan);\n        EMOTION_STATIC_MAP.put(\"[抠鼻]\", R.drawable.emotion_koubi);\n        EMOTION_STATIC_MAP.put(\"[鼓掌]\", R.drawable.emotion_guzhang);\n        EMOTION_STATIC_MAP.put(\"[糗大了]\", R.drawable.emotion_qiudale);\n        EMOTION_STATIC_MAP.put(\"[坏笑]\", R.drawable.emotion_huaixiao);\n        EMOTION_STATIC_MAP.put(\"[左哼哼]\", R.drawable.emotion_zuohengheng);\n        EMOTION_STATIC_MAP.put(\"[右哼哼]\", R.drawable.emotion_youhengheng);\n        EMOTION_STATIC_MAP.put(\"[哈欠]\", R.drawable.emotion_haqian);\n        EMOTION_STATIC_MAP.put(\"[鄙视]\", R.drawable.emotion_bishi);\n        EMOTION_STATIC_MAP.put(\"[委屈]\", R.drawable.emotion_weiqu);\n        EMOTION_STATIC_MAP.put(\"[快哭了]\", R.drawable.emotion_kuaikule);\n        EMOTION_STATIC_MAP.put(\"[阴险]\", R.drawable.emotion_yingxian);\n        EMOTION_STATIC_MAP.put(\"[亲亲]\", R.drawable.emotion_qinqin);\n        EMOTION_STATIC_MAP.put(\"[吓]\", R.drawable.emotion_xia);\n        EMOTION_STATIC_MAP.put(\"[可怜]\", R.drawable.emotion_kelian);\n        EMOTION_STATIC_MAP.put(\"[菜刀]\", R.drawable.emotion_caidao);\n        EMOTION_STATIC_MAP.put(\"[西瓜]\", R.drawable.emotion_xigua);\n        EMOTION_STATIC_MAP.put(\"[啤酒]\", R.drawable.emotion_pijiu);\n        EMOTION_STATIC_MAP.put(\"[篮球]\", R.drawable.emotion_lanqiu);\n        EMOTION_STATIC_MAP.put(\"[乒乓]\", R.drawable.emotion_pingpang);\n        EMOTION_STATIC_MAP.put(\"[咖啡]\", R.drawable.emotion_kafei);\n        EMOTION_STATIC_MAP.put(\"[饭]\", R.drawable.emotion_fan);\n        EMOTION_STATIC_MAP.put(\"[猪头]\", R.drawable.emotion_zhutou);\n        EMOTION_STATIC_MAP.put(\"[玫瑰]\", R.drawable.emotion_meigui);\n        EMOTION_STATIC_MAP.put(\"[凋谢]\", R.drawable.emotion_diaoxie);\n        EMOTION_STATIC_MAP.put(\"[示爱]\", R.drawable.emotion_shiai);\n        EMOTION_STATIC_MAP.put(\"[爱心]\", R.drawable.emotion_aixin);\n        EMOTION_STATIC_MAP.put(\"[心碎]\", R.drawable.emotion_xinsui);\n        EMOTION_STATIC_MAP.put(\"[蛋糕]\", R.drawable.emotion_dangao);\n        EMOTION_STATIC_MAP.put(\"[闪电]\", R.drawable.emotion_shandian);\n        EMOTION_STATIC_MAP.put(\"[炸弹]\", R.drawable.emotion_zhadan);\n        EMOTION_STATIC_MAP.put(\"[刀]\", R.drawable.emotion_dao);\n        EMOTION_STATIC_MAP.put(\"[足球]\", R.drawable.emotion_zhuqiu);\n        EMOTION_STATIC_MAP.put(\"[瓢虫]\", R.drawable.emotion_pachong);\n        EMOTION_STATIC_MAP.put(\"[便便]\", R.drawable.emotion_bianbian);\n        EMOTION_STATIC_MAP.put(\"[月亮]\", R.drawable.emotion_yueliang);\n        EMOTION_STATIC_MAP.put(\"[太阳]\", R.drawable.emotion_taiyang);\n        EMOTION_STATIC_MAP.put(\"[礼物]\", R.drawable.emotion_liwu);\n        EMOTION_STATIC_MAP.put(\"[拥抱]\", R.drawable.emotion_baobao);\n        EMOTION_STATIC_MAP.put(\"[强]\", R.drawable.emotion_qiang);\n        EMOTION_STATIC_MAP.put(\"[弱]\", R.drawable.emotion_ruo);\n        EMOTION_STATIC_MAP.put(\"[握手]\", R.drawable.emotion_woshou);\n        EMOTION_STATIC_MAP.put(\"[胜利]\", R.drawable.emotion_shengli);\n        EMOTION_STATIC_MAP.put(\"[抱拳]\", R.drawable.emotion_baoquan);\n        EMOTION_STATIC_MAP.put(\"[勾引]\", R.drawable.emotion_gouying);\n        EMOTION_STATIC_MAP.put(\"[拳头]\", R.drawable.emotion_quantou);\n        EMOTION_STATIC_MAP.put(\"[差劲]\", R.drawable.emotion_chajing);\n        EMOTION_STATIC_MAP.put(\"[爱你]\", R.drawable.emotion_aini);\n        EMOTION_STATIC_MAP.put(\"[NO]\", R.drawable.emotion_no);\n        EMOTION_STATIC_MAP.put(\"[OK]\", R.drawable.emotion_ok);\n        EMOTION_STATIC_MAP.put(\"[爱情]\", R.drawable.emotion_aiqing);\n        EMOTION_STATIC_MAP.put(\"[飞吻]\", R.drawable.emotion_feiwen);\n        EMOTION_STATIC_MAP.put(\"[跳跳]\", R.drawable.emotion_tiaotiao);\n        EMOTION_STATIC_MAP.put(\"[发抖]\", R.drawable.emotion_fadou);\n        EMOTION_STATIC_MAP.put(\"[怄火]\", R.drawable.emotion_ouhuo);\n        EMOTION_STATIC_MAP.put(\"[转圈]\", R.drawable.emotion_zhuanquan);\n        EMOTION_STATIC_MAP.put(\"[磕头]\", R.drawable.emotion_ketou);\n        EMOTION_STATIC_MAP.put(\"[回头]\", R.drawable.emotion_huitou);\n        EMOTION_STATIC_MAP.put(\"[跳绳]\", R.drawable.emotion_tiaosheng);\n        EMOTION_STATIC_MAP.put(\"[挥手]\", R.drawable.emotion_huishou);\n        EMOTION_STATIC_MAP.put(\"[激动]\", R.drawable.emotion_jidong);\n        EMOTION_STATIC_MAP.put(\"[街舞]\", R.drawable.emotion_jiewu);\n        EMOTION_STATIC_MAP.put(\"[献吻]\", R.drawable.emotion_xianwen);\n        EMOTION_STATIC_MAP.put(\"[左太极]\", R.drawable.emotion_zuotaiji);\n        EMOTION_STATIC_MAP.put(\"[右太极]\", R.drawable.emotion_youtaiji);\n        EMOTION_STATIC_MAP.put(\"[双喜]\", R.drawable.emotion_shuangxi);\n        EMOTION_STATIC_MAP.put(\"[鞭炮]\", R.drawable.emotion_bianpao);\n        EMOTION_STATIC_MAP.put(\"[灯笼]\", R.drawable.emotion_denglong);\n        EMOTION_STATIC_MAP.put(\"[发财]\", R.drawable.emotion_facai);\n        EMOTION_STATIC_MAP.put(\"[K歌]\", R.drawable.emotion_kge);\n        EMOTION_STATIC_MAP.put(\"[购物]\", R.drawable.emotion_gouwu);\n        EMOTION_STATIC_MAP.put(\"[邮件]\", R.drawable.emotion_youjian);\n        EMOTION_STATIC_MAP.put(\"[帅]\", R.drawable.emotion_dashuai);\n        EMOTION_STATIC_MAP.put(\"[喝彩]\", R.drawable.emotion_hecai);\n        EMOTION_STATIC_MAP.put(\"[祈祷]\", R.drawable.emotion_qidao);\n        EMOTION_STATIC_MAP.put(\"[爆筋]\", R.drawable.emotion_baojing);\n        EMOTION_STATIC_MAP.put(\"[棒棒糖]\", R.drawable.emotion_bangbangtang);\n        EMOTION_STATIC_MAP.put(\"[喝奶]\", R.drawable.emotion_henai);\n        EMOTION_STATIC_MAP.put(\"[下面]\", R.drawable.emotion_xiamian);\n        EMOTION_STATIC_MAP.put(\"[香蕉]\", R.drawable.emotion_xiangjiao);\n        EMOTION_STATIC_MAP.put(\"[飞机]\", R.drawable.emotion_feiji);\n        EMOTION_STATIC_MAP.put(\"[开车]\", R.drawable.emotion_kaiche);\n        EMOTION_STATIC_MAP.put(\"[左车头]\", R.drawable.emotion_zuochetou);\n        EMOTION_STATIC_MAP.put(\"[车厢]\", R.drawable.emotion_chexiang);\n        EMOTION_STATIC_MAP.put(\"[右车头]\", R.drawable.emotion_youchexiang);\n        EMOTION_STATIC_MAP.put(\"[多云]\", R.drawable.emotion_duoyun);\n        EMOTION_STATIC_MAP.put(\"[下雨]\", R.drawable.emotion_xiayu);\n        EMOTION_STATIC_MAP.put(\"[钞票]\", R.drawable.emotion_chaopiao);\n        EMOTION_STATIC_MAP.put(\"[熊猫]\", R.drawable.emotion_xiongmao);\n        EMOTION_STATIC_MAP.put(\"[灯泡]\", R.drawable.emotion_dengpao);\n        EMOTION_STATIC_MAP.put(\"[风车]\", R.drawable.emotion_fengche);\n        EMOTION_STATIC_MAP.put(\"[闹钟]\", R.drawable.emotion_naozhong);\n        EMOTION_STATIC_MAP.put(\"[打伞]\", R.drawable.emotion_dashan);\n        EMOTION_STATIC_MAP.put(\"[彩球]\", R.drawable.emotion_caiqiu);\n        EMOTION_STATIC_MAP.put(\"[钻戒]\", R.drawable.emotion_zhuanjie);\n        EMOTION_STATIC_MAP.put(\"[沙发]\", R.drawable.emotion_shafa);\n        EMOTION_STATIC_MAP.put(\"[纸巾]\", R.drawable.emotion_zhijing);\n        EMOTION_STATIC_MAP.put(\"[药]\", R.drawable.emotion_yao);\n        EMOTION_STATIC_MAP.put(\"[手枪]\", R.drawable.emotion_shouqiang);\n        EMOTION_STATIC_MAP.put(\"[青蛙]\", R.drawable.emotion_qingwa);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/FileUtils.java",
    "content": "package com.rance.chatui.util;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.ContentUris;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Environment;\nimport android.provider.DocumentsContract;\nimport android.provider.MediaStore;\nimport android.util.Log;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.text.DecimalFormat;\n\n/**\n * Created by chengz\n *\n * @date 2017/8/2.\n */\n\npublic class FileUtils {\n    /**\n     * 根据Uri获取文件的绝对路径，解决Android4.4以上版本Uri转换\n     *\n     * @param context\n     * @param fileUri\n     */\n    @TargetApi(19)\n    public static String getFileAbsolutePath(Activity context, Uri fileUri) {\n        if (context == null || fileUri == null)\n            return null;\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, fileUri)) {\n            if (isExternalStorageDocument(fileUri)) {\n                String docId = DocumentsContract.getDocumentId(fileUri);\n                String[] split = docId.split(\":\");\n                String type = split[0];\n                if (\"primary\".equalsIgnoreCase(type)) {\n                    return Environment.getExternalStorageDirectory() + \"/\" + split[1];\n                }\n            } else if (isDownloadsDocument(fileUri)) {\n                String id = DocumentsContract.getDocumentId(fileUri);\n                Uri contentUri = ContentUris.withAppendedId(Uri.parse(\"content://downloads/public_downloads\"), Long.valueOf(id));\n                return getDataColumn(context, contentUri, null, null);\n            } else if (isMediaDocument(fileUri)) {\n                String docId = DocumentsContract.getDocumentId(fileUri);\n                String[] split = docId.split(\":\");\n                String type = split[0];\n                Uri contentUri = null;\n                if (\"image\".equals(type)) {\n                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;\n                } else if (\"video\".equals(type)) {\n                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;\n                } else if (\"audio\".equals(type)) {\n                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;\n                }\n                String selection = MediaStore.Images.Media._ID + \"=?\";\n                String[] selectionArgs = new String[] { split[1] };\n                return getDataColumn(context, contentUri, selection, selectionArgs);\n            }\n        } // MediaStore (and general)\n        else if (\"content\".equalsIgnoreCase(fileUri.getScheme())) {\n            // Return the remote address\n            if (isGooglePhotosUri(fileUri))\n                return fileUri.getLastPathSegment();\n            return getDataColumn(context, fileUri, null, null);\n        }\n        // File\n        else if (\"file\".equalsIgnoreCase(fileUri.getScheme())) {\n            return fileUri.getPath();\n        }\n        return null;\n    }\n\n    public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {\n        Cursor cursor = null;\n        String[] projection = { MediaStore.Images.Media.DATA };\n        try {\n            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);\n            if (cursor != null && cursor.moveToFirst()) {\n                int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);\n                return cursor.getString(index);\n            }\n        } finally {\n            if (cursor != null)\n                cursor.close();\n        }\n        return null;\n    }\n\n    /**\n     * @param uri\n     *            The Uri to check.\n     * @return Whether the Uri authority is ExternalStorageProvider.\n     */\n    public static boolean isExternalStorageDocument(Uri uri) {\n        return \"com.android.externalstorage.documents\".equals(uri.getAuthority());\n    }\n\n    /**\n     * @param uri\n     *            The Uri to check.\n     * @return Whether the Uri authority is DownloadsProvider.\n     */\n    public static boolean isDownloadsDocument(Uri uri) {\n        return \"com.android.providers.downloads.documents\".equals(uri.getAuthority());\n    }\n\n    /**\n     * @param uri\n     *            The Uri to check.\n     * @return Whether the Uri authority is MediaProvider.\n     */\n    public static boolean isMediaDocument(Uri uri) {\n        return \"com.android.providers.media.documents\".equals(uri.getAuthority());\n    }\n\n    /**\n     * @param uri\n     *            The Uri to check.\n     * @return Whether the Uri authority is Google Photos.\n     */\n    public static boolean isGooglePhotosUri(Uri uri) {\n        return \"com.google.android.apps.photos.content\".equals(uri.getAuthority());\n    }\n\n    public static String getFileSize(String filePath) throws Exception {\n        File file = new File(filePath);\n        return getFileSize(file);\n    }\n\n    public static String getFileSize(File file) throws Exception {\n        long size = 0;\n        if (file.exists()) {\n            FileInputStream fis = null;\n            fis = new FileInputStream(file);\n            size = fis.available();\n        } else {\n            file.createNewFile();\n            Log.e(\"获取文件大小\", \"文件不存在!\");\n        }\n        return formatFileSize(size);\n    }\n\n    public static String getExtensionName(String filePath) throws Exception {\n        File file = new File(filePath);\n        return getExtensionName(file);\n    }\n\n    public static String getExtensionName(File file) {\n        String fileName= file.getName();\n        return fileName.substring(fileName.lastIndexOf(\".\")+1);\n    }\n\n    public static String getFileName(String filePath) {\n        File file = new File(filePath);\n        return file.getName();\n    }\n\n    private static String formatFileSize(long fileS)\n    {\n        DecimalFormat df = new DecimalFormat(\"#.00\");\n        String fileSizeString = \"\";\n        String wrongSize=\"0B\";\n        if(fileS==0){\n            return wrongSize;\n        }\n        if (fileS < 1024){\n            fileSizeString = df.format((double) fileS) + \"B\";\n        }\n        else if (fileS < 1048576){\n            fileSizeString = df.format((double) fileS / 1024) + \"KB\";\n        }\n        else if (fileS < 1073741824){\n            fileSizeString = df.format((double) fileS / 1048576) + \"MB\";\n        }\n        else{\n            fileSizeString = df.format((double) fileS / 1073741824) + \"GB\";\n        }\n        return fileSizeString;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/GifOpenHelper.java",
    "content": "package com.rance.chatui.util;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Bitmap.Config;\n\nimport java.io.InputStream;\nimport java.util.Vector;\n\n//Handler for read & extract Bitmap from *.gif\npublic class GifOpenHelper {\n\n\t// to store *.gif data, Bitmap & delay\n\tclass GifFrame {\n\t\t// to access image & delay w/o interface\n\t\tpublic Bitmap image;\n\t\tpublic int delay;\n\n\t\tpublic GifFrame(Bitmap im, int del) {\n\t\t\timage = im;\n\t\t\tdelay = del;\n\t\t}\n\n\t}\n\n\t// to define some error type\n\tpublic static final int STATUS_OK = 0;\n\tpublic static final int STATUS_FORMAT_ERROR = 1;\n\tpublic static final int STATUS_OPEN_ERROR = 2;\n\n\tprotected int status;\n\n\tprotected InputStream in;\n\n\tprotected int width; // full image width\n\tprotected int height; // full image height\n\tprotected boolean gctFlag; // global color table used\n\tprotected int gctSize; // size of global color table\n\tprotected int loopCount = 1; // iterations; 0 = repeat forever\n\n\tprotected int[] gct; // global color table\n\tprotected int[] lct; // local color table\n\tprotected int[] act; // active color table\n\n\tprotected int bgIndex; // background color index\n\tprotected int bgColor; // background color\n\tprotected int lastBgColor; // previous bg color\n\tprotected int pixelAspect; // pixel aspect ratio\n\n\tprotected boolean lctFlag; // local color table flag\n\tprotected boolean interlace; // interlace flag\n\tprotected int lctSize; // local color table size\n\n\tprotected int ix, iy, iw, ih; // current image rectangle\n\tprotected int lrx, lry, lrw, lrh;\n\tprotected Bitmap image; // current frame\n\tprotected Bitmap lastImage; // previous frame\n\tprotected int frameindex = 0;\n\n\tpublic int getFrameindex() {\n\t\treturn frameindex;\n\t}\n\n\tpublic void setFrameindex(int frameindex) {\n\t\tthis.frameindex = frameindex;\n\t\tif (frameindex > frames.size() - 1) {\n\t\t\tframeindex = 0;\n\t\t}\n\t}\n\n\tprotected byte[] block = new byte[256]; // current data block\n\tprotected int blockSize = 0; // block size\n\n\t// last graphic control extension info\n\tprotected int dispose = 0;\n\t// 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev\n\tprotected int lastDispose = 0;\n\tprotected boolean transparency = false; // use transparent color\n\tprotected int delay = 0; // delay in milliseconds\n\tprotected int transIndex; // transparent color index\n\n\tprotected static final int MaxStackSize = 4096;\n\t// max decoder pixel stack size\n\n\t// LZW decoder working arrays\n\tprotected short[] prefix;\n\tprotected byte[] suffix;\n\tprotected byte[] pixelStack;\n\tprotected byte[] pixels;\n\n\tprotected Vector<GifFrame> frames; // frames read from current file\n\tprotected int frameCount;\n\n\t// to get its Width / Height\n\tpublic int getWidth() {\n\t\treturn width;\n\t}\n\n\tpublic int getHeigh() {\n\t\treturn height;\n\t}\n\n\t/**\n\t * Gets display duration for specified frame.\n\t * \n\t * @param n\n\t *            int index of frame\n\t * @return delay in milliseconds\n\t */\n\tpublic int getDelay(int n) {\n\t\tdelay = -1;\n\t\tif ((n >= 0) && (n < frameCount)) {\n\t\t\tdelay = ((GifFrame) frames.elementAt(n)).delay;\n\t\t}\n\t\treturn delay;\n\t}\n\n\tpublic int getFrameCount() {\n\t\treturn frameCount;\n\t}\n\n\tpublic Bitmap getImage() {\n\t\treturn getFrame(0);\n\t}\n\n\tpublic int getLoopCount() {\n\t\treturn loopCount;\n\t}\n\n\tprotected void setPixels() {\n\t\tint[] dest = new int[width * height];\n\t\t// fill in starting image contents based on last image's dispose code\n\t\tif (lastDispose > 0) {\n\t\t\tif (lastDispose == 3) {\n\t\t\t\t// use image before last\n\t\t\t\tint n = frameCount - 2;\n\t\t\t\tif (n > 0) {\n\t\t\t\t\tlastImage = getFrame(n - 1);\n\t\t\t\t} else {\n\t\t\t\t\tlastImage = null;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (lastImage != null) {\n\t\t\t\tlastImage.getPixels(dest, 0, width, 0, 0, width, height);\n\t\t\t\t// copy pixels\n\t\t\t\tif (lastDispose == 2) {\n\t\t\t\t\t// fill last image rect area with background color\n\t\t\t\t\tint c = 0;\n\t\t\t\t\tif (!transparency) {\n\t\t\t\t\t\tc = lastBgColor;\n\t\t\t\t\t}\n\t\t\t\t\tfor (int i = 0; i < lrh; i++) {\n\t\t\t\t\t\tint n1 = (lry + i) * width + lrx;\n\t\t\t\t\t\tint n2 = n1 + lrw;\n\t\t\t\t\t\tfor (int k = n1; k < n2; k++) {\n\t\t\t\t\t\t\tdest[k] = c;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// copy each source line to the appropriate place in the destination\n\t\tint pass = 1;\n\t\tint inc = 8;\n\t\tint iline = 0;\n\t\tfor (int i = 0; i < ih; i++) {\n\t\t\tint line = i;\n\t\t\tif (interlace) {\n\t\t\t\tif (iline >= ih) {\n\t\t\t\t\tpass++;\n\t\t\t\t\tswitch (pass) {\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tiline = 4;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\tiline = 2;\n\t\t\t\t\t\tinc = 4;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\tiline = 1;\n\t\t\t\t\t\tinc = 2;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tline = iline;\n\t\t\t\tiline += inc;\n\t\t\t}\n\t\t\tline += iy;\n\t\t\tif (line < height) {\n\t\t\t\tint k = line * width;\n\t\t\t\tint dx = k + ix; // start of line in dest\n\t\t\t\tint dlim = dx + iw; // end of dest line\n\t\t\t\tif ((k + width) < dlim) {\n\t\t\t\t\tdlim = k + width; // past dest edge\n\t\t\t\t}\n\t\t\t\tint sx = i * iw; // start of line in source\n\t\t\t\twhile (dx < dlim) {\n\t\t\t\t\t// map color and insert in destination\n\t\t\t\t\tint index = ((int) pixels[sx++]) & 0xff;\n\t\t\t\t\tint c = act[index];\n\t\t\t\t\tif (c != 0) {\n\t\t\t\t\t\tdest[dx] = c;\n\t\t\t\t\t}\n\t\t\t\t\tdx++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\timage = Bitmap.createBitmap(dest, width, height, Config.ARGB_4444);\n\t}\n\n\tpublic Bitmap getFrame(int n) {\n\t\tBitmap im = null;\n\t\tif ((n >= 0) && (n < frameCount)) {\n\t\t\tim = ((GifFrame) frames.elementAt(n)).image;\n\t\t}\n\t\treturn im;\n\t}\n\n\tpublic Bitmap nextBitmap() {\n\t\tframeindex++;\n\t\tif (frameindex > frames.size() - 1) {\n\t\t\tframeindex = 0;\n\t\t}\n\t\treturn ((GifFrame) frames.elementAt(frameindex)).image;\n\t}\n\n\tpublic int nextDelay() {\n\t\treturn ((GifFrame) frames.elementAt(frameindex)).delay;\n\t}\n\n\t// to read & parse all *.gif stream\n\tpublic int read(InputStream is) {\n\t\tinit();\n\t\tif (is != null) {\n\t\t\tin = is;\n\n\t\t\treadHeader();\n\t\t\tif (!err()) {\n\t\t\t\treadContents();\n\t\t\t\tif (frameCount < 0) {\n\t\t\t\t\tstatus = STATUS_FORMAT_ERROR;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tstatus = STATUS_OPEN_ERROR;\n\t\t}\n\t\ttry {\n\t\t\tis.close();\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn status;\n\t}\n\n\tprotected void decodeImageData() {\n\t\tint NullCode = -1;\n\t\tint npix = iw * ih;\n\t\tint available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;\n\n\t\tif ((pixels == null) || (pixels.length < npix)) {\n\t\t\tpixels = new byte[npix]; // allocate new pixel array\n\t\t}\n\t\tif (prefix == null) {\n\t\t\tprefix = new short[MaxStackSize];\n\t\t}\n\t\tif (suffix == null) {\n\t\t\tsuffix = new byte[MaxStackSize];\n\t\t}\n\t\tif (pixelStack == null) {\n\t\t\tpixelStack = new byte[MaxStackSize + 1];\n\t\t}\n\t\t// Initialize GIF data stream decoder.\n\t\tdata_size = read();\n\t\tclear = 1 << data_size;\n\t\tend_of_information = clear + 1;\n\t\tavailable = clear + 2;\n\t\told_code = NullCode;\n\t\tcode_size = data_size + 1;\n\t\tcode_mask = (1 << code_size) - 1;\n\t\tfor (code = 0; code < clear; code++) {\n\t\t\tprefix[code] = 0;\n\t\t\tsuffix[code] = (byte) code;\n\t\t}\n\n\t\t// Decode GIF pixel stream.\n\t\tdatum = bits = count = first = top = pi = bi = 0;\n\t\tfor (i = 0; i < npix;) {\n\t\t\tif (top == 0) {\n\t\t\t\tif (bits < code_size) {\n\t\t\t\t\t// Load bytes until there are enough bits for a code.\n\t\t\t\t\tif (count == 0) {\n\t\t\t\t\t\t// Read a new data block.\n\t\t\t\t\t\tcount = readBlock();\n\t\t\t\t\t\tif (count <= 0) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbi = 0;\n\t\t\t\t\t}\n\t\t\t\t\tdatum += (((int) block[bi]) & 0xff) << bits;\n\t\t\t\t\tbits += 8;\n\t\t\t\t\tbi++;\n\t\t\t\t\tcount--;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Get the next code.\n\t\t\t\tcode = datum & code_mask;\n\t\t\t\tdatum >>= code_size;\n\t\t\t\tbits -= code_size;\n\n\t\t\t\t// Interpret the code\n\t\t\t\tif ((code > available) || (code == end_of_information)) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (code == clear) {\n\t\t\t\t\t// Reset decoder.\n\t\t\t\t\tcode_size = data_size + 1;\n\t\t\t\t\tcode_mask = (1 << code_size) - 1;\n\t\t\t\t\tavailable = clear + 2;\n\t\t\t\t\told_code = NullCode;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (old_code == NullCode) {\n\t\t\t\t\tpixelStack[top++] = suffix[code];\n\t\t\t\t\told_code = code;\n\t\t\t\t\tfirst = code;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tin_code = code;\n\t\t\t\tif (code == available) {\n\t\t\t\t\tpixelStack[top++] = (byte) first;\n\t\t\t\t\tcode = old_code;\n\t\t\t\t}\n\t\t\t\twhile (code > clear) {\n\t\t\t\t\tpixelStack[top++] = suffix[code];\n\t\t\t\t\tcode = prefix[code];\n\t\t\t\t}\n\t\t\t\tfirst = ((int) suffix[code]) & 0xff;\n\t\t\t\t// Add a new string to the string table,\n\t\t\t\tif (available >= MaxStackSize) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tpixelStack[top++] = (byte) first;\n\t\t\t\tprefix[available] = (short) old_code;\n\t\t\t\tsuffix[available] = (byte) first;\n\t\t\t\tavailable++;\n\t\t\t\tif (((available & code_mask) == 0)\n\t\t\t\t\t\t&& (available < MaxStackSize)) {\n\t\t\t\t\tcode_size++;\n\t\t\t\t\tcode_mask += available;\n\t\t\t\t}\n\t\t\t\told_code = in_code;\n\t\t\t}\n\n\t\t\t// Pop a pixel off the pixel stack.\n\t\t\ttop--;\n\t\t\tpixels[pi++] = pixelStack[top];\n\t\t\ti++;\n\t\t}\n\t\tfor (i = pi; i < npix; i++) {\n\t\t\tpixels[i] = 0; // clear missing pixels\n\t\t}\n\t}\n\n\tprotected boolean err() {\n\t\treturn status != STATUS_OK;\n\t}\n\n\t// to initia variable\n\tpublic void init() {\n\t\tstatus = STATUS_OK;\n\t\tframeCount = 0;\n\t\tframes = new Vector<GifFrame>();\n\t\tgct = null;\n\t\tlct = null;\n\t}\n\n\tprotected int read() {\n\t\tint curByte = 0;\n\t\ttry {\n\t\t\tcurByte = in.read();\n\t\t} catch (Exception e) {\n\t\t\tstatus = STATUS_FORMAT_ERROR;\n\t\t}\n\t\treturn curByte;\n\t}\n\n\tprotected int readBlock() {\n\t\tblockSize = read();\n\t\tint n = 0;\n\t\tif (blockSize > 0) {\n\t\t\ttry {\n\t\t\t\tint count = 0;\n\t\t\t\twhile (n < blockSize) {\n\t\t\t\t\tcount = in.read(block, n, blockSize - n);\n\t\t\t\t\tif (count == -1) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tn += count;\n\t\t\t\t}\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t\tif (n < blockSize) {\n\t\t\t\tstatus = STATUS_FORMAT_ERROR;\n\t\t\t}\n\t\t}\n\t\treturn n;\n\t}\n\n\t// Global Color Table\n\tprotected int[] readColorTable(int ncolors) {\n\t\tint nbytes = 3 * ncolors;\n\t\tint[] tab = null;\n\t\tbyte[] c = new byte[nbytes];\n\t\tint n = 0;\n\t\ttry {\n\t\t\tn = in.read(c);\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\tif (n < nbytes) {\n\t\t\tstatus = STATUS_FORMAT_ERROR;\n\t\t} else {\n\t\t\ttab = new int[256]; // max size to avoid bounds checks\n\t\t\tint i = 0;\n\t\t\tint j = 0;\n\t\t\twhile (i < ncolors) {\n\t\t\t\tint r = ((int) c[j++]) & 0xff;\n\t\t\t\tint g = ((int) c[j++]) & 0xff;\n\t\t\t\tint b = ((int) c[j++]) & 0xff;\n\t\t\t\ttab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;\n\t\t\t}\n\t\t}\n\t\treturn tab;\n\t}\n\n\t// Image Descriptor\n\tprotected void readContents() {\n\t\t// read GIF file content blocks\n\t\tboolean done = false;\n\t\twhile (!(done || err())) {\n\t\t\tint code = read();\n\t\t\tswitch (code) {\n\t\t\tcase 0x2C: // image separator\n\t\t\t\treadImage();\n\t\t\t\tbreak;\n\t\t\tcase 0x21: // extension\n\t\t\t\tcode = read();\n\t\t\t\tswitch (code) {\n\t\t\t\tcase 0xf9: // graphics control extension\n\t\t\t\t\treadGraphicControlExt();\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 0xff: // application extension\n\t\t\t\t\treadBlock();\n\t\t\t\t\tString app = \"\";\n\t\t\t\t\tfor (int i = 0; i < 11; i++) {\n\t\t\t\t\t\tapp += (char) block[i];\n\t\t\t\t\t}\n\t\t\t\t\tif (app.equals(\"NETSCAPE2.0\")) {\n\t\t\t\t\t\treadNetscapeExt();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tskip(); // don't care\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault: // uninteresting extension\n\t\t\t\t\tskip();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase 0x3b: // terminator\n\t\t\t\tdone = true;\n\t\t\t\tbreak;\n\n\t\t\tcase 0x00: // bad byte, but keep going and see what happens\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tstatus = STATUS_FORMAT_ERROR;\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected void readGraphicControlExt() {\n\t\tread(); // block size\n\t\tint packed = read(); // packed fields\n\t\tdispose = (packed & 0x1c) >> 2; // disposal method\n\t\tif (dispose == 0) {\n\t\t\tdispose = 1; // elect to keep old image if discretionary\n\t\t}\n\t\ttransparency = (packed & 1) != 0;\n\t\tdelay = readShort() * 10; // delay in milliseconds\n\t\ttransIndex = read(); // transparent color index\n\t\tread(); // block terminator\n\t}\n\n\t// to get Stream - Head\n\tprotected void readHeader() {\n\t\tString id = \"\";\n\t\tfor (int i = 0; i < 6; i++) {\n\t\t\tid += (char) read();\n\t\t}\n\t\tif (!id.startsWith(\"GIF\")) {\n\t\t\tstatus = STATUS_FORMAT_ERROR;\n\t\t\treturn;\n\t\t}\n\t\treadLSD();\n\t\tif (gctFlag && !err()) {\n\t\t\tgct = readColorTable(gctSize);\n\t\t\tbgColor = gct[bgIndex];\n\t\t}\n\t}\n\n\tprotected void readImage() {\n\t\t// offset of X\n\t\tix = readShort(); // (sub)image position & size\n\t\t// offset of Y\n\t\tiy = readShort();\n\t\t// width of bitmap\n\t\tiw = readShort();\n\t\t// height of bitmap\n\t\tih = readShort();\n\n\t\t// Local Color Table Flag\n\t\tint packed = read();\n\t\tlctFlag = (packed & 0x80) != 0; // 1 - local color table flag\n\n\t\t// Interlace Flag, to array with interwoven if ENABLE, with order\n\t\t// otherwise\n\t\tinterlace = (packed & 0x40) != 0; // 2 - interlace flag\n\t\t// 3 - sort flag\n\t\t// 4-5 - reserved\n\t\tlctSize = 2 << (packed & 7); // 6-8 - local color table size\n\t\tif (lctFlag) {\n\t\t\tlct = readColorTable(lctSize); // read table\n\t\t\tact = lct; // make local table active\n\t\t} else {\n\t\t\tact = gct; // make global table active\n\t\t\tif (bgIndex == transIndex) {\n\t\t\t\tbgColor = 0;\n\t\t\t}\n\t\t}\n\t\tint save = 0;\n\t\tif (transparency) {\n\t\t\tsave = act[transIndex];\n\t\t\tact[transIndex] = 0; // set transparent color if specified\n\t\t}\n\t\tif (act == null) {\n\t\t\tstatus = STATUS_FORMAT_ERROR; // no color table defined\n\t\t}\n\t\tif (err()) {\n\t\t\treturn;\n\t\t}\n\t\tdecodeImageData(); // decode pixel data\n\t\tskip();\n\t\tif (err()) {\n\t\t\treturn;\n\t\t}\n\t\tframeCount++;\n\t\t// create new image to receive frame data\n\t\timage = Bitmap.createBitmap(width, height, Config.ARGB_4444);\n\t\t// createImage(width, height);\n\t\tsetPixels(); // transfer pixel data to image\n\t\tframes.addElement(new GifFrame(image, delay)); // add image to frame\n\t\t// list\n\t\tif (transparency) {\n\t\t\tact[transIndex] = save;\n\t\t}\n\t\tresetFrame();\n\t}\n\n\t// Logical Screen Descriptor\n\tprotected void readLSD() {\n\t\t// logical screen size\n\t\twidth = readShort();\n\t\theight = readShort();\n\t\t// packed fields\n\t\tint packed = read();\n\t\tgctFlag = (packed & 0x80) != 0; // 1 : global color table flag\n\t\t// 2-4 : color resolution\n\t\t// 5 : gct sort flag\n\t\tgctSize = 2 << (packed & 7); // 6-8 : gct size\n\t\tbgIndex = read(); // background color index\n\t\tpixelAspect = read(); // pixel aspect ratio\n\t}\n\n\tprotected void readNetscapeExt() {\n\t\tdo {\n\t\t\treadBlock();\n\t\t\tif (block[0] == 1) {\n\t\t\t\t// loop count sub-block\n\t\t\t\tint b1 = ((int) block[1]) & 0xff;\n\t\t\t\tint b2 = ((int) block[2]) & 0xff;\n\t\t\t\tloopCount = (b2 << 8) | b1;\n\t\t\t}\n\t\t} while ((blockSize > 0) && !err());\n\t}\n\n\t// read 8 bit data\n\tprotected int readShort() {\n\t\t// read 16-bit value, LSB first\n\t\treturn read() | (read() << 8);\n\t}\n\n\tprotected void resetFrame() {\n\t\tlastDispose = dispose;\n\t\tlrx = ix;\n\t\tlry = iy;\n\t\tlrw = iw;\n\t\tlrh = ih;\n\t\tlastImage = image;\n\t\tlastBgColor = bgColor;\n\t\tdispose = 0;\n\t\ttransparency = false;\n\t\tdelay = 0;\n\t\tlct = null;\n\t}\n\n\t/**\n\t * Skips variable length blocks up to and including next zero length block.\n\t */\n\tprotected void skip() {\n\t\tdo {\n\t\t\treadBlock();\n\t\t} while ((blockSize > 0) && !err());\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/GlobalOnItemClickManagerUtils.java",
    "content": "package com.rance.chatui.util;\n\nimport android.content.Context;\nimport android.view.KeyEvent;\nimport android.view.View;\nimport android.widget.AdapterView;\nimport android.widget.EditText;\n\nimport com.rance.chatui.adapter.EmotionGridViewAdapter;\n\n/**\n * 作者：Rance on 2016/11/29 10:47\n * 邮箱：rance935@163.com\n * 点击表情的全局监听管理类\n */\npublic class GlobalOnItemClickManagerUtils {\n\n    private static GlobalOnItemClickManagerUtils instance;\n    private EditText mEditText;//输入框\n    private static Context mContext;\n\n    public static GlobalOnItemClickManagerUtils getInstance(Context context) {\n        mContext = context;\n        if (instance == null) {\n            synchronized (GlobalOnItemClickManagerUtils.class) {\n                if (instance == null) {\n                    instance = new GlobalOnItemClickManagerUtils();\n                }\n            }\n        }\n        return instance;\n    }\n\n    public void attachToEditText(EditText editText) {\n        mEditText = editText;\n    }\n\n    public AdapterView.OnItemClickListener getOnItemClickListener() {\n        return new AdapterView.OnItemClickListener() {\n            @Override\n            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n                Object itemAdapter = parent.getAdapter();\n\n                if (itemAdapter instanceof EmotionGridViewAdapter) {\n                    // 点击的是表情\n                    EmotionGridViewAdapter emotionGvAdapter = (EmotionGridViewAdapter) itemAdapter;\n\n                    if (position == emotionGvAdapter.getCount() - 1) {\n                        // 如果点击了最后一个回退按钮,则调用删除键事件\n                        mEditText.dispatchKeyEvent(new KeyEvent(\n                                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));\n                    } else {\n                        // 如果点击了表情,则添加到输入框中\n                        String emotionName = emotionGvAdapter.getItem(position);\n\n                        // 获取当前光标位置,在指定位置上添加表情图片文本\n                        int curPosition = mEditText.getSelectionStart();\n                        StringBuilder sb = new StringBuilder(mEditText.getText().toString());\n                        sb.insert(curPosition, emotionName);\n\n                        // 特殊文字处理,将表情等转换一下\n                        mEditText.setText(Utils.getEmotionContent(mContext, mEditText, sb.toString()));\n\n                        // 将光标设置到新增完表情的右侧\n                        mEditText.setSelection(curPosition + emotionName.length());\n                    }\n\n                }\n            }\n        };\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/MediaManager.java",
    "content": "package com.rance.chatui.util;\n\nimport android.media.AudioManager;\nimport android.media.MediaPlayer;\nimport android.media.MediaPlayer.OnCompletionListener;\nimport android.media.MediaPlayer.OnErrorListener;\n\n/**\n * 作者：Rance on 2016/12/15 15:11\n * 邮箱：rance935@163.com\n */\npublic class MediaManager {\n\n    private static MediaPlayer mMediaPlayer;\n    private static boolean isPause;\n\n    /**\n     * 播放音乐\n     *\n     * @param filePath\n     * @param onCompletionListener\n     */\n    public static void playSound(final String filePath, final OnCompletionListener onCompletionListener) {\n        if (mMediaPlayer == null) {\n            mMediaPlayer = new MediaPlayer();\n\n            //设置一个error监听器\n            mMediaPlayer.setOnErrorListener(new OnErrorListener() {\n\n                public boolean onError(MediaPlayer arg0, int arg1, int arg2) {\n                    mMediaPlayer.reset();\n                    return false;\n                }\n            });\n        } else {\n            mMediaPlayer.reset();\n        }\n        try {\n            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);\n            mMediaPlayer.setOnCompletionListener(onCompletionListener);\n            mMediaPlayer.setDataSource(filePath);\n            mMediaPlayer.prepare();\n            mMediaPlayer.start();\n        } catch (Exception e) {\n\n        }\n    }\n\n    /**\n     * 暂停播放\n     */\n    public static void pause() {\n        if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { //正在播放的时候\n            mMediaPlayer.pause();\n            isPause = true;\n        }\n    }\n\n    /**\n     * 当前是isPause状态\n     */\n    public static void resume() {\n        if (mMediaPlayer != null && isPause) {\n            mMediaPlayer.start();\n            isPause = false;\n        }\n    }\n\n    /**\n     * 释放资源\n     */\n    public static void release() {\n        if (mMediaPlayer != null) {\n            mMediaPlayer.release();\n            mMediaPlayer = null;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/MessageCenter.java",
    "content": "package com.rance.chatui.util;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.util.Log;\n\nimport com.rance.chatui.enity.Link;\nimport com.rance.chatui.enity.MessageInfo;\n\nimport org.greenrobot.eventbus.EventBus;\n\n/**\n * Created by chengz\n *\n * @date 2017/8/4.\n */\n\npublic class MessageCenter {\n    private static final String TAG = \"MessageCenter\";\n    //including images and links\n    public final static String MIME_TYPE_IMAGE = \"image/*\";\n    public final static String MIME_TYPE_IMAGE_JPG = \"image/jpg\";\n    public final static String MIME_TYPE_IMAGE_JPEG = \"image/jpeg\";\n    public final static String MIME_TYPE_IMAGE_PNG = \"image/png\";\n    public final static String MIME_TYPE_IMAGE_BMP = \"image/x-ms-bmp\";\n    public final static String MIME_TYPE_IMAGE_OTHER = \"image/x-adobe-dng\";\n    public final static String MIME_TYPE_PDF = \"application/pdf\";\n    public final static String MIME_TYPE_XLS = \"application/vnd.ms-excel\";\n    public final static String MIME_TYPE_XLSX = \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\";\n    public final static String MIME_TYPE_DOC = \"application/msword\";\n    public final static String MIME_TYPE_DOCX = \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\";\n    public final static String MIME_TYPE_PPT = \"application/vnd.ms-powerpoint\";\n    public final static String MIME_TYPE_PPTX = \"application/vnd.openxmlformats-officedocument.presentationml.presentation\";\n    public final static String MIME_TYPE_TEXT = \"text/plain\";\n    /**\n     * process received messages\n     * @param bundle\n     * @param mimeType\n     */\n    public static void handleIncoming(Bundle bundle, String mimeType, Activity activity) {\n        for (String key : bundle.keySet()) {\n            Log.e(TAG, \"handleIncomeAction: \" + key + \" => \" + bundle.get(key) + \";\");\n        }\n//\n//        Log.e(TAG, \"handleIncomeAction: ->\" + mimeType);\n        Log.e(TAG, \"handleIncoming: mimeType->\" + mimeType);\n        if (mimeType == null) {\n            return;\n        }\n        switch (mimeType) {\n            case MIME_TYPE_IMAGE:\n            case MIME_TYPE_IMAGE_JPG:\n            case MIME_TYPE_IMAGE_JPEG:\n            case MIME_TYPE_IMAGE_PNG:\n            case MIME_TYPE_IMAGE_BMP:\n            case MIME_TYPE_IMAGE_OTHER:\n                if (bundle.containsKey(\"url\") && bundle.getString(\"url\") != null\n                        && !\"\" .equals(bundle.getString(\"url\"))) {\n                    Log.e(TAG, \"handleIncoming: url->\" + bundle.getString(\"url\") );\n                    // link\n                    MessageInfo messageInfo = new MessageInfo();\n                    messageInfo.setMimeType(mimeType);\n                    messageInfo.setFileType(Constants.CHAT_FILE_TYPE_LINK);\n                    Link link = new Link();\n                    link.setSubject(bundle.getString(Intent.EXTRA_SUBJECT));\n                    link.setText(bundle.getString(Intent.EXTRA_TEXT));\n                    link.setStream(bundle.get(Intent.EXTRA_STREAM).toString());\n                    link.setUrl(bundle.getString(\"url\"));\n                    messageInfo.setObject(link);\n                    EventBus.getDefault().post(messageInfo);\n                } else {\n                    // image\n                    Log.e(TAG, \"handleIncoming: stream ->\" + bundle.getString(Intent.EXTRA_STREAM) );\n                    MessageInfo messageInfo = new MessageInfo();\n                    messageInfo.setMimeType(mimeType);\n                    messageInfo.setFilepath(bundle.get(Intent.EXTRA_STREAM).toString());\n                    messageInfo.setFileType(Constants.CHAT_FILE_TYPE_IMAGE);\n                    EventBus.getDefault().post(messageInfo);\n                }\n                break;\n            case MIME_TYPE_PDF:\n            case MIME_TYPE_DOC:\n            case MIME_TYPE_DOCX:\n            case MIME_TYPE_XLS:\n            case MIME_TYPE_XLSX:\n            case MIME_TYPE_PPT:\n            case MIME_TYPE_PPTX:\n            case MIME_TYPE_TEXT:\n                MessageInfo messageInfo = new MessageInfo();\n                messageInfo.setMimeType(mimeType);\n                messageInfo.setFileType(Constants.CHAT_FILE_TYPE_FILE);\n                messageInfo.setFilepath(FileUtils.getFileAbsolutePath(activity, (Uri) bundle.get(Intent.EXTRA_STREAM)));\n                EventBus.getDefault().post(messageInfo);\n                break;\n            default:\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/PhotoUtils.java",
    "content": "package com.rance.chatui.util;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.provider.MediaStore;\nimport android.support.v4.app.Fragment;\nimport android.util.Log;\n\nimport com.rance.chatui.base.BaseFragment;\n\n/**\n * Created by chengz\n *\n * @date 2017/8/1.\n */\n\npublic class PhotoUtils {\n    private static final String TAG = \"PhotoUtils\";\n\n    /**\n     * 拍照方法\n     * @param baseFragment\n     * @param imageUri\n     * @param requestCodeCamera\n     */\n    public static void takePicture(BaseFragment baseFragment, Uri imageUri, int requestCodeCamera) {\n        Intent intentCamera = new Intent();\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {\n            intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n        }\n        intentCamera.setAction(MediaStore.ACTION_IMAGE_CAPTURE);\n        intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);\n        Log.e(TAG, \"takePicture: ->\" + requestCodeCamera);\n        baseFragment.startActivityForResult(intentCamera, requestCodeCamera);\n    }\n\n    /**\n     * 裁剪图片\n     * @param activity\n     * @param orgUri\n     * @param desUri\n     * @param aspectX\n     * @param aspectY\n     * @param width\n     * @param height\n     * @param requestCode\n     */\n    public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX,\n                                    int aspectY, int width, int height, int requestCode) {\n        Intent intent = new Intent(\"com.android.camera.action.CROP\");\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n        }\n\n        intent.setDataAndType(orgUri, \"image/*\");\n        intent.putExtra(\"crop\", \"true\");\n        intent.putExtra(\"aspectX\", aspectX);\n        intent.putExtra(\"aspectY\", aspectY);\n        intent.putExtra(\"outputX\", width);\n        intent.putExtra(\"outputY\", height);\n        intent.putExtra(\"scale\", true);\n        intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);\n        intent.putExtra(\"return-data\", false);\n        intent.putExtra(\"outputFormat\", Bitmap.CompressFormat.JPEG.toString());\n        intent.putExtra(\"noFaceDetection\", true);\n        activity.startActivityForResult(intent, requestCode);\n    }\n\n    /**\n     * open picture\n     * @param activity\n     * @param requestCode\n     */\n    public static void openPic(Activity activity, int requestCode) {\n        Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);\n        photoPickerIntent.setType(\"image/*\");\n        activity.startActivityForResult(photoPickerIntent, requestCode);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/PopupWindowFactory.java",
    "content": "package com.rance.chatui.util;\n\nimport android.content.Context;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.PopupWindow;\n\n\n/**\n * 作者：Rance on 2016/11/29 10:47\n * 邮箱：rance935@163.com\n */\npublic class PopupWindowFactory {\n\n    private Context mContext;\n\n    private PopupWindow mPop;\n\n    /**\n     * @param mContext 上下文\n     * @param view PopupWindow显示的布局文件\n     */\n    public PopupWindowFactory(Context mContext, View view){\n        this(mContext,view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);\n    }\n\n    /**\n     * @param mContext 上下文\n     * @param view PopupWindow显示的布局文件\n     * @param width PopupWindow的宽\n     * @param heigth PopupWindow的高\n     */\n    public PopupWindowFactory(Context mContext, View view, int width, int heigth){\n        init(mContext,view,width,heigth);\n    }\n\n\n    private void init(Context mContext, View view, int width, int heigth){\n        this.mContext = mContext;\n\n        //下面这两个必须有！！\n        view.setFocusable(true);\n        view.setFocusableInTouchMode(true);\n\n        // PopupWindow(布局，宽度，高度)\n        mPop = new PopupWindow(view,width,heigth,true);\n        mPop.setFocusable(true);\n\n        // 重写onKeyListener,按返回键消失\n        view.setOnKeyListener(new View.OnKeyListener() {\n            @Override\n            public boolean onKey(View v, int keyCode, KeyEvent event) {\n                if (keyCode == KeyEvent.KEYCODE_BACK) {\n                    mPop.dismiss();\n                    return true;\n                }\n                return false;\n            }\n        });\n\n        //点击其他地方消失\n        view.setOnTouchListener(new View.OnTouchListener() {\n            @Override\n            public boolean onTouch(View v, MotionEvent event) {\n                if (mPop != null && mPop.isShowing()) {\n                    mPop.dismiss();\n                    return true;\n                }\n                return false;\n            }});\n\n\n    }\n\n    public PopupWindow getPopupWindow(){\n        return mPop;\n    }\n\n\n    /**\n     * 以触发弹出窗的view为基准，出现在view的内部上面，弹出的pop_view左上角正对view的左上角\n     * @param parent view\n     * @param gravity 在view的什么位置 Gravity.CENTER、Gravity.TOP......\n     * @param x 与控件的x坐标距离\n     * @param y 与控件的y坐标距离\n     */\n    public void showAtLocation(View parent, int gravity, int x, int y){\n\n        if(mPop.isShowing()){\n            return ;\n        }\n        mPop.showAtLocation(parent, gravity, x, y);\n\n    }\n\n    /**\n     * 以触发弹出窗的view为基准，出现在view的正下方，弹出的pop_view左上角正对view的左下角\n     * @param anchor view\n     */\n    public void showAsDropDown(View anchor){\n        showAsDropDown(anchor,0,0);\n    }\n\n    /**\n     * 以触发弹出窗的view为基准，出现在view的正下方，弹出的pop_view左上角正对view的左下角\n     * @param anchor view\n     * @param xoff 与view的x坐标距离\n     * @param yoff 与view的y坐标距离\n     */\n    public void showAsDropDown(View anchor, int xoff, int yoff){\n        if(mPop.isShowing()){\n            return ;\n        }\n\n        mPop.showAsDropDown(anchor, xoff, yoff);\n    }\n\n    /**\n     * 隐藏PopupWindow\n     */\n    public void dismiss(){\n        if (mPop.isShowing()) {\n            mPop.dismiss();\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/util/Utils.java",
    "content": "package com.rance.chatui.util;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.text.Spannable;\nimport android.text.SpannableString;\nimport android.text.style.ImageSpan;\nimport android.widget.TextView;\n\nimport java.text.SimpleDateFormat;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * 作者：Rance on 2016/12/20 16:41\n * 邮箱：rance935@163.com\n */\npublic class Utils {\n    /**\n     * dp转dip\n     *\n     * @param context\n     * @param dp\n     * @return\n     */\n    public static int dp2px(Context context, float dp) {\n        float density = context.getResources().getDisplayMetrics().density;\n        return (int) (dp * density + 0.5F);\n    }\n\n    /**\n     * 文本中的emojb字符处理为表情图片\n     *\n     * @param context\n     * @param tv\n     * @param source\n     * @return\n     */\n    public static SpannableString getEmotionContent(final Context context, final TextView tv, String source) {\n        SpannableString spannableString = new SpannableString(source);\n        Resources res = context.getResources();\n\n        String regexEmotion = \"\\\\[([\\u4e00-\\u9fa5\\\\w])+\\\\]\";\n        Pattern patternEmotion = Pattern.compile(regexEmotion);\n        Matcher matcherEmotion = patternEmotion.matcher(spannableString);\n\n        while (matcherEmotion.find()) {\n            // 获取匹配到的具体字符\n            String key = matcherEmotion.group();\n            // 匹配字符串的开始位置\n            int start = matcherEmotion.start();\n            // 利用表情名字获取到对应的图片\n            Integer imgRes = EmotionUtils.EMOTION_STATIC_MAP.get(key);\n            if (imgRes != null) {\n                // 压缩表情图片\n                int size = (int) tv.getTextSize() * 13 / 8;\n                Bitmap bitmap = BitmapFactory.decodeResource(res, imgRes);\n                Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);\n\n                ImageSpan span = new ImageSpan(context, scaleBitmap);\n                spannableString.setSpan(span, start, start + key.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);\n            }\n        }\n        return spannableString;\n    }\n\n    /**\n     * 返回当前时间的格式为 yyyyMMddHHmmss\n     *\n     * @return\n     */\n    public static String getCurrentTime() {\n        SimpleDateFormat sdf = new SimpleDateFormat(\"yyyyMMddHHmmss\");\n        return sdf.format(System.currentTimeMillis());\n    }\n\n    //毫秒转秒\n    public static String long2String(long time) {\n        //毫秒转秒\n        int sec = (int) time / 1000;\n        int min = sec / 60;    //分钟\n        sec = sec % 60;        //秒\n        if (min < 10) {    //分钟补0\n            if (sec < 10) {    //秒补0\n                return \"0\" + min + \":0\" + sec;\n            } else {\n                return \"0\" + min + \":\" + sec;\n            }\n        } else {\n            if (sec < 10) {    //秒补0\n                return min + \":0\" + sec;\n            } else {\n                return min + \":\" + sec;\n            }\n        }\n    }\n\n    /**\n     * 毫秒转化时分秒毫秒\n     *\n     * @param ms\n     * @return\n     */\n    public static String formatTime(Long ms) {\n        Integer ss = 1000;\n        Integer mi = ss * 60;\n        Integer hh = mi * 60;\n        Integer dd = hh * 24;\n\n        Long day = ms / dd;\n        Long hour = (ms - day * dd) / hh;\n        Long minute = (ms - day * dd - hour * hh) / mi;\n        Long second = (ms - day * dd - hour * hh - minute * mi) / ss;\n\n        StringBuffer sb = new StringBuffer();\n        if (day > 0) {\n            sb.append(day + \"d\");\n        }\n        if (hour > 0) {\n            sb.append(hour + \"h\");\n        }\n        if (minute > 0) {\n            sb.append(minute + \"′\");\n        }\n        if (second > 0) {\n            sb.append(second + \"″\");\n        }\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/BubbleDrawable.java",
    "content": "package com.rance.chatui.widget;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapShader;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.ColorFilter;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.PixelFormat;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Shader;\nimport android.graphics.drawable.Drawable;\n\n/**\n * 作者：Rance on 2016/12/15 11:28\n * 邮箱：rance935@163.com\n * 自定义聊天气泡drawable\n */\npublic class BubbleDrawable extends Drawable {\n    private RectF mRect;\n    private Path mPath = new Path();\n    private BitmapShader mBitmapShader;\n    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);\n    private float mArrowWidth;\n    private float mAngle;\n    private float mArrowHeight;\n    private float mArrowPosition;\n    private int bubbleColor;\n    private Bitmap bubbleBitmap;\n    private ArrowLocation mArrowLocation;\n    private BubbleType bubbleType;\n    private BubbleDrawable(Builder builder) {\n        this.mRect = builder.mRect;\n        this.mAngle = builder.mAngle;\n        this.mArrowHeight = builder.mArrowHeight;\n        this.mArrowWidth = builder.mArrowWidth;\n        this.mArrowPosition = builder.mArrowPosition;\n        this.bubbleColor = builder.bubbleColor;\n        this.bubbleBitmap = builder.bubbleBitmap;\n        this.mArrowLocation = builder.mArrowLocation;\n        this.bubbleType = builder.bubbleType;\n    }\n\n    @Override\n    protected void onBoundsChange(Rect bounds) {\n        super.onBoundsChange(bounds);\n    }\n\n    @Override\n    public void draw(Canvas canvas) {\n        setUp(canvas);\n    }\n\n    @Override\n    public int getOpacity() {\n        return PixelFormat.TRANSLUCENT;\n    }\n\n    @Override\n    public void setAlpha(int alpha) {\n        mPaint.setAlpha(alpha);\n    }\n\n    @Override\n    public void setColorFilter(ColorFilter cf) {\n        mPaint.setColorFilter(cf);\n    }\n\n    private void setUpPath(ArrowLocation mArrowLocation, Path path){\n        switch (mArrowLocation){\n            case LEFT:\n                setUpLeftPath(mRect, path);\n                break;\n            case RIGHT:\n                setUpRightPath(mRect, path);\n                break;\n            case TOP:\n                setUpTopPath(mRect, path);\n                break;\n            case BOTTOM:\n                setUpBottomPath(mRect, path);\n                break;\n        }\n    }\n\n    private void setUp(Canvas canvas){\n        switch (bubbleType){\n            case COLOR:\n                mPaint.setColor(bubbleColor);\n                break;\n            case BITMAP:\n                if (bubbleBitmap == null)\n                    return;\n                if (mBitmapShader == null){\n                    mBitmapShader = new BitmapShader(bubbleBitmap,\n                            Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);\n                }\n                mPaint.setShader(mBitmapShader);\n                setUpShaderMatrix();\n                break;\n        }\n        setUpPath(mArrowLocation, mPath);\n        canvas.drawPath(mPath, mPaint);\n    }\n\n    private void setUpLeftPath(RectF rect, Path path){\n\n        path.moveTo(mArrowWidth + rect.left + mAngle, rect.top);\n        path.lineTo(rect.width() - mAngle, rect.top);\n        path.arcTo(new RectF(rect.right - mAngle , rect.top, rect.right,\n                mAngle + rect.top), 270, 90);\n        path.lineTo(rect.right, rect.bottom - mAngle);\n        path.arcTo(new RectF(rect.right - mAngle , rect.bottom - mAngle,\n                rect.right, rect.bottom), 0, 90);\n        path.lineTo(rect.left + mArrowWidth + mAngle, rect.bottom);\n        path.arcTo(new RectF(rect.left + mArrowWidth, rect.bottom - mAngle ,\n                mAngle + rect.left + mArrowWidth, rect.bottom), 90, 90);\n        path.lineTo(rect.left + mArrowWidth,  mArrowHeight + mArrowPosition);\n        path.lineTo(rect.left, mArrowPosition + mArrowHeight / 2);\n        path.lineTo(rect.left + mArrowWidth, mArrowPosition);\n        path.lineTo(rect.left + mArrowWidth, rect.top + mAngle);\n        path.arcTo(new RectF(rect.left + mArrowWidth, rect.top, mAngle\n                + rect.left + mArrowWidth, mAngle + rect.top), 180, 90);\n        path.close();\n    }\n\n    private void setUpTopPath(RectF rect, Path path){\n        path.moveTo(rect.left + Math.min(mArrowPosition, mAngle), rect.top + mArrowHeight);\n        path.lineTo(rect.left + mArrowPosition,  rect.top + mArrowHeight);\n        path.lineTo(rect.left + mArrowWidth / 2 + mArrowPosition, rect.top);\n        path.lineTo(rect.left + mArrowWidth + mArrowPosition, rect.top + mArrowHeight);\n        path.lineTo(rect.right - mAngle, rect.top + mArrowHeight);\n\n        path.arcTo(new RectF(rect.right - mAngle,\n                rect.top + mArrowHeight , rect.right, mAngle + rect.top + mArrowHeight), 270, 90);\n        path.lineTo(rect.right, rect.bottom - mAngle);\n\n        path.arcTo(new RectF(rect.right - mAngle , rect.bottom - mAngle,\n                rect.right , rect.bottom), 0, 90);\n        path.lineTo(rect.left + mAngle, rect.bottom);\n\n        path.arcTo(new RectF(rect.left, rect.bottom - mAngle,\n                mAngle + rect.left , rect.bottom), 90, 90);\n        path.lineTo(rect.left , rect.top + mArrowHeight + mAngle);\n        path.arcTo(new RectF(rect.left, rect.top + mArrowHeight , mAngle\n                + rect.left, mAngle + rect.top + mArrowHeight), 180, 90);\n        path.close();\n    }\n\n    private void setUpRightPath(RectF rect, Path path){\n\n        path.moveTo(rect.left + mAngle, rect.top);\n        path.lineTo(rect.width() - mAngle - mArrowWidth, rect.top);\n        path.arcTo(new RectF(rect.right - mAngle - mArrowWidth,\n                rect.top, rect.right - mArrowWidth, mAngle + rect.top), 270, 90);\n        path.lineTo(rect.right - mArrowWidth,  mArrowPosition);\n        path.lineTo(rect.right, mArrowPosition + mArrowHeight / 2);\n        path.lineTo(rect.right - mArrowWidth, mArrowPosition + mArrowHeight);\n        path.lineTo(rect.right - mArrowWidth, rect.bottom - mAngle);\n\n        path.arcTo(new RectF(rect.right - mAngle - mArrowWidth, rect.bottom - mAngle,\n                rect.right - mArrowWidth, rect.bottom), 0, 90);\n        path.lineTo(rect.left + mArrowWidth, rect.bottom);\n\n        path.arcTo(new RectF(rect.left, rect.bottom - mAngle,\n                mAngle + rect.left , rect.bottom), 90, 90);\n\n        path.arcTo(new RectF(rect.left, rect.top, mAngle\n                + rect.left, mAngle + rect.top), 180, 90);\n        path.close();\n    }\n\n    private void setUpBottomPath(RectF rect, Path path){\n\n        path.moveTo(rect.left + mAngle, rect.top);\n        path.lineTo(rect.width() - mAngle, rect.top);\n        path.arcTo(new RectF(rect.right - mAngle,\n                rect.top, rect.right, mAngle + rect.top), 270, 90);\n\n        path.lineTo(rect.right, rect.bottom - mArrowHeight - mAngle);\n        path.arcTo(new RectF(rect.right - mAngle, rect.bottom - mAngle - mArrowHeight,\n                rect.right, rect.bottom - mArrowHeight), 0, 90);\n\n        path.lineTo(rect.left + mArrowWidth + mArrowPosition,  rect.bottom - mArrowHeight);\n        path.lineTo(rect.left + mArrowPosition + mArrowWidth / 2, rect.bottom);\n        path.lineTo(rect.left + mArrowPosition, rect.bottom - mArrowHeight);\n        path.lineTo(rect.left + Math.min(mAngle, mArrowPosition), rect.bottom - mArrowHeight);\n\n        path.arcTo(new RectF(rect.left, rect.bottom - mAngle - mArrowHeight,\n                mAngle + rect.left , rect.bottom - mArrowHeight), 90, 90);\n        path.lineTo(rect.left, rect.top + mAngle);\n        path.arcTo(new RectF(rect.left, rect.top, mAngle\n                + rect.left, mAngle + rect.top), 180, 90);\n        path.close();\n    }\n\n    private void setUpShaderMatrix() {\n        float scale;\n        Matrix mShaderMatrix = new Matrix();\n        mShaderMatrix.set(null);\n        int mBitmapWidth = bubbleBitmap.getWidth();\n        int mBitmapHeight = bubbleBitmap.getHeight();\n        float scaleX = getIntrinsicWidth() / (float) mBitmapWidth;\n        float scaleY = getIntrinsicHeight() / (float) mBitmapHeight;\n        scale = Math.min(scaleX, scaleY);\n        mShaderMatrix.postScale(scale, scale);\n        mShaderMatrix.postTranslate(mRect.left, mRect.top);\n        mBitmapShader.setLocalMatrix(mShaderMatrix);\n    }\n\n    @Override\n    public int getIntrinsicWidth() {\n        return (int)mRect.width();\n    }\n\n    @Override\n    public int getIntrinsicHeight() {\n        return (int)mRect.height();\n    }\n\n    public static class Builder{\n        public static float DEFAULT_ARROW_WITH = 25;\n        public static float DEFAULT_ARROW_HEIGHT = 25;\n        public static float DEFAULT_ANGLE = 20;\n        public static float DEFAULT_ARROW_POSITION = 50;\n        public static int DEFAULT_BUBBLE_COLOR = Color.RED;\n        private RectF mRect;\n        private float mArrowWidth = DEFAULT_ARROW_WITH;\n        private float mAngle = DEFAULT_ANGLE;\n        private float mArrowHeight = DEFAULT_ARROW_HEIGHT;\n        private float mArrowPosition = DEFAULT_ARROW_POSITION;\n        private int bubbleColor = DEFAULT_BUBBLE_COLOR;\n        private Bitmap bubbleBitmap;\n        private BubbleType bubbleType = BubbleType.COLOR;\n        private ArrowLocation mArrowLocation = ArrowLocation.LEFT;\n\n        public Builder rect(RectF rect){\n            this.mRect = rect;\n            return this;\n        }\n\n        public Builder arrowWidth(float mArrowWidth){\n            this.mArrowWidth = mArrowWidth;\n            return this;\n        }\n\n        public Builder angle(float mAngle){\n            this.mAngle = mAngle * 2;\n            return this;\n        }\n\n        public Builder arrowHeight(float mArrowHeight){\n            this.mArrowHeight = mArrowHeight;\n            return this;\n        }\n\n        public Builder arrowPosition(float mArrowPosition){\n            this.mArrowPosition = mArrowPosition;\n            return this;\n        }\n\n        public Builder bubbleColor(int bubbleColor){\n            this.bubbleColor = bubbleColor;\n            bubbleType(BubbleType.COLOR);\n            return this;\n        }\n\n        public Builder bubbleBitmap(Bitmap bubbleBitmap){\n            this.bubbleBitmap = bubbleBitmap;\n            bubbleType(BubbleType.BITMAP);\n            return this;\n        }\n\n        public Builder arrowLocation(ArrowLocation arrowLocation){\n            this.mArrowLocation = arrowLocation;\n            return this;\n        }\n\n        public Builder bubbleType(BubbleType bubbleType){\n            this.bubbleType = bubbleType;\n            return this;\n        }\n\n        public BubbleDrawable build(){\n            if (mRect == null){\n                throw new IllegalArgumentException(\"BubbleDrawable Rect can not be null\");\n            }\n            return new BubbleDrawable(this);\n        }\n    }\n\n    public enum ArrowLocation{\n        LEFT(0x00),\n        RIGHT(0x01),\n        TOP(0x02),\n        BOTTOM(0x03);\n\n        private int mValue;\n\n        ArrowLocation(int value){\n            this.mValue = value;\n        }\n\n        public static ArrowLocation mapIntToValue(int stateInt) {\n            for (ArrowLocation value : ArrowLocation.values()) {\n                if (stateInt == value.getIntValue()) {\n                    return value;\n                }\n            }\n            return getDefault();\n        }\n\n        public static ArrowLocation getDefault(){\n            return LEFT;\n        }\n\n        public int getIntValue() {\n            return mValue;\n        }\n    }\n\n    public enum BubbleType{\n        COLOR,\n        BITMAP\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/BubbleImageView.java",
    "content": "package com.rance.chatui.widget;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.RectF;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.util.TypedValue;\nimport android.widget.ImageView;\n\nimport com.rance.chatui.R;\n\n/**\n * 作者：Rance on 2016/12/15 10:49\n * 邮箱：rance935@163.com\n * 自定义聊天气泡图片\n */\npublic class BubbleImageView extends ImageView {\n    private BubbleDrawable bubbleDrawable;\n    private Drawable sourceDrawable;\n    private float mArrowWidth;\n    private float mAngle;\n    private float mArrowHeight;\n    private float mArrowPosition;\n    private Bitmap mBitmap;\n    private BubbleDrawable.ArrowLocation mArrowLocation;\n    public BubbleImageView(Context context) {\n        super(context);\n        initView(null);\n    }\n\n    public BubbleImageView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initView(attrs);\n    }\n\n    public BubbleImageView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        initView(attrs);\n    }\n\n    private void initView(AttributeSet attrs){\n        if (attrs != null){\n            TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.BubbleView);\n            mArrowWidth = array.getDimension(R.styleable.BubbleView_arrowWidth,\n                    BubbleDrawable.Builder.DEFAULT_ARROW_WITH);\n            mArrowHeight = array.getDimension(R.styleable.BubbleView_arrowHeight,\n                    BubbleDrawable.Builder.DEFAULT_ARROW_HEIGHT);\n            mAngle = array.getDimension(R.styleable.BubbleView_angle,\n                    BubbleDrawable.Builder.DEFAULT_ANGLE);\n            mArrowPosition = array.getDimension(R.styleable.BubbleView_arrowPosition,\n                    BubbleDrawable.Builder.DEFAULT_ARROW_POSITION);\n            int location = array.getInt(R.styleable.BubbleView_arrowLocation, 0);\n            mArrowLocation = BubbleDrawable.ArrowLocation.mapIntToValue(location);\n            array.recycle();\n        }\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        int width = getMeasuredWidth();\n        int height = getMeasuredHeight();\n        if (width <= 0 && height > 0){\n            setMeasuredDimension(height , height);\n        }\n        if (height <= 0 && width > 0){\n            setMeasuredDimension(width , width);\n        }\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        if (w > 0 && h > 0){\n            setUp(w, h);\n        }\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n        setUp();\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        int saveCount = canvas.getSaveCount();\n        canvas.translate(getPaddingLeft(), getPaddingTop());\n        if (bubbleDrawable != null)\n            bubbleDrawable.draw(canvas);\n        canvas.restoreToCount(saveCount);\n    }\n\n    private void setUp(int left, int right, int top, int bottom){\n        Log.d(\"setUp\", \"left-->\" + left);\n        Log.d(\"setUp\", \"right-->\" + right);\n        Log.d(\"setUp\", \"top-->\" + top);\n        Log.d(\"setUp\", \"bottom-->\" + bottom);\n        if (right <= left || bottom <= top)\n            return;\n\n        RectF rectF = new RectF(left, top, right, bottom);\n        if (sourceDrawable != null)\n            mBitmap = getBitmapFromDrawable(sourceDrawable);\n        bubbleDrawable = new BubbleDrawable.Builder()\n                .rect(rectF)\n                .arrowLocation(mArrowLocation)\n                .angle(mAngle)\n                .arrowHeight(mArrowHeight)\n                .arrowWidth(mArrowWidth)\n                .bubbleType(BubbleDrawable.BubbleType.BITMAP)\n                .arrowPosition(mArrowPosition)\n                .bubbleBitmap(mBitmap)\n                .build();\n    }\n\n    private void setUp(int width, int height){\n        setUp(getPaddingLeft(), width - getPaddingRight(),\n                getPaddingTop(), height - getPaddingBottom());\n    }\n\n    private void setUp(){\n        int width = getWidth();\n        int height = getHeight();\n        int scale;\n\n        if (width > 0 && height <= 0 && sourceDrawable != null){\n            if (sourceDrawable.getIntrinsicWidth() >= 0){\n                scale = width / sourceDrawable.getIntrinsicWidth();\n                height = scale * sourceDrawable.getIntrinsicHeight();\n            }\n        }\n\n        if (height > 0 &&  width <= 0 && sourceDrawable != null){\n            if (sourceDrawable.getIntrinsicHeight() >= 0){\n                scale = height / sourceDrawable.getIntrinsicHeight();\n                width = scale * sourceDrawable.getIntrinsicWidth();\n            }\n        }\n        setUp(width, height);\n    }\n\n    @Override\n    public void setImageBitmap(Bitmap mBitmap) {\n        if (mBitmap == null)\n            return;\n        this.mBitmap = mBitmap;\n        sourceDrawable = new BitmapDrawable(getResources(), mBitmap);\n        setUp();\n        super.setImageDrawable(bubbleDrawable);\n    }\n\n    @Override\n    public void setImageDrawable(Drawable drawable){\n        if (drawable == null )\n            return;\n        sourceDrawable = drawable;\n        setUp();\n        super.setImageDrawable(bubbleDrawable);\n    }\n\n    @Override\n    public void setImageResource(int res){\n        setImageDrawable(getDrawable(res));\n    }\n\n    private Drawable getDrawable(int res){\n        if (res == 0){\n            throw new IllegalArgumentException(\"getDrawable res can not be zero\");\n        }\n        return getContext().getResources().getDrawable(res);\n    }\n\n    private Bitmap getBitmapFromDrawable(Drawable drawable) {\n        return getBitmapFromDrawable(getContext(), drawable, getWidth(), getWidth(), 25);\n    }\n\n    public static Bitmap getBitmapFromDrawable(Context mContext, Drawable drawable, int width, int height, int defaultSize) {\n        if (drawable == null) {\n            return null;\n        }\n        if (drawable instanceof BitmapDrawable) {\n            return ((BitmapDrawable) drawable).getBitmap();\n        }\n        try {\n            Bitmap bitmap;\n            if (width > 0 && height > 0){\n                bitmap = Bitmap.createBitmap(width,\n                        height, Bitmap.Config.ARGB_8888);\n            }else{\n                bitmap = Bitmap.createBitmap(dp2px(mContext, defaultSize),\n                        dp2px(mContext, defaultSize), Bitmap.Config.ARGB_8888);\n            }\n            Canvas canvas = new Canvas(bitmap);\n            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());\n            drawable.draw(canvas);\n            return bitmap;\n        } catch (OutOfMemoryError e) {\n            return null;\n        }\n    }\n\n    public static int dp2px(Context context, int dp) {\n        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,\n                context.getResources().getDisplayMetrics());\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/BubbleLinearLayout.java",
    "content": "package com.rance.chatui.widget;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.RectF;\nimport android.util.AttributeSet;\nimport android.widget.LinearLayout;\n\nimport com.rance.chatui.R;\n\n/**\n * 作者：Rance on 2016/12/15 10:49\n * 邮箱：rance935@163.com\n * 自定义聊天气泡LinearLayout\n */\npublic class BubbleLinearLayout extends LinearLayout {\n    private BubbleDrawable bubbleDrawable;\n    private float mArrowWidth;\n    private float mAngle;\n    private float mArrowHeight;\n    private float mArrowPosition;\n    private BubbleDrawable.ArrowLocation mArrowLocation;\n    private int bubbleColor;\n    public BubbleLinearLayout(Context context) {\n        super(context);\n        initView(null);\n    }\n\n    public BubbleLinearLayout(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initView(attrs);\n    }\n\n\n    private void initView(AttributeSet attrs){\n        if (attrs != null){\n            TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.BubbleView);\n            mArrowWidth = array.getDimension(R.styleable.BubbleView_arrowWidth,\n                    BubbleDrawable.Builder.DEFAULT_ARROW_WITH);\n            mArrowHeight = array.getDimension(R.styleable.BubbleView_arrowHeight,\n                    BubbleDrawable.Builder.DEFAULT_ARROW_HEIGHT);\n            mAngle = array.getDimension(R.styleable.BubbleView_angle,\n                    BubbleDrawable.Builder.DEFAULT_ANGLE);\n            mArrowPosition = array.getDimension(R.styleable.BubbleView_arrowPosition,\n                    BubbleDrawable.Builder.DEFAULT_ARROW_POSITION);\n            bubbleColor = array.getColor(R.styleable.BubbleView_bubbleColor,\n                    BubbleDrawable.Builder.DEFAULT_BUBBLE_COLOR);\n            int location = array.getInt(R.styleable.BubbleView_arrowLocation, 0);\n            mArrowLocation = BubbleDrawable.ArrowLocation.mapIntToValue(location);\n            array.recycle();\n        }\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        if (w > 0 && h > 0){\n            setUp(w, h);\n        }\n    }\n\n    private void setUp(int left, int right, int top, int bottom){\n        if (right < left || bottom < top)\n            return;\n        RectF rectF = new RectF(left, top, right, bottom);\n        bubbleDrawable = new BubbleDrawable.Builder()\n                .rect(rectF)\n                .arrowLocation(mArrowLocation)\n                .bubbleType(BubbleDrawable.BubbleType.COLOR)\n                .angle(mAngle)\n                .arrowHeight(mArrowHeight)\n                .arrowWidth(mArrowWidth)\n                .arrowPosition(mArrowPosition)\n                .bubbleColor(bubbleColor)\n                .build();\n    }\n\n    private void setUp(int width, int height){\n        setUp(getPaddingLeft(),  + width - getPaddingRight(),\n                getPaddingTop(), height - getPaddingBottom());\n        setBackgroundDrawable(bubbleDrawable);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/ChatContextMenu.java",
    "content": "package com.rance.chatui.widget;\n\nimport android.animation.Animator;\nimport android.annotation.TargetApi;\nimport android.content.ClipData;\nimport android.content.ClipboardManager;\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.os.Build;\nimport android.os.Message;\nimport android.support.annotation.NonNull;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewAnimationUtils;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.labo.kaji.relativepopupwindow.RelativePopupWindow;\nimport com.rance.chatui.R;\nimport com.rance.chatui.enity.MessageInfo;\n\n/**\n * Created by chengz\n *\n * @date 2017/7/31.\n */\n\npublic class ChatContextMenu extends RelativePopupWindow {\n\n    private MessageInfo mMessageInfo;\n    private Context mContext;\n    public ChatContextMenu(Context context, MessageInfo messageInfo) {\n        this.mMessageInfo = messageInfo;\n        this.mContext = context;\n        View view = LayoutInflater.from(context).inflate(R.layout.popup_context_menu, null);\n        setContentView(view);\n\n        setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);\n        setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);\n        setFocusable(true);\n        setOutsideTouchable(true);\n        setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));\n\n        // Disable default animation for circular reveal\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            setAnimationStyle(0);\n        }\n\n        TextView tvCopy = (TextView) view.findViewById(R.id.tv_copy);\n        TextView tvTransit = (TextView) view.findViewById(R.id.tv_transit);\n\n        tvCopy.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                copyToClipboard();\n            }\n        });\n        tvTransit.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                transitContent();\n            }\n        });\n    }\n\n    private void transitContent() {\n\n    }\n\n    private void copyToClipboard() {\n        ClipboardManager cm = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);\n        ClipData data = ClipData.newPlainText(\"Moa\", mMessageInfo.getContent());\n        cm.setPrimaryClip(data);\n    }\n\n    @Override\n    public void showOnAnchor(@NonNull View anchor, int vertPos, int horizPos, int x, int y, boolean fitInScreen) {\n        super.showOnAnchor(anchor, vertPos, horizPos, x, y, fitInScreen);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            circularReveal(anchor);\n        }\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private void circularReveal(@NonNull final View anchor) {\n        final View contentView = getContentView();\n        contentView.post(new Runnable() {\n            @Override\n            public void run() {\n                final int[] myLocation = new int[2];\n                final int[] anchorLocation = new int[2];\n                contentView.getLocationOnScreen(myLocation);\n                anchor.getLocationOnScreen(anchorLocation);\n                final int cx = anchorLocation[0] - myLocation[0] + anchor.getWidth()/2;\n                final int cy = anchorLocation[1] - myLocation[1] + anchor.getHeight()/2;\n\n                contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);\n                final int dx = Math.max(cx, contentView.getMeasuredWidth() - cx);\n                final int dy = Math.max(cy, contentView.getMeasuredHeight() - cy);\n                final float finalRadius = (float) Math.hypot(dx, dy);\n                Animator animator = ViewAnimationUtils.createCircularReveal(contentView, cx, cy, 0f, finalRadius);\n                animator.setDuration(1);\n                animator.start();\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/EmotionInputDetector.java",
    "content": "package com.rance.chatui.widget;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.graphics.Rect;\nimport android.os.Build;\nimport android.support.v4.view.ViewPager;\nimport android.text.Editable;\nimport android.text.TextWatcher;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.view.Gravity;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.EditText;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.rance.chatui.R;\nimport com.rance.chatui.enity.MessageInfo;\nimport com.rance.chatui.util.AudioRecorderUtils;\nimport com.rance.chatui.util.Constants;\nimport com.rance.chatui.util.PopupWindowFactory;\nimport com.rance.chatui.util.Utils;\n\nimport org.greenrobot.eventbus.EventBus;\n\n/**\n * 作者：Rance on 2016/12/13 15:19\n * 邮箱：rance935@163.com\n * 输入框管理类\n */\npublic class EmotionInputDetector {\n    private static final String TAG = \"EmotionInputDetector\";\n    private static final String SHARE_PREFERENCE_NAME = \"com.dss886.emotioninputdetector\";\n    private static final String SHARE_PREFERENCE_TAG = \"soft_input_height\";\n\n    private Activity mActivity;\n    private InputMethodManager mInputManager;\n    private SharedPreferences sp;\n    private View mEmotionLayout;\n    private EditText mEditText;\n    private TextView mVoiceText;\n    private View mContentView;\n    private ViewPager mViewPager;\n    private View mSendButton;\n    private View mAddButton;\n    private Boolean isShowEmotion = false;\n    private Boolean isShowAdd = false;\n    private boolean isShowVoice = false;\n    private AudioRecorderUtils mAudioRecorderUtils;\n    private PopupWindowFactory mVoicePop;\n    private TextView mPopVoiceText;\n\n    private EmotionInputDetector() {\n    }\n\n    public static EmotionInputDetector with(Activity activity) {\n        EmotionInputDetector emotionInputDetector = new EmotionInputDetector();\n        emotionInputDetector.mActivity = activity;\n        emotionInputDetector.mInputManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);\n        emotionInputDetector.sp = activity.getSharedPreferences(SHARE_PREFERENCE_NAME, Context.MODE_PRIVATE);\n        return emotionInputDetector;\n    }\n\n    public EmotionInputDetector bindToContent(View contentView) {\n        mContentView = contentView;\n        return this;\n    }\n\n    public EmotionInputDetector bindToEditText(EditText editText) {\n        mEditText = editText;\n        mEditText.requestFocus();\n        mEditText.setOnTouchListener(new View.OnTouchListener() {\n            @Override\n            public boolean onTouch(View v, MotionEvent event) {\n                if (event.getAction() == MotionEvent.ACTION_UP && mEmotionLayout.isShown()) {\n                    lockContentHeight();\n                    hideEmotionLayout(true);\n\n                    mEditText.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            unlockContentHeightDelayed();\n                        }\n                    }, 200L);\n                }\n                return false;\n            }\n        });\n\n        mEditText.addTextChangedListener(new TextWatcher() {\n            @Override\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n\n            }\n\n            @Override\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n                if (s.length() > 0) {\n                    mAddButton.setVisibility(View.GONE);\n                    mSendButton.setVisibility(View.VISIBLE);\n                } else {\n                    mAddButton.setVisibility(View.VISIBLE);\n                    mSendButton.setVisibility(View.GONE);\n                }\n            }\n\n            @Override\n            public void afterTextChanged(Editable s) {\n\n            }\n        });\n\n        return this;\n    }\n\n    public EmotionInputDetector bindToEmotionButton(View emotionButton) {\n        emotionButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mEmotionLayout.isShown()) {\n                    if (isShowAdd) {\n                        mViewPager.setCurrentItem(0);\n                        isShowEmotion = true;\n                        isShowAdd = false;\n                    } else {\n                        lockContentHeight();\n                        hideEmotionLayout(true);\n                        isShowEmotion = false;\n                        unlockContentHeightDelayed();\n                    }\n                } else {\n                    if (isSoftInputShown()) {\n                        lockContentHeight();\n                        showEmotionLayout();\n                        unlockContentHeightDelayed();\n                    } else {\n                        showEmotionLayout();\n                    }\n                    mViewPager.setCurrentItem(0);\n                    isShowEmotion = true;\n                }\n            }\n        });\n        return this;\n    }\n\n    public EmotionInputDetector bindToAddButton(View addButton) {\n        mAddButton = addButton;\n        addButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (mEmotionLayout.isShown()) {\n                    if (isShowEmotion) {\n                        mViewPager.setCurrentItem(1);\n                        isShowAdd = true;\n                        isShowEmotion = false;\n                    } else {\n                        lockContentHeight();\n                        hideEmotionLayout(true);\n                        isShowAdd = false;\n                        unlockContentHeightDelayed();\n                    }\n                } else {\n                    if (isSoftInputShown()) {\n                        lockContentHeight();\n                        showEmotionLayout();\n                        unlockContentHeightDelayed();\n                    } else {\n                        showEmotionLayout();\n                    }\n                    mViewPager.setCurrentItem(1);\n                    isShowAdd = true;\n                }\n            }\n        });\n        return this;\n    }\n\n    public EmotionInputDetector bindToSendButton(View sendButton) {\n        mSendButton = sendButton;\n        sendButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                mAddButton.setVisibility(View.VISIBLE);\n                mSendButton.setVisibility(View.GONE);\n                MessageInfo messageInfo = new MessageInfo();\n                messageInfo.setContent(mEditText.getText().toString());\n                messageInfo.setFileType(Constants.CHAT_FILE_TYPE_TEXT);\n                EventBus.getDefault().post(messageInfo);\n                mEditText.setText(\"\");\n            }\n        });\n        return this;\n    }\n\n    public EmotionInputDetector bindToVoiceButton(final ImageView voiceButton) {\n        voiceButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (isShowVoice) {\n                    voiceButton.setImageResource(R.mipmap.icon_voice);\n                } else {\n                    voiceButton.setImageResource(R.mipmap.icon_keyboard);\n\n                }\n\n                isShowVoice = !isShowVoice;\n\n                hideEmotionLayout(false);\n                hideSoftInput();\n                mVoiceText.setVisibility(mVoiceText.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);\n                mEditText.setVisibility(mVoiceText.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);\n            }\n        });\n        return this;\n    }\n\n    public EmotionInputDetector bindToVoiceText(TextView voiceText) {\n        mVoiceText = voiceText;\n        mVoiceText.setOnTouchListener(new View.OnTouchListener() {\n            @Override\n            public boolean onTouch(View v, MotionEvent event) {\n                // 获得x轴坐标\n                int x = (int) event.getX();\n                // 获得y轴坐标\n                int y = (int) event.getY();\n\n                switch (event.getAction()) {\n                    case MotionEvent.ACTION_DOWN:\n                        mVoicePop.showAtLocation(v, Gravity.CENTER, 0, 0);\n                        mVoiceText.setText(\"松开结束\");\n                        mPopVoiceText.setText(\"手指上滑，取消发送\");\n                        mVoiceText.setTag(\"1\");\n                        mAudioRecorderUtils.startRecord(mActivity);\n                        break;\n                    case MotionEvent.ACTION_MOVE:\n                        if (wantToCancel(x, y)) {\n                            mVoiceText.setText(\"松开结束\");\n                            mPopVoiceText.setText(\"松开手指，取消发送\");\n                            mVoiceText.setTag(\"2\");\n                        } else {\n                            mVoiceText.setText(\"松开结束\");\n                            mPopVoiceText.setText(\"手指上滑，取消发送\");\n                            mVoiceText.setTag(\"1\");\n                        }\n                        break;\n                    case MotionEvent.ACTION_UP:\n                        mVoicePop.dismiss();\n                        if (mVoiceText.getTag().equals(\"2\")) {\n                            //取消录音（删除录音文件）\n                            mAudioRecorderUtils.cancelRecord();\n                        } else {\n                            //结束录音（保存录音文件）\n                            mAudioRecorderUtils.stopRecord();\n                        }\n                        mVoiceText.setText(\"按住说话\");\n                        mVoiceText.setTag(\"3\");\n                        mVoiceText.setVisibility(View.GONE);\n                        mEditText.setVisibility(View.VISIBLE);\n                        break;\n                }\n                return true;\n            }\n        });\n        return this;\n    }\n\n    private boolean wantToCancel(int x, int y) {\n        // 超过按钮的宽度\n        if (x < 0 || x > mVoiceText.getWidth()) {\n            return true;\n        }\n        // 超过按钮的高度\n        if (y < -50 || y > mVoiceText.getHeight() + 50) {\n            return true;\n        }\n        return false;\n    }\n\n    public EmotionInputDetector setEmotionView(View emotionView) {\n        mEmotionLayout = emotionView;\n        return this;\n    }\n\n    public EmotionInputDetector setViewPager(ViewPager viewPager) {\n        mViewPager = viewPager;\n        return this;\n    }\n\n    public EmotionInputDetector build() {\n        mActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN |\n                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);\n        hideSoftInput();\n        mAudioRecorderUtils = new AudioRecorderUtils();\n\n        View view = View.inflate(mActivity, R.layout.layout_microphone, null);\n        mVoicePop = new PopupWindowFactory(mActivity, view);\n\n        //PopupWindow布局文件里面的控件\n        final ImageView mImageView = (ImageView) view.findViewById(R.id.iv_recording_icon);\n        final TextView mTextView = (TextView) view.findViewById(R.id.tv_recording_time);\n        mPopVoiceText = (TextView) view.findViewById(R.id.tv_recording_text);\n        //录音回调\n        mAudioRecorderUtils.setOnAudioStatusUpdateListener(new AudioRecorderUtils.OnAudioStatusUpdateListener() {\n\n            //录音中....db为声音分贝，time为录音时长\n            @Override\n            public void onUpdate(double db, long time) {\n                mImageView.getDrawable().setLevel((int) (3000 + 6000 * db / 100));\n                mTextView.setText(Utils.long2String(time));\n            }\n\n            //录音结束，filePath为保存路径\n            @Override\n            public void onStop(long time, String filePath) {\n                mTextView.setText(Utils.long2String(0));\n                MessageInfo messageInfo = new MessageInfo();\n                messageInfo.setFileType(Constants.CHAT_FILE_TYPE_VOICE);\n                messageInfo.setFilepath(filePath);\n                messageInfo.setVoiceTime(time);\n                EventBus.getDefault().post(messageInfo);\n            }\n\n            @Override\n            public void onError() {\n                mVoiceText.setVisibility(View.GONE);\n                mEditText.setVisibility(View.VISIBLE);\n            }\n        });\n        return this;\n    }\n\n    public boolean interceptBackPress() {\n        if (mEmotionLayout.isShown()) {\n            hideEmotionLayout(false);\n            return true;\n        }\n        return false;\n    }\n\n    private void showEmotionLayout() {\n        int softInputHeight = getSupportSoftInputHeight();\n        if (softInputHeight == 0) {\n            softInputHeight = sp.getInt(SHARE_PREFERENCE_TAG, 768);\n        }\n        hideSoftInput();\n        Log.e(TAG, \"showEmotionLayout: ->\" + softInputHeight );\n        mEmotionLayout.getLayoutParams().height = softInputHeight;\n        mEmotionLayout.setVisibility(View.VISIBLE);\n    }\n\n    public void hideEmotionLayout(boolean showSoftInput) {\n        if (mEmotionLayout.isShown()) {\n            mEmotionLayout.setVisibility(View.GONE);\n            if (showSoftInput) {\n                showSoftInput();\n            }\n        }\n    }\n\n    private void lockContentHeight() {\n        Log.e(TAG, \"lockContentHeight: ->\" + mContentView.getHeight());\n        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentView.getLayoutParams();\n        params.height = mContentView.getHeight();\n        params.weight = 0.0F;\n    }\n\n    private void unlockContentHeightDelayed() {\n        mEditText.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                ((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F;\n            }\n        }, 200L);\n    }\n\n    private void showSoftInput() {\n        mEditText.requestFocus();\n        mEditText.post(new Runnable() {\n            @Override\n            public void run() {\n                mInputManager.showSoftInput(mEditText, 0);\n            }\n        });\n    }\n\n    public void hideSoftInput() {\n        mInputManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);\n    }\n\n    private boolean isSoftInputShown() {\n        return getSupportSoftInputHeight() != 0;\n    }\n\n    private int getSupportSoftInputHeight() {\n        Rect r = new Rect();\n        mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);\n        int screenHeight = mActivity.getWindow().getDecorView().getRootView().getHeight();\n        int softInputHeight = screenHeight - r.bottom;\n        if (Build.VERSION.SDK_INT >= 20) {\n            // When SDK Level >= 20 (Android L), the softInputHeight will contain the height of softButtonsBar (if has)\n            softInputHeight = softInputHeight - getSoftButtonsBarHeight();\n        }\n        if (softInputHeight < 0) {\n            Log.w(\"EmotionInputDetector\", \"Warning: value of softInputHeight is below zero!\");\n        }\n        if (softInputHeight > 0) {\n            Log.e(TAG, \"getSupportSoftInputHeight: ->\" + softInputHeight );\n            sp.edit().putInt(SHARE_PREFERENCE_TAG, softInputHeight).apply();\n        }\n        return softInputHeight;\n    }\n\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n    private int getSoftButtonsBarHeight() {\n        DisplayMetrics metrics = new DisplayMetrics();\n        mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);\n        int usableHeight = metrics.heightPixels;\n        mActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);\n        int realHeight = metrics.heightPixels;\n        if (realHeight > usableHeight) {\n            return realHeight - usableHeight;\n        } else {\n            return 0;\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/GifTextView.java",
    "content": "package com.rance.chatui.widget;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.os.Handler;\nimport android.support.v7.widget.AppCompatTextView;\nimport android.text.Spannable;\nimport android.text.SpannableString;\nimport android.text.style.ImageSpan;\nimport android.util.AttributeSet;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport com.rance.chatui.util.EmotionUtils;\nimport com.rance.chatui.util.GifOpenHelper;\nimport com.rance.chatui.util.Utils;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class GifTextView extends AppCompatTextView {\n    /**\n     * 注：如果获取的gif帧与帧之间的时间间隔都不相同，建议调个固定的，最好的方法是将gif图的间隔设置相同\n     */\n    private static final int DELAYED = 600;\n\n    /**\n     * @author Dragon SpanInfo\n     *         类用于存储一个要显示的图片（动态或静态）的信息，包括分解后的每一帧mapList、替代文字的起始位置、终止位置\n     *         、帧的总数、当前需要显示的帧、帧与帧之间的时间间隔\n     */\n    private class SpanInfo {\n        ArrayList<Bitmap> mapList;\n        @SuppressWarnings(\"unused\")\n        int start, end, frameCount, currentFrameIndex, delay;\n\n        public SpanInfo() {\n            mapList = new ArrayList<Bitmap>();\n            start = end = frameCount = currentFrameIndex = delay = 0;\n        }\n    }\n\n    /**\n     * spanInfoList 是一个SpanInfo的list ,用于处理一个TextView中出现多个要匹配的图片的情况\n     */\n    private ArrayList<SpanInfo> spanInfoList = null;\n    private Handler handler; // 用于处理从子线程TextView传来的消息\n    private String myText; // 存储textView应该显示的文本\n\n    /**\n     * 这三个构造方法一个也不要少，否则会产生CastException，注意在这三个构造函数中都为spanInfoList实例化，可能有些浪费\n     * ，但保证不会有空指针异常\n     *\n     * @param context\n     * @param attrs\n     * @param defStyle\n     */\n    @SuppressLint(\"NewApi\")\n    public GifTextView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        GifTextView.this.setFocusableInTouchMode(false);\n    }\n\n    @SuppressLint(\"NewApi\")\n    public GifTextView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        GifTextView.this.setFocusableInTouchMode(false);\n    }\n\n    @SuppressLint(\"NewApi\")\n    public GifTextView(Context context) {\n        super(context);\n        GifTextView.this.setFocusableInTouchMode(false);\n    }\n\n    /**\n     * 对要显示在textView上的文本进行解析，看看是否文本中有需要与Gif或者静态图片匹配的文本 若有，那么调用parseGif\n     * 对该文本对应的Gif图片进行解析 或者嗲用parseBmp解析静态图片\n     *\n     * @param inputStr\n     */\n    private boolean parseText(String inputStr) {\n        myText = inputStr;\n        String regexEmotion = \"\\\\[([\\u4e00-\\u9fa5\\\\w])+\\\\]\";\n        Pattern patternEmotion = Pattern.compile(regexEmotion);\n        Matcher mMatcher = patternEmotion.matcher(inputStr);\n\n        boolean hasGif = false;\n        while (mMatcher.find()) {\n            String faceName = mMatcher.group();\n            Integer faceId = null;\n            /**\n             * 这里匹配时用到了图片库，即一个专门存放图片id和其匹配的名称的静态对象，这两个静态对象放在了FaceData.java\n             * 中，并采用了静态块的方法进行了初始化，不会有空指针异常\n             */\n            if ((faceId = EmotionUtils.EMPTY_GIF_MAP.get(faceName)) != null) {\n                if (isGif) {\n                    parseGif(faceId, mMatcher.start(), mMatcher.end());\n                } else {\n                    parseBmp(faceId, mMatcher.start(), mMatcher.end());\n                }\n            }\n            hasGif = true;\n        }\n        return hasGif;\n    }\n\n    /**\n     * 对静态图片进行解析：\n     * 创建一个SpanInfo对象，帧数设为1，按照下面的参数设置，最后不要忘记将SpanInfo对象添加进spanInfoList中， 否则不会显示\n     *\n     * @param resourceId\n     * @param start\n     * @param end\n     */\n    @SuppressWarnings(\"unused\")\n    private void parseBmp(int resourceId, int start, int end) {\n        Bitmap bitmap = BitmapFactory.decodeResource(getContext()\n                .getResources(), resourceId);\n        ImageSpan imageSpan = new ImageSpan(getContext(), bitmap);\n        SpanInfo spanInfo = new SpanInfo();\n        spanInfo.currentFrameIndex = 0;\n        spanInfo.frameCount = 1;\n        spanInfo.start = start;\n        spanInfo.end = end;\n        spanInfo.delay = 100;\n        spanInfo.mapList.add(bitmap);\n        spanInfoList.add(spanInfo);\n\n    }\n\n    /**\n     * 解析Gif图片，与静态图片唯一的不同是这里需要调用GifOpenHelper类读取Gif返回一系一组bitmap（用for 循环把这一\n     * 组的bitmap存储在SpanInfo.mapList中，此时的frameCount参数也大于1了）\n     *\n     * @param resourceId\n     * @param start\n     * @param end\n     */\n    private void parseGif(int resourceId, int start, int end) {\n\n        GifOpenHelper helper = new GifOpenHelper();\n        helper.read(getContext().getResources().openRawResource(resourceId));\n        SpanInfo spanInfo = new SpanInfo();\n        spanInfo.currentFrameIndex = 0;\n        spanInfo.frameCount = helper.getFrameCount();\n        spanInfo.start = start;\n        spanInfo.end = end;\n        spanInfo.mapList.add(helper.getImage());\n        for (int i = 1; i < helper.getFrameCount(); i++) {\n            spanInfo.mapList.add(helper.nextBitmap());\n        }\n        spanInfo.delay = helper.nextDelay(); // 获得每一帧之间的延迟\n        spanInfoList.add(spanInfo);\n\n    }\n\n    private boolean isGif;\n\n    /**\n     * GifTextView 与外部对象的接口，以后设置文本内容时使用setSpanText() 而不再是setText();\n     *\n     * @param handler\n     * @param text\n     */\n    public void setSpanText(Handler handler, final String text, boolean isGif) {\n        this.handler = handler; // 获得UI的Handler\n        this.isGif = isGif;\n        spanInfoList = new ArrayList<SpanInfo>();\n        if (parseText(text)) {// 对String对象进行解析\n            if (parseMessage(this)) {\n                startPost();\n            }\n        } else {\n            setText(myText);\n        }\n    }\n\n    public boolean parseMessage(GifTextView gifTextView) {\n        if (gifTextView.myText != null && !gifTextView.myText.equals(\"\")) {\n            SpannableString sb = new SpannableString(\"\" + gifTextView.myText); // 获得要显示的文本\n            int gifCount = 0;\n            SpanInfo info = null;\n            for (int i = 0; i < gifTextView.spanInfoList.size(); i++) { // for循环，处理显示多个图片的问题\n                info = gifTextView.spanInfoList.get(i);\n                if (info.mapList.size() > 1) {\n                            /*\n                             * gifCount用来区分是Gif还是BMP，若是gif gifCount>0\n\t\t\t\t\t\t\t * ,否则gifCount=0\n\t\t\t\t\t\t\t */\n                    gifCount++;\n\n                }\n                Bitmap bitmap = info.mapList\n                        .get(info.currentFrameIndex);\n                info.currentFrameIndex = (info.currentFrameIndex + 1)\n                        % (info.frameCount);\n                /**\n                 * currentFrameIndex\n                 * 用于控制当前应该显示的帧的序号，每次显示之后currentFrameIndex 应该加1\n                 * ，加到frameCount后再变成0循环显示\n                 */\n                int size = Utils.dp2px(gifTextView.getContext(), 30);\n                if (gifCount != 0) {\n                    bitmap = Bitmap.createScaledBitmap(bitmap, size,\n                            size, true);\n\n                } else {\n                    bitmap = Bitmap.createScaledBitmap(bitmap, size,\n                            size, true);\n                }\n                ImageSpan imageSpan = new ImageSpan(gifTextView.getContext(),\n                        bitmap);\n                if (info.end <= sb.length()) {\n                    sb.setSpan(imageSpan, info.start, info.end,\n                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);\n                } else {\n                    break;\n                }\n\n            }\n            // 对所有的图片对应的ImageSpan完成设置后，调用TextView的setText方法设置文本\n            gifTextView.setText(sb);\n            if (gifCount != 0) {\n                return true;\n            } else {\n                return false;\n            }\n        }\n        return false;\n    }\n\n    public TextRunnable rTextRunnable;\n\n    public void startPost() {\n        rTextRunnable = new TextRunnable(this); // 生成Runnable对象\n        handler.post(rTextRunnable); // 利用UI线程的Handler 将r添加进消息队列中。\n    }\n\n    public static final class TextRunnable implements Runnable {\n        private final WeakReference<GifTextView> mWeakReference;\n\n        public TextRunnable(GifTextView f) {\n            mWeakReference = new WeakReference<GifTextView>(f);\n        }\n\n        @Override\n        public void run() {\n            GifTextView gifTextView = mWeakReference.get();\n            if (gifTextView != null) {\n                /**\n                 * 这一步是为了节省内存而是用，即如果文本中只有静态图片没有动态图片，那么该线程就此终止，不会重复执行\n                 * 。而如果有动图，那么会一直执行\n                 */\n                if (gifTextView.parseMessage(gifTextView)) {\n                    gifTextView.handler.postDelayed(this, DELAYED);\n                }\n            }\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/IndicatorView.java",
    "content": "package com.rance.chatui.widget;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.widget.LinearLayout;\n\nimport com.rance.chatui.R;\nimport com.rance.chatui.util.Utils;\n\nimport java.util.ArrayList;\n\n/**\n * 作者：Rance on 2016/11/29 10:47\n * 邮箱：rance935@163.com\n * 自定义表情底部指示器\n */\npublic class IndicatorView extends LinearLayout {\n\n    private Context mContext;\n    private ArrayList<View> mImageViews;//所有指示器集合\n    private int size = 6;\n    private int marginSize = 15;\n    private int pointSize;//指示器的大小\n    private int marginLeft;//间距\n\n    public IndicatorView(Context context) {\n        this(context, null);\n    }\n\n    public IndicatorView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        mContext = context;\n        pointSize = Utils.dp2px(context, size);\n        marginLeft = Utils.dp2px(context, marginSize);\n    }\n\n    /**\n     * 初始化指示器\n     *\n     * @param count 指示器的数量\n     */\n    public void initIndicator(int count) {\n        mImageViews = new ArrayList<>();\n        this.removeAllViews();\n        LayoutParams lp;\n        for (int i = 0; i < count; i++) {\n            View v = new View(mContext);\n            lp = new LayoutParams(pointSize, pointSize);\n            if (i != 0)\n                lp.leftMargin = marginLeft;\n            v.setLayoutParams(lp);\n            if (i == 0) {\n                v.setBackgroundResource(R.drawable.bg_circle_white);\n            } else {\n                v.setBackgroundResource(R.drawable.bg_circle_gary);\n            }\n            mImageViews.add(v);\n            this.addView(v);\n        }\n    }\n\n    /**\n     * 页面移动时切换指示器\n     */\n    public void playByStartPointToNext(int startPosition, int nextPosition) {\n        if (startPosition < 0 || nextPosition < 0 || nextPosition == startPosition) {\n            startPosition = nextPosition = 0;\n        }\n        final View ViewStrat = mImageViews.get(startPosition);\n        final View ViewNext = mImageViews.get(nextPosition);\n        ViewNext.setBackgroundResource(R.drawable.bg_circle_white);\n        ViewStrat.setBackgroundResource(R.drawable.bg_circle_gary);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/NoScrollViewPager.java",
    "content": "package com.rance.chatui.widget;\n\nimport android.content.Context;\nimport android.support.v4.view.ViewPager;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\n\n/**\n * 作者：Rance on 2016/11/25 16:55\n * 邮箱：rance935@163.com\n */\npublic class NoScrollViewPager extends ViewPager {\n\n    public NoScrollViewPager(Context context) {\n        super(context);\n    }\n\n    public NoScrollViewPager(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent arg0) {\n        return false;\n    }\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent arg0) {\n        return false;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/rance/chatui/widget/StateButton.java",
    "content": "package com.rance.chatui.widget;\n\nimport android.content.Context;\nimport android.content.res.ColorStateList;\nimport android.content.res.TypedArray;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.GradientDrawable;\nimport android.graphics.drawable.StateListDrawable;\nimport android.support.annotation.ColorInt;\nimport android.support.annotation.FloatRange;\nimport android.support.annotation.IntRange;\nimport android.support.v7.widget.AppCompatButton;\nimport android.util.AttributeSet;\n\nimport com.rance.chatui.R;\n\n/**\n * 作者：Rance on 2016/11/18 16:53\n * 邮箱：rance935@163.com\n * 自定义按钮  详情请参考https://github.com/niniloveyou/StateButton\n */\npublic class StateButton extends AppCompatButton {\n\n    //text color\n    private int mNormalTextColor = 0;\n    private int mPressedTextColor = 0;\n    private int mUnableTextColor = 0;\n    ColorStateList mTextColorStateList;\n\n    //animation duration\n    private int mDuration = 0;\n\n    //radius\n    private float mRadius = 0;\n    private boolean mRound;\n\n    //stroke\n    private float mStrokeDashWidth = 0;\n    private float mStrokeDashGap = 0;\n    private int mNormalStrokeWidth = 0;\n    private int mPressedStrokeWidth = 0;\n    private int mUnableStrokeWidth = 0;\n    private int mNormalStrokeColor = 0;\n    private int mPressedStrokeColor = 0;\n    private int mUnableStrokeColor = 0;\n\n    //background color\n    private int mNormalBackgroundColor = 0;\n    private int mPressedBackgroundColor = 0;\n    private int mUnableBackgroundColor = 0;\n\n    private GradientDrawable mNormalBackground;\n    private GradientDrawable mPressedBackground;\n    private GradientDrawable mUnableBackground;\n\n    private int[][] states;\n\n    StateListDrawable mStateBackground;\n\n    public StateButton(Context context) {\n        this(context, null);\n    }\n\n    public StateButton(Context context, AttributeSet attrs) {\n        this(context, attrs, android.support.v7.appcompat.R.attr.buttonStyle);\n    }\n\n    public StateButton(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        setup(attrs);\n    }\n\n    private void setup(AttributeSet attrs) {\n\n        states = new int[4][];\n\n        Drawable drawable = getBackground();\n        if(drawable != null && drawable instanceof StateListDrawable){\n            mStateBackground = (StateListDrawable) drawable;\n        }else{\n            mStateBackground = new StateListDrawable();\n        }\n\n        mNormalBackground = new GradientDrawable();\n        mPressedBackground = new GradientDrawable();\n        mUnableBackground = new GradientDrawable();\n\n        //pressed, focused, normal, unable\n        states[0] = new int[] { android.R.attr.state_pressed, android.R.attr.state_enabled };\n        states[1] = new int[] { android.R.attr.state_enabled, android.R.attr.state_focused };\n        states[2] = new int[] { android.R.attr.state_enabled };\n        states[3] = new int[] { android.R.attr.state_window_focused };\n\n        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StateButton);\n\n        //get original text color as default\n        //set text color\n        mTextColorStateList = getTextColors();\n        int mDefaultNormalTextColor = mTextColorStateList.getColorForState(states[2], getCurrentTextColor());\n        int mDefaultPressedTextColor = mTextColorStateList.getColorForState(states[0], getCurrentTextColor());\n        int mDefaultUnableTextColor = mTextColorStateList.getColorForState(states[3], getCurrentTextColor());\n        mNormalTextColor = a.getColor(R.styleable.StateButton_normalTextColor, mDefaultNormalTextColor);\n        mPressedTextColor = a.getColor(R.styleable.StateButton_pressedTextColor, mDefaultPressedTextColor);\n        mUnableTextColor = a.getColor(R.styleable.StateButton_unableTextColor, mDefaultUnableTextColor);\n        setTextColor();\n\n        //set animation duration\n        mDuration = a.getInteger(R.styleable.StateButton_animationDuration, mDuration);\n        mStateBackground.setEnterFadeDuration(mDuration);\n\n        //set background color\n        mNormalBackgroundColor = a.getColor(R.styleable.StateButton_normalBackgroundColor, 0);\n        mPressedBackgroundColor = a.getColor(R.styleable.StateButton_pressedBackgroundColor, 0);\n        mUnableBackgroundColor = a.getColor(R.styleable.StateButton_unableBackgroundColor, 0);\n        mNormalBackground.setColor(mNormalBackgroundColor);\n        mPressedBackground.setColor(mPressedBackgroundColor);\n        mUnableBackground.setColor(mUnableBackgroundColor);\n\n        //set radius\n        mRadius = a.getDimensionPixelSize(R.styleable.StateButton_radius, 0);\n        mRound = a.getBoolean(R.styleable.StateButton_round, false);\n        mNormalBackground.setCornerRadius(mRadius);\n        mPressedBackground.setCornerRadius(mRadius);\n        mUnableBackground.setCornerRadius(mRadius);\n\n        //set stroke\n        mStrokeDashWidth = a.getDimensionPixelSize(R.styleable.StateButton_strokeDashWidth, 0);\n        mStrokeDashGap = a.getDimensionPixelSize(R.styleable.StateButton_strokeDashWidth, 0);\n        mNormalStrokeWidth = a.getDimensionPixelSize(R.styleable.StateButton_normalStrokeWidth, 0);\n        mPressedStrokeWidth = a.getDimensionPixelSize(R.styleable.StateButton_pressedStrokeWidth, 0);\n        mUnableStrokeWidth = a.getDimensionPixelSize(R.styleable.StateButton_unableStrokeWidth, 0);\n        mNormalStrokeColor = a.getColor(R.styleable.StateButton_normalStrokeColor, 0);\n        mPressedStrokeColor = a.getColor(R.styleable.StateButton_pressedStrokeColor, 0);\n        mUnableStrokeColor = a.getColor(R.styleable.StateButton_unableStrokeColor, 0);\n        setStroke();\n\n        //set background\n        mStateBackground.addState(states[0], mPressedBackground);\n        mStateBackground.addState(states[1], mPressedBackground);\n        mStateBackground.addState(states[2], mNormalBackground);\n        mStateBackground.addState(states[3], mUnableBackground);\n        setBackgroundDrawable(mStateBackground);\n        a.recycle();\n    }\n\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        setRound(mRound);\n    }\n\n    /****************** stroke color *********************/\n\n    public void setNormalStrokeColor(@ColorInt int normalStrokeColor) {\n        this.mNormalStrokeColor = normalStrokeColor;\n        setStroke(mNormalBackground, mNormalStrokeColor, mNormalStrokeWidth);\n    }\n\n    public void setPressedStrokeColor(@ColorInt int pressedStrokeColor) {\n        this.mPressedStrokeColor = pressedStrokeColor;\n        setStroke(mPressedBackground, mPressedStrokeColor, mPressedStrokeWidth);\n    }\n\n    public void setUnableStrokeColor(@ColorInt int unableStrokeColor) {\n        this.mUnableStrokeColor = unableStrokeColor;\n        setStroke(mUnableBackground, mUnableStrokeColor, mUnableStrokeWidth);\n    }\n\n    public void setStateStrokeColor(@ColorInt int normal, @ColorInt int pressed, @ColorInt int unable){\n        mNormalStrokeColor = normal;\n        mPressedStrokeColor = pressed;\n        mUnableStrokeColor = unable;\n        setStroke();\n    }\n\n    /****************** stroke width *********************/\n\n    public void setNormalStrokeWidth(int normalStrokeWidth) {\n        this.mNormalStrokeWidth = normalStrokeWidth;\n        setStroke(mNormalBackground, mNormalStrokeColor, mNormalStrokeWidth);\n    }\n\n    public void setPressedStrokeWidth(int pressedStrokeWidth) {\n        this.mPressedStrokeWidth = pressedStrokeWidth;\n        setStroke(mPressedBackground, mPressedStrokeColor, mPressedStrokeWidth);\n    }\n\n    public void setUnableStrokeWidth(int unableStrokeWidth) {\n        this.mUnableStrokeWidth = unableStrokeWidth;\n        setStroke(mUnableBackground, mUnableStrokeColor, mUnableStrokeWidth);\n    }\n\n    public void setStateStrokeWidth(int normal, int pressed, int unable){\n        mNormalStrokeWidth = normal;\n        mPressedStrokeWidth = pressed;\n        mUnableStrokeWidth= unable;\n        setStroke();\n    }\n\n    public void setStrokeDash(float strokeDashWidth, float strokeDashGap) {\n        this.mStrokeDashWidth = strokeDashWidth;\n        this.mStrokeDashGap = strokeDashWidth;\n        setStroke();\n    }\n\n    private void setStroke(){\n        setStroke(mNormalBackground, mNormalStrokeColor, mNormalStrokeWidth);\n        setStroke(mPressedBackground, mPressedStrokeColor, mPressedStrokeWidth);\n        setStroke(mUnableBackground, mUnableStrokeColor, mUnableStrokeWidth);\n    }\n\n    private void setStroke(GradientDrawable mBackground, int mStrokeColor, int mStrokeWidth) {\n        mBackground.setStroke(mStrokeWidth, mStrokeColor, mStrokeDashWidth, mStrokeDashGap);\n    }\n\n    /********************   radius  *******************************/\n\n    public void setRadius(@FloatRange(from = 0) float radius) {\n        this.mRadius = radius;\n        mNormalBackground.setCornerRadius(mRadius);\n        mPressedBackground.setCornerRadius(mRadius);\n        mUnableBackground.setCornerRadius(mRadius);\n    }\n\n    public void setRound(boolean round){\n        this.mRound = round;\n        int height = getMeasuredHeight();\n        if(mRound){\n            setRadius(height / 2f);\n        }\n    }\n\n    public void setRadius(float[] radii){\n        mNormalBackground.setCornerRadii(radii);\n        mPressedBackground.setCornerRadii(radii);\n        mUnableBackground.setCornerRadii(radii);\n    }\n\n    /********************  background color  **********************/\n\n    public void setStateBackgroundColor(@ColorInt int normal, @ColorInt int pressed, @ColorInt int unable){\n        mPressedBackgroundColor = normal;\n        mNormalBackgroundColor = pressed;\n        mUnableBackgroundColor = unable;\n        mNormalBackground.setColor(mNormalBackgroundColor);\n        mPressedBackground.setColor(mPressedBackgroundColor);\n        mUnableBackground.setColor(mUnableBackgroundColor);\n    }\n\n    public void setNormalBackgroundColor(@ColorInt int normalBackgroundColor) {\n        this.mNormalBackgroundColor = normalBackgroundColor;\n        mNormalBackground.setColor(mNormalBackgroundColor);\n    }\n\n    public void setPressedBackgroundColor(@ColorInt int pressedBackgroundColor) {\n        this.mPressedBackgroundColor = pressedBackgroundColor;\n        mPressedBackground.setColor(mPressedBackgroundColor);\n    }\n\n    public void setUnableBackgroundColor(@ColorInt int unableBackgroundColor) {\n        this.mUnableBackgroundColor = unableBackgroundColor;\n        mUnableBackground.setColor(mUnableBackgroundColor);\n    }\n\n    /*******************alpha animation duration********************/\n    public void setAnimationDuration(@IntRange(from = 0)int duration){\n        this.mDuration = duration;\n        mStateBackground.setEnterFadeDuration(mDuration);\n    }\n\n    /***************  text color   ***********************/\n\n    private void setTextColor() {\n        int[] colors = new int[] {mPressedTextColor, mPressedTextColor, mNormalTextColor, mUnableTextColor};\n        mTextColorStateList = new ColorStateList(states, colors);\n        setTextColor(mTextColorStateList);\n    }\n\n    public void setStateTextColor(@ColorInt int normal, @ColorInt int pressed, @ColorInt int unable){\n        this.mNormalTextColor = normal;\n        this.mPressedTextColor = pressed;\n        this.mUnableTextColor = unable;\n        setTextColor();\n    }\n\n    public void setNormalTextColor(@ColorInt int normalTextColor) {\n        this.mNormalTextColor = normalTextColor;\n        setTextColor();\n\n    }\n\n    public void setPressedTextColor(@ColorInt int pressedTextColor) {\n        this.mPressedTextColor = pressedTextColor;\n        setTextColor();\n    }\n\n    public void setUnableTextColor(@ColorInt int unableTextColor) {\n        this.mUnableTextColor = unableTextColor;\n        setTextColor();\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_circle_gary.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"oval\"\n       android:useLevel=\"false\" >\n\n    <solid android:color=\"@color/divider_line\" />\n\n    <stroke\n            android:width=\"1dp\"\n            android:color=\"@color/divider_line\" />\n\n    <size\n            android:height=\"5dp\"\n            android:width=\"5dp\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_circle_white.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"oval\"\n       android:useLevel=\"false\" >\n\n    <solid android:color=\"@color/white\" />\n\n    <stroke\n            android:width=\"1dp\"\n            android:color=\"@color/white\" />\n\n    <size\n            android:height=\"5dp\"\n            android:width=\"5dp\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_surname.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n    <solid android:color=\"@color/text_sub\" />\n    <size\n        android:height=\"20dp\"\n        android:width=\"20dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/corners_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n\n    <solid android:color=\"@color/divider_line\"/>\n\n    <corners\n            android:radius=\"5.0dip\"/>\n\n    <padding\n            android:bottom=\"1.0dip\"\n            android:left=\"1.0dip\"\n            android:right=\"1.0dip\"\n            android:top=\"1.0dip\"/>\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/corners_edit_white.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n\n    <solid android:color=\"@color/white\"/>\n\n    <corners\n            android:radius=\"5.0dip\"/>\n\n    <padding\n            android:bottom=\"1.0dip\"\n            android:left=\"1.0dip\"\n            android:right=\"1.0dip\"\n            android:top=\"1.0dip\"/>\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/divider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"line\">\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/record_microphone.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <item android:id=\"@android:id/background\" android:drawable=\"@mipmap/record_top\" />\n    <item android:id=\"@android:id/progress\" >\n        <clip android:drawable=\"@mipmap/record_bottom\" android:gravity=\"bottom\" android:clipOrientation=\"vertical\" />\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/record_microphone_bj.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <corners\n        android:radius=\"8dp\" />\n\n    <solid\n        android:color=\"@color/black\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/voice_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:oneshot=\"false\">\n    <item\n        android:drawable=\"@mipmap/icon_voice_left3\"\n        android:duration=\"300\" />\n    <item\n        android:drawable=\"@mipmap/icon_voice_left1\"\n        android:duration=\"300\" />\n    <item\n        android:drawable=\"@mipmap/icon_voice_left2\"\n        android:duration=\"300\" />\n    <item\n        android:drawable=\"@mipmap/icon_voice_left3\"\n        android:duration=\"300\" />\n</animation-list>"
  },
  {
    "path": "app/src/main/res/drawable/voice_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animation-list xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:oneshot=\"false\">\n    <item\n        android:drawable=\"@mipmap/icon_voice_right3\"\n        android:duration=\"300\" />\n    <item\n        android:drawable=\"@mipmap/icon_voice_right1\"\n        android:duration=\"300\" />\n    <item\n        android:drawable=\"@mipmap/icon_voice_right2\"\n        android:duration=\"300\" />\n    <item\n        android:drawable=\"@mipmap/icon_voice_right3\"\n        android:duration=\"300\" />\n</animation-list>"
  },
  {
    "path": "app/src/main/res/layout/activity_contact.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <android.support.v7.widget.RecyclerView\n        android:id=\"@+id/rv_contact\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n    </android.support.v7.widget.RecyclerView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_full_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/full_lay\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\">\n\n    <ImageView\n        android:id=\"@+id/full_image\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerCrop\"/>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\"\n    android:orientation=\"vertical\">\n\n    <android.support.v7.widget.RecyclerView\n        android:id=\"@+id/chat_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:background=\"@color/bg_content\"/>\n\n    <include\n        layout=\"@layout/include_reply_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_contact.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"80dp\"\n    android:layout_marginTop=\"10dp\"\n    android:layout_marginBottom=\"10dp\"\n    android:orientation=\"vertical\">\n    <TextView\n        android:id=\"@+id/tv_surname\"\n        android:text=\"李\"\n        android:paddingLeft=\"14dp\"\n        android:paddingRight=\"14dp\"\n        android:paddingTop=\"10dp\"\n        android:paddingBottom=\"10dp\"\n        android:textColor=\"@color/white\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginLeft=\"20dp\"\n        android:textSize=\"28sp\"\n        android:background=\"@drawable/bg_surname\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n\n    <LinearLayout\n        android:orientation=\"vertical\"\n        android:layout_toRightOf=\"@+id/tv_surname\"\n        android:layout_marginLeft=\"20dp\"\n        android:layout_centerVertical=\"true\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\">\n        <TextView\n            android:id=\"@+id/tv_name\"\n            android:text=\"李老大\"\n            android:textColor=\"@color/black\"\n            android:textSize=\"18sp\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/tv_phone\"\n            android:text=\"13843859438\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\" />\n    </LinearLayout>\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_chat_emotion.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#eee\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:gravity=\"center\">\n\n        <android.support.v4.view.ViewPager\n            android:id=\"@+id/fragment_chat_vp\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            />\n    </LinearLayout>\n\n    <com.rance.chatui.widget.IndicatorView\n        android:id=\"@+id/fragment_chat_group\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"30dp\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_chat_function.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"40dp\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0px\"\n        android:layout_weight=\"1\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/chat_function_album\"\n            style=\"@style/text_function_item\"\n            android:drawablePadding=\"5dp\"\n            android:drawableTop=\"@mipmap/icon_album\"\n            android:gravity=\"center\"\n            android:text=\"相册\" />\n\n        <TextView\n            android:id=\"@+id/chat_function_capture\"\n            style=\"@style/text_function_item\"\n            android:drawablePadding=\"5dp\"\n            android:drawableTop=\"@mipmap/icon_capture\"\n            android:gravity=\"center\"\n            android:text=\"拍照\" />\n\n        <TextView\n            android:id=\"@+id/chat_function_contact\"\n            style=\"@style/text_function_item\"\n            android:drawablePadding=\"5dp\"\n            android:drawableTop=\"@mipmap/icon_im_contact\"\n            android:gravity=\"center\"\n            android:text=\"联系人\" />\n\n\n        <TextView\n            android:id=\"@+id/chat_function_location\"\n            style=\"@style/text_function_item\"\n            android:drawablePadding=\"5dp\"\n            android:drawableTop=\"@mipmap/icon_im_location\"\n            android:gravity=\"center\"\n            android:text=\"位置\" />\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0px\"\n        android:layout_weight=\"1\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\">\n\n\n        <TextView\n            android:id=\"@+id/chat_function_file\"\n            style=\"@style/text_function_item\"\n            android:drawablePadding=\"5dp\"\n            android:drawableTop=\"@mipmap/icon_im_file\"\n            android:gravity=\"center\"\n            android:text=\"文件\" />\n\n        <TextView\n            android:id=\"@+id/chat_function_cloud\"\n            style=\"@style/text_function_item\"\n            android:drawablePadding=\"5dp\"\n            android:drawableTop=\"@mipmap/icon_cloud\"\n            android:gravity=\"center\"\n            android:text=\"我的资料\" />\n\n        <TextView\n            android:id=\"@+id/chat_blank_one\"\n            style=\"@style/text_function_item\"\n            android:drawablePadding=\"5dp\"\n            android:gravity=\"center\" />\n\n        <TextView\n            android:id=\"@+id/chat_blank_two\"\n            style=\"@style/text_function_item\"\n            android:drawablePadding=\"5dp\"\n            android:gravity=\"center\" />\n    </LinearLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/include_reply_layout.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clickable=\"true\"\n    android:orientation=\"vertical\">\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"1px\"\n        android:background=\"@color/divider_line\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/bg_content\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:paddingBottom=\"6dp\"\n        android:paddingLeft=\"8dp\"\n        android:paddingRight=\"8dp\"\n        android:paddingTop=\"6dp\">\n\n        <ImageView\n            android:id=\"@+id/emotion_voice\"\n            android:layout_width=\"28dp\"\n            android:layout_height=\"28dp\"\n            android:layout_marginRight=\"@dimen/content_horizontal_margin\"\n            android:clickable=\"true\"\n            android:scaleType=\"centerCrop\"\n            android:src=\"@mipmap/icon_voice\" />\n\n        <EditText\n            android:id=\"@+id/edit_text\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/corners_edit_white\"\n            android:maxLines=\"3\"\n            android:minHeight=\"36dp\"\n            android:paddingLeft=\"@dimen/content_horizontal_margin\"\n            android:paddingRight=\"@dimen/content_horizontal_margin\"\n            android:textSize=\"16sp\" />\n\n        <TextView\n            android:id=\"@+id/voice_text\"\n            style=\"@style/text_subhead_black\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/corners_edit\"\n            android:gravity=\"center\"\n            android:minHeight=\"36dp\"\n            android:paddingLeft=\"@dimen/content_horizontal_margin\"\n            android:paddingRight=\"@dimen/content_horizontal_margin\"\n            android:text=\"按住说话\"\n            android:visibility=\"gone\" />\n\n        <ImageView\n            android:id=\"@+id/emotion_button\"\n            android:layout_width=\"28dp\"\n            android:layout_height=\"28dp\"\n            android:layout_marginLeft=\"@dimen/content_horizontal_margin\"\n            android:clickable=\"true\"\n            android:src=\"@mipmap/icon_face\" />\n\n        <ImageView\n            android:id=\"@+id/emotion_add\"\n            android:layout_width=\"28dp\"\n            android:layout_height=\"28dp\"\n            android:layout_marginLeft=\"@dimen/content_horizontal_margin\"\n            android:clickable=\"true\"\n            android:scaleType=\"centerCrop\"\n            android:src=\"@mipmap/icon_add\" />\n\n        <com.rance.chatui.widget.StateButton\n            android:id=\"@+id/emotion_send\"\n            android:layout_width=\"40dp\"\n            android:layout_height=\"30dp\"\n            android:layout_marginLeft=\"@dimen/content_horizontal_margin\"\n            android:padding=\"1dp\"\n            android:text=\"发送\"\n            android:textColor=\"@color/white\"\n            android:visibility=\"gone\"\n            app:normalBackgroundColor=\"@color/colorPrimary\"\n            app:pressedBackgroundColor=\"@color/colorPrimaryDark\"\n            app:radius=\"5dp\" />\n\n    </LinearLayout>\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"1px\"\n        android:background=\"@color/divider_line\" />\n\n    <RelativeLayout\n        android:id=\"@+id/emotion_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"@color/bg_content\"\n        android:orientation=\"vertical\"\n        android:visibility=\"gone\">\n\n        <com.rance.chatui.widget.NoScrollViewPager\n            android:id=\"@+id/viewpager\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n    </RelativeLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_chat_accept.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:padding=\"@dimen/content_vertical_margin\">\n\n\n    <TextView\n        android:id=\"@+id/chat_item_date\"\n        style=\"@style/text_body\"\n        android:layout_gravity=\"center_horizontal\"\n        android:visibility=\"gone\" />\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <ImageView\n            android:id=\"@+id/chat_item_header\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:layout_alignParentLeft=\"true\"\n            android:scaleType=\"centerCrop\"/>\n\n        <com.rance.chatui.widget.BubbleImageView\n            android:id=\"@+id/chat_item_content_image\"\n            android:layout_width=\"150dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_toRightOf=\"@id/chat_item_header\"\n            android:scaleType=\"centerCrop\"\n            android:visibility=\"gone\"\n            app:angle=\"6dp\"\n            app:arrowHeight=\"8dp\"\n            app:arrowLocation=\"left\"\n            app:arrowPosition=\"15dp\"\n            app:arrowWidth=\"10dp\" />\n\n        <com.rance.chatui.widget.BubbleLinearLayout\n            android:id=\"@+id/chat_item_layout_content\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_toRightOf=\"@id/chat_item_header\"\n            android:gravity=\"left|center_vertical\"\n            android:minHeight=\"48dp\"\n            android:minWidth=\"120dp\"\n            android:orientation=\"horizontal\"\n            app:angle=\"4dp\"\n            app:arrowHeight=\"10dp\"\n            app:arrowLocation=\"left\"\n            app:arrowPosition=\"10dp\"\n            app:arrowWidth=\"8dp\"\n            app:bubbleColor=\"@color/chat_accept_bg\">\n\n            <com.rance.chatui.widget.GifTextView\n                android:id=\"@+id/chat_item_content_text\"\n                style=\"@style/text_subhead_black\"\n                android:layout_marginLeft=\"@dimen/content_horizontal_margin\"\n                android:padding=\"@dimen/content_vertical_margin\" />\n\n            <ImageView\n                android:id=\"@+id/chat_item_voice\"\n                android:layout_width=\"18dp\"\n                android:layout_height=\"18dp\"\n                android:layout_marginLeft=\"@dimen/activity_horizontal_margin\"\n                android:src=\"@mipmap/icon_voice_left3\"\n                android:visibility=\"gone\" />\n        </com.rance.chatui.widget.BubbleLinearLayout>\n\n        <com.rance.chatui.widget.BubbleLinearLayout\n            android:id=\"@+id/chat_item_layout_file\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_toRightOf=\"@id/chat_item_header\"\n            android:gravity=\"left|center_vertical\"\n            android:minHeight=\"48dp\"\n            android:minWidth=\"120dp\"\n            android:visibility=\"gone\"\n            android:orientation=\"horizontal\"\n            app:angle=\"4dp\"\n            app:arrowHeight=\"10dp\"\n            app:arrowLocation=\"left\"\n            app:arrowPosition=\"10dp\"\n            app:arrowWidth=\"8dp\"\n            app:bubbleColor=\"@color/chat_accept_bg\">\n\n            <RelativeLayout\n                android:id=\"@+id/chat_item_file\"\n                style=\"@style/file_body\">\n\n                <ImageView\n                    android:id=\"@+id/iv_file_type\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:src=\"@mipmap/icon_file_pdf\" />\n\n                <TextView\n                    android:id=\"@+id/tv_file_name\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"20dp\"\n                    android:layout_toRightOf=\"@+id/iv_file_type\"/>\n\n                <TextView\n                    android:id=\"@+id/tv_file_size\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_below=\"@+id/tv_file_name\"\n                    android:layout_marginLeft=\"20dp\"\n                    android:layout_marginTop=\"4dp\"\n                    android:layout_toRightOf=\"@+id/iv_file_type\" />\n            </RelativeLayout>\n        </com.rance.chatui.widget.BubbleLinearLayout>\n        <com.rance.chatui.widget.BubbleLinearLayout\n            android:id=\"@+id/chat_item_layout_contact\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"left|center_vertical\"\n            android:paddingTop=\"10dp\"\n            android:minHeight=\"98dp\"\n            android:minWidth=\"200dp\"\n            android:visibility=\"gone\"\n            android:orientation=\"vertical\"\n            app:angle=\"4dp\"\n            app:arrowHeight=\"10dp\"\n            app:arrowLocation=\"left\"\n            app:arrowPosition=\"20dp\"\n            app:arrowWidth=\"8dp\"\n            app:bubbleColor=\"@color/chat_accept_bg\">\n            <TextView\n                android:id=\"@+id/tv_label\"\n                android:text=\"通讯录联系人\"\n                android:layout_marginTop=\"10dp\"\n                android:textColor=\"@color/chat_accept_text\"\n                android:layout_marginLeft=\"20dp\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\" />\n\n            <View\n                android:background=\"@color/chat_accept_text\"\n                android:layout_marginLeft=\"10dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginRight=\"14dp\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"1px\" />\n            <RelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"10dp\"\n                android:layout_marginLeft=\"10dp\"\n                android:layout_marginBottom=\"10dp\"\n                android:orientation=\"vertical\">\n                <TextView\n                    android:id=\"@+id/tv_contact_surname\"\n                    android:text=\"李\"\n                    android:paddingLeft=\"14dp\"\n                    android:paddingRight=\"14dp\"\n                    android:paddingTop=\"10dp\"\n                    android:paddingBottom=\"10dp\"\n                    android:textColor=\"@color/chat_accept_text\"\n                    android:layout_centerVertical=\"true\"\n                    android:layout_marginLeft=\"8dp\"\n                    android:textSize=\"28sp\"\n                    android:background=\"@drawable/bg_surname\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\" />\n\n                <LinearLayout\n                    android:orientation=\"vertical\"\n                    android:layout_toRightOf=\"@+id/tv_contact_surname\"\n                    android:layout_marginLeft=\"10dp\"\n                    android:layout_centerVertical=\"true\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\">\n                    <TextView\n                        android:id=\"@+id/tv_contact_name\"\n                        android:text=\"李老大\"\n                        android:textColor=\"@color/chat_accept_text\"\n                        android:textSize=\"18sp\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\" />\n\n                    <TextView\n                        android:id=\"@+id/tv_contact_phone\"\n                        android:text=\"13843859438\"\n                        android:textColor=\"@color/chat_accept_text\"\n                        android:layout_marginTop=\"8dp\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\" />\n                </LinearLayout>\n\n            </RelativeLayout>\n        </com.rance.chatui.widget.BubbleLinearLayout>\n        <com.rance.chatui.widget.BubbleLinearLayout\n            android:id=\"@+id/chat_item_layout_link\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"left|center_vertical\"\n            android:paddingTop=\"10dp\"\n            android:minHeight=\"98dp\"\n            android:minWidth=\"210dp\"\n            android:visibility=\"gone\"\n            android:orientation=\"vertical\"\n            app:angle=\"4dp\"\n            app:arrowHeight=\"10dp\"\n            app:arrowLocation=\"right\"\n            app:arrowPosition=\"20dp\"\n            app:arrowWidth=\"8dp\"\n            app:bubbleColor=\"@color/chat_accept_bg\">\n            <RelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginBottom=\"10dp\">\n                <TextView\n                    android:id=\"@+id/tv_link_subject\"\n                    android:paddingLeft=\"10dp\"\n                    android:paddingRight=\"14dp\"\n                    android:paddingTop=\"10dp\"\n                    android:paddingBottom=\"10dp\"\n                    android:text=\"普京吃屎\"\n                    android:textColor=\"@color/chat_accept_text\"\n                    android:textSize=\"16sp\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\" />\n\n                <LinearLayout\n                    android:orientation=\"horizontal\"\n                    android:layout_below=\"@+id/tv_link_subject\"\n                    android:layout_marginLeft=\"10dp\"\n                    android:layout_centerVertical=\"true\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\">\n                    <TextView\n                        android:id=\"@+id/tv_link_text\"\n                        android:textColor=\"@color/chat_accept_text\"\n                        android:text=\"震惊，美国竟然阿道夫阿阿斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬\"\n                        android:maxLines=\"3\"\n                        android:textSize=\"12sp\"\n                        android:maxWidth=\"110dp\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\" />\n\n                    <ImageView\n                        android:id=\"@+id/iv_link_picture\"\n                        android:src=\"@mipmap/pic_preview\"\n                        android:layout_marginLeft=\"10dp\"\n                        android:layout_width=\"46dp\"\n                        android:layout_height=\"46dp\" />\n                </LinearLayout>\n\n            </RelativeLayout>\n        </com.rance.chatui.widget.BubbleLinearLayout>\n        <TextView\n            android:id=\"@+id/chat_item_voice_time\"\n            style=\"@style/text_body\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginLeft=\"@dimen/content_vertical_margin\"\n            android:layout_toRightOf=\"@+id/chat_item_layout_content\"\n            android:visibility=\"gone\" />\n\n\n    </RelativeLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_chat_send.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:padding=\"@dimen/content_vertical_margin\">\n\n    <TextView\n        android:id=\"@+id/chat_item_date\"\n        style=\"@style/text_body\"\n        android:layout_gravity=\"center_horizontal\"\n        android:visibility=\"gone\" />\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <ImageView\n            android:id=\"@+id/chat_item_header\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:layout_alignParentRight=\"true\"\n            android:scaleType=\"centerCrop\" />\n\n        <RelativeLayout\n            android:id=\"@+id/chat_item_layout\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_toLeftOf=\"@id/chat_item_header\">\n\n            <com.rance.chatui.widget.BubbleImageView\n                android:id=\"@+id/chat_item_content_image\"\n                android:layout_width=\"150dp\"\n                android:layout_height=\"wrap_content\"\n                android:scaleType=\"centerCrop\"\n                android:visibility=\"gone\"\n                app:angle=\"6dp\"\n                app:arrowHeight=\"8dp\"\n                app:arrowLocation=\"right\"\n                app:arrowPosition=\"15dp\"\n                app:arrowWidth=\"10dp\" />\n\n            <com.rance.chatui.widget.BubbleLinearLayout\n                android:id=\"@+id/chat_item_layout_content\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"right|center_vertical\"\n                android:minHeight=\"48dp\"\n                android:minWidth=\"120dp\"\n                android:visibility=\"gone\"\n                android:orientation=\"horizontal\"\n                app:angle=\"4dp\"\n                app:arrowHeight=\"10dp\"\n                app:arrowLocation=\"right\"\n                app:arrowPosition=\"10dp\"\n                app:arrowWidth=\"8dp\"\n                app:bubbleColor=\"@color/chat_send_bg\">\n\n                <com.rance.chatui.widget.GifTextView\n                    android:id=\"@+id/chat_item_content_text\"\n                    style=\"@style/text_subhead_black\"\n                    android:layout_marginRight=\"@dimen/content_horizontal_margin\"\n                    android:padding=\"@dimen/content_vertical_margin\"\n                    android:visibility=\"gone\" />\n\n                <ImageView\n                    android:id=\"@+id/chat_item_voice\"\n                    android:layout_width=\"18dp\"\n                    android:layout_height=\"18dp\"\n                    android:layout_marginRight=\"@dimen/activity_horizontal_margin\"\n                    android:scaleType=\"fitCenter\"\n                    android:src=\"@mipmap/icon_voice_right3\"\n                    android:visibility=\"gone\" />\n            </com.rance.chatui.widget.BubbleLinearLayout>\n\n            <com.rance.chatui.widget.BubbleLinearLayout\n                android:id=\"@+id/chat_item_layout_file\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"right|center_vertical\"\n                android:minHeight=\"48dp\"\n                android:minWidth=\"120dp\"\n                android:orientation=\"horizontal\"\n                android:visibility=\"gone\"\n                app:angle=\"4dp\"\n                app:arrowHeight=\"10dp\"\n                app:arrowLocation=\"right\"\n                app:arrowPosition=\"10dp\"\n                app:arrowWidth=\"8dp\"\n                app:bubbleColor=\"@color/chat_send_bg\">\n\n                <RelativeLayout\n                    android:id=\"@+id/rl_item_file\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginRight=\"30dp\"\n                    android:padding=\"10dp\">\n\n                    <ImageView\n                        android:id=\"@+id/iv_file_type\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:src=\"@mipmap/icon_file_excel\" />\n\n                    <TextView\n                        android:id=\"@+id/tv_file_name\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginLeft=\"10dp\"\n                        android:layout_marginTop=\"4dp\"\n                        android:layout_toRightOf=\"@+id/iv_file_type\"\n                        android:textColor=\"@color/chat_send_text\"\n                        android:textSize=\"12sp\" />\n\n                    <TextView\n                        android:id=\"@+id/tv_file_size\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_below=\"@+id/tv_file_name\"\n                        android:layout_marginLeft=\"10dp\"\n                        android:layout_marginTop=\"26dp\"\n                        android:layout_toRightOf=\"@+id/iv_file_type\"\n                        android:textColor=\"@color/chat_send_text\"\n                        android:textSize=\"12sp\" />\n                </RelativeLayout>\n            </com.rance.chatui.widget.BubbleLinearLayout>\n\n            <com.rance.chatui.widget.BubbleLinearLayout\n                android:id=\"@+id/chat_item_layout_contact\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"left|center_vertical\"\n                android:paddingTop=\"10dp\"\n                android:minHeight=\"98dp\"\n                android:minWidth=\"200dp\"\n                android:visibility=\"gone\"\n                android:orientation=\"vertical\"\n                app:angle=\"4dp\"\n                app:arrowHeight=\"10dp\"\n                app:arrowLocation=\"right\"\n                app:arrowPosition=\"20dp\"\n                app:arrowWidth=\"8dp\"\n                app:bubbleColor=\"@color/chat_send_bg\">\n                <TextView\n                    android:id=\"@+id/tv_label\"\n                    android:text=\"通讯录联系人\"\n                    android:layout_marginTop=\"10dp\"\n                    android:textColor=\"@color/chat_send_text\"\n                    android:layout_marginLeft=\"10dp\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\" />\n\n                <View\n                    android:background=\"@color/chat_send_text\"\n                    android:layout_marginLeft=\"10dp\"\n                    android:layout_marginTop=\"4dp\"\n                    android:layout_marginRight=\"14dp\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"1px\" />\n                <RelativeLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginTop=\"10dp\"\n                    android:layout_marginBottom=\"10dp\"\n                    android:orientation=\"vertical\">\n                    <TextView\n                        android:id=\"@+id/tv_contact_surname\"\n                        android:paddingLeft=\"14dp\"\n                        android:paddingRight=\"14dp\"\n                        android:paddingTop=\"10dp\"\n                        android:paddingBottom=\"10dp\"\n                        android:textColor=\"@color/chat_send_text\"\n                        android:layout_centerVertical=\"true\"\n                        android:layout_marginLeft=\"8dp\"\n                        android:textSize=\"28sp\"\n                        android:background=\"@drawable/bg_surname\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\" />\n\n                    <LinearLayout\n                        android:orientation=\"vertical\"\n                        android:layout_toRightOf=\"@+id/tv_contact_surname\"\n                        android:layout_marginLeft=\"10dp\"\n                        android:layout_centerVertical=\"true\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\">\n                        <TextView\n                            android:id=\"@+id/tv_contact_name\"\n                            android:textColor=\"@color/chat_send_text\"\n                            android:textSize=\"18sp\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\" />\n\n                        <TextView\n                            android:id=\"@+id/tv_contact_phone\"\n                            android:textColor=\"@color/chat_send_text\"\n                            android:layout_marginTop=\"8dp\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\" />\n                    </LinearLayout>\n\n                </RelativeLayout>\n            </com.rance.chatui.widget.BubbleLinearLayout>\n            <com.rance.chatui.widget.BubbleLinearLayout\n                android:id=\"@+id/chat_item_layout_link\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"right|center_vertical\"\n                android:paddingTop=\"10dp\"\n                android:minHeight=\"98dp\"\n                android:minWidth=\"240dp\"\n                android:visibility=\"gone\"\n                android:orientation=\"vertical\"\n                app:angle=\"4dp\"\n                app:arrowHeight=\"10dp\"\n                app:arrowLocation=\"right\"\n                app:arrowPosition=\"20dp\"\n                app:arrowWidth=\"8dp\"\n                app:bubbleColor=\"@color/chat_send_bg\">\n                <RelativeLayout\n                    android:layout_width=\"wrap_content\"\n                    android:layout_gravity=\"center\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"10dp\">\n                    <TextView\n                        android:id=\"@+id/tv_link_subject\"\n                        android:paddingRight=\"14dp\"\n                        android:paddingTop=\"10dp\"\n                        android:paddingBottom=\"10dp\"\n                        android:maxWidth=\"200dp\"\n                        android:textColor=\"@color/chat_send_text\"\n                        android:textSize=\"16sp\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\" />\n\n                    <LinearLayout\n                        android:orientation=\"horizontal\"\n                        android:layout_below=\"@+id/tv_link_subject\"\n                        android:layout_centerVertical=\"true\"\n                        android:gravity=\"center_vertical\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\">\n                        <TextView\n                            android:id=\"@+id/tv_link_text\"\n                            android:textColor=\"@color/chat_send_text\"\n                            android:maxLines=\"3\"\n                            android:textSize=\"12sp\"\n                            android:maxWidth=\"120dp\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\" />\n\n                        <ImageView\n                            android:id=\"@+id/iv_link_picture\"\n                            android:layout_marginLeft=\"10dp\"\n                            android:scaleType=\"centerCrop\"\n                            android:layout_width=\"50dp\"\n                            android:layout_height=\"50dp\" />\n                    </LinearLayout>\n\n                </RelativeLayout>\n            </com.rance.chatui.widget.BubbleLinearLayout>\n        </RelativeLayout>\n\n        <TextView\n            android:id=\"@+id/chat_item_voice_time\"\n            style=\"@style/text_body\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"@dimen/content_vertical_margin\"\n            android:layout_toLeftOf=\"@+id/chat_item_layout\"\n            android:visibility=\"gone\" />\n\n        <ImageView\n            android:id=\"@+id/chat_item_fail\"\n            android:layout_width=\"18dp\"\n            android:layout_height=\"18dp\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"@dimen/content_vertical_margin\"\n            android:layout_toLeftOf=\"@+id/chat_item_voice_time\"\n            android:focusable=\"false\"\n            android:src=\"@drawable/msg_state_fail_resend\"\n            android:visibility=\"gone\" />\n\n        <ProgressBar\n            android:id=\"@+id/chat_item_progress\"\n            style=\"@android:style/Widget.ProgressBar.Small.Inverse\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"@dimen/content_vertical_margin\"\n            android:layout_toLeftOf=\"@+id/chat_item_voice_time\"\n            android:visibility=\"gone\" />\n    </RelativeLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_contact.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"20dp\"\n    android:orientation=\"horizontal\">\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:text=\"张三\"\n        android:textColor=\"@color/black\"\n        android:textSize=\"18sp\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n    <TextView\n        android:id=\"@+id/tv_phone\"\n        android:text=\"13843859438\"\n        android:layout_marginLeft=\"20dp\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_microphone.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/record_microphone_bj\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\"\n        android:padding=\"16dp\">\n\n        <ImageView\n            android:id=\"@+id/iv_recording_icon\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:src=\"@drawable/record_microphone\" />\n\n        <TextView\n            android:id=\"@+id/tv_recording_time\"\n            android:layout_width=\"150dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"6dp\"\n            android:gravity=\"center\"\n            android:text=\"00:00\"\n            android:textColor=\"#FFFFFF\"\n            android:textSize=\"16sp\" />\n\n        <TextView\n            android:id=\"@+id/tv_recording_text\"\n            android:layout_width=\"150dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"@dimen/content_vertical_margin\"\n            android:gravity=\"center\"\n            android:text=\"松开保存\"\n            android:textColor=\"#FFFFFF\"\n            android:textSize=\"16sp\" />\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/popup_context_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/bg_message\"\n    android:paddingLeft=\"10dp\"\n    android:paddingRight=\"10dp\"\n    android:paddingTop=\"10dp\"\n    android:paddingBottom=\"20dp\"\n    android:orientation=\"horizontal\">\n\n    <TextView\n        android:id=\"@+id/tv_copy\"\n        android:drawableTop=\"@mipmap/icon_copy\"\n        android:drawablePadding=\"4dp\"\n        android:text=\"复制\"\n        android:textSize=\"12sp\"\n        android:textColor=\"@color/white\"\n        android:layout_width=\"0px\"\n        android:layout_weight=\"1\"\n        android:gravity=\"center\"\n        android:layout_height=\"wrap_content\" />\n\n    <TextView\n        android:id=\"@+id/tv_transit\"\n        android:drawableTop=\"@mipmap/icon_transmit\"\n        android:drawablePadding=\"4dp\"\n        android:text=\"转发\"\n        android:gravity=\"center\"\n        android:textSize=\"12sp\"\n        android:textColor=\"@color/white\"\n        android:layout_width=\"0px\"\n        android:layout_weight=\"1\"\n        android:layout_height=\"wrap_content\" />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/values/attr.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- 按钮自定义属性 -->\n    <declare-styleable name=\"StateButton\">\n        <!--text color-->\n        <attr name=\"normalTextColor\" format=\"color|reference\" />\n        <attr name=\"pressedTextColor\" format=\"color|reference\" />\n        <attr name=\"unableTextColor\" format=\"color|reference\" />\n\n        <!--stroke width and color, dash width, dash gap-->\n        <attr name=\"strokeDashWidth\" format=\"dimension|reference\" />\n        <attr name=\"strokeDashGap\" format=\"dimension|reference\" />\n        <attr name=\"normalStrokeWidth\" format=\"dimension|reference\" />\n        <attr name=\"pressedStrokeWidth\" format=\"dimension|reference\" />\n        <attr name=\"unableStrokeWidth\" format=\"dimension|reference\" />\n        <attr name=\"normalStrokeColor\" format=\"color|reference\" />\n        <attr name=\"pressedStrokeColor\" format=\"color|reference\" />\n        <attr name=\"unableStrokeColor\" format=\"color|reference\" />\n\n        <!--background color-->\n        <attr name=\"normalBackgroundColor\" format=\"color|reference\" />\n        <attr name=\"pressedBackgroundColor\" format=\"color|reference\" />\n        <attr name=\"unableBackgroundColor\" format=\"color|reference\" />\n\n        <!--background radius-->\n        <attr name=\"radius\" format=\"dimension|reference\" />\n        <attr name=\"round\" format=\"boolean|reference\" />\n\n        <!--animation duration-->\n        <attr name=\"animationDuration\" format=\"integer|reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"BubbleView\">\n        <attr name=\"arrowWidth\" format=\"dimension\" />\n        <attr name=\"angle\" format=\"dimension\" />\n        <attr name=\"arrowHeight\" format=\"dimension\" />\n        <attr name=\"arrowPosition\" format=\"dimension\" />\n        <attr name=\"bubbleColor\" format=\"color\" />\n        <attr name=\"arrowLocation\" format=\"enum\">\n            <enum name=\"left\" value=\"0x00\" />\n            <enum name=\"right\" value=\"0x01\" />\n            <enum name=\"top\" value=\"0x02\" />\n            <enum name=\"bottom\" value=\"0x03\" />\n        </attr>\n    </declare-styleable>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#3F51B5</color>\n    <color name=\"white\">#FFFFFF</color>\n    <color name=\"black\">#000000</color>\n\n    <!-- 其它背景色或分割线相关 -->\n    <color name=\"bg_content\">#f3f3f3</color>\n    <color name=\"divider_line\">#d9d9d9</color>\n\n    <!-- 字体色 -->\n    <color name=\"text_title\">#1b1b1b</color>\n    <color name=\"text_content\">#818181</color>\n    <color name=\"text_sub\">#919191</color>\n    <color name=\"text_hint\">#c7c7c7</color>\n    <color name=\"text_black\">#404040</color>\n\n    <color name=\"chat_send_bg\">#E4F4FE</color>\n    <color name=\"chat_send_text\">#000000</color>\n\n    <color name=\"chat_accept_bg\">#EBEFF0</color>\n    <color name=\"chat_accept_text\">#000000</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n    <dimen name=\"content_horizontal_margin\">10dp</dimen>\n    <dimen name=\"content_vertical_margin\">10dp</dimen>\n    <dimen name=\"content_small\">5dp</dimen>\n\n    <!-- 字体大小 -->\n    <dimen name=\"text_title\">19sp</dimen>\n    <dimen name=\"text_subhead\">17sp</dimen>\n    <dimen name=\"text_body\">12sp</dimen>\n    <dimen name=\"text_caption\">13sp</dimen>\n    <dimen name=\"line_height\">1dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">ChatUI</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"text_subhead_black\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textColor\">@color/chat_accept_text</item>\n        <item name=\"android:textSize\">@dimen/text_subhead</item>\n    </style>\n    <style name=\"text_function_item\">\n        <item name=\"android:layout_width\">0px</item>\n        <item name=\"android:layout_weight\">1</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textColor\">@color/text_sub</item>\n        <item name=\"android:textSize\">@dimen/text_body</item>\n    </style>\n    <style name=\"text_body\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textColor\">@color/text_sub</item>\n        <item name=\"android:textSize\">@dimen/text_body</item>\n    </style>\n    <style name=\"text_send_body\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textColor\">@color/chat_send_text</item>\n        <item name=\"android:textSize\">@dimen/text_body</item>\n    </style>\n    <style name=\"file_body\">\n        <item name=\"android:layout_width\">0px</item>\n        <item name=\"android:layout_weight\">1</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/xml/file_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <external-path\n        name=\"chat\"\n        path=\"\" />\n</paths>\n"
  },
  {
    "path": "app/src/test/java/com/rance/chatui/ExampleUnitTest.java",
    "content": "package com.rance.chatui;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * To work on unit tests, switch the Test Artifact in the Build Variants view.\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:2.1.2'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Dec 28 10:00:20 PST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.10-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx10248m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app'\n"
  }
]