diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000000000000000000000000000000000..181aa29c195ca6cab5f393414c4c01550bc1dbb6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,17 @@ +## 提issue小提示 +为了尽快定位问题,需要提供足够的信息,请按照我们的要求格式来提问。***如果您不按照格式提问,我们将无法回复您的问题*** + +#### 操作说明 +详细说明操作那个界面,每一步骤都要说得详细,如果是SDK或者api使用问题,需要详细说明调用的那个函数,参数都是什么。 + +#### 表现现象 +描述一下什么现象,比如说函数返回error,errorcode是多少,或者界面上是什么状态,比如发送消息显示发送失败的红点。不要笼统的说功能有问题,不起作用。 + +#### 预期结果 +你认为正确的表现应该是什么样的。 + +#### 补充条件 +是否是必现的,还是偶现的?是否只有在特殊的网络/设备/平台上出现,还是所有的都出现。还有您用的版本是什么时候的,是不是最新版 + +#### demo对比结果 +请用demo对比测试,demo上是什么状态。 diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000000000000000000000000000000000000..99d0be2efd5e8817bffd61f9e2e92fbedc269f54 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,27 @@ +name: Android CI + +on: workflow_dispatch + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew clean aDebug + + # 打包完成之后进行上传 + - uses: actions/upload-artifact@v4 + with: + path: chat/build/outputs/apk/debug/chat-debug.apk diff --git a/.gitignore b/.gitignore index bf0a5765f2be5bfc7725b22ab379e617681bf06c..5d846bbd3e75164bc8ecdf1786059f84a86d5fbc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /local.properties /.idea/workspace.xml /.idea/libraries +/chat/debug/ .DS_Store /build /captures @@ -18,7 +19,7 @@ mars_android_sdk/obj .idea/**/tasks.xml .idea/**/dictionaries .idea/**/shelf - +.idea # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids @@ -116,7 +117,7 @@ captures/ .externalNativeBuild # Google Services (e.g. APIs or Firebase) -google-services.json +#google-services.json # Freeline freeline.py diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 40ed93785ddd928ffb7367195f11792245c4ca24..0000000000000000000000000000000000000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 97626ba45445dc9f3afa66e6a149914dc39e3df6..0000000000000000000000000000000000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 7c9cca99e9b96dfc4872e1b8272de2c7d701a0cf..0000000000000000000000000000000000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 039bbd8c17b129abb66f50f8b4489e01dd2696a5..0000000000000000000000000000000000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index d8ad051ba767d2aab5076acb656c9fc4cfc34b2b..0000000000000000000000000000000000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460d8b38ac04e3a3224d7c79ef719b1991a9..0000000000000000000000000000000000000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 830674470f8052bb64c64a5b513f5df2815a70c1..0000000000000000000000000000000000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..19b3625a901271bf3257e5a01573917e7831b9c5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,55 @@ +Creative Commons Attribution-NoDerivs 3.0 Unported + +CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. + +1. Definitions +a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. +b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. +c. "Distribute" means to make available to the public the original and copies of the Work through sale or other transfer of ownership. +d. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. +e. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. +f. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. +g. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. +h. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. +i. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. +2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. +3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: +a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; and, +b. to Distribute and Publicly Perform the Work including as incorporated in Collections. +c. For the avoidance of doubt: +i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; +ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, +iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. +The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats, but otherwise you have no rights to make Adaptations. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: +a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. +b. If You Distribute, or Publicly Perform the Work or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work. The credit required by this Section 4(b) may be implemented in any reasonable manner; provided, however, that in the case of a Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. +c. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. +5. Representations, Warranties and Disclaimer +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +7. Termination +a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. +b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. +8. Miscellaneous +a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. +b. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. +c. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. +d. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. +e. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. +Creative Commons Notice + +Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. + +Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. + +Creative Commons may be contacted at http://creativecommons.org/. + diff --git a/README.md b/README.md index dfede18330c2e03480cdbde43b4be0a3619ed2c4..672fcd8cb946129e9d2a3916252c599666c83a7e 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,84 @@ ## 野火IM解决方案 -野火IM是一套跨平台、全开源的即时通讯解决方案,主要包含以下内容。 +野火IM是专业级即时通讯和实时音视频整体解决方案,由北京野火无限网络科技有限公司维护和支持。 -| 仓库 | 说明 | 备注 | -| ------------------------------------------------------------ | ------------------------------------------------------- | ---- | -| [android-chat](https://github.com/wildfirechat/android-chat) | 野火IM Android App |可以很方便地进行二次开发,或集成到现有应用当中 | -| [ios-chat](https://github.com/wildfirechat/ios-chat) | 野火IM iOS App |可以很方便地进行二次开发,或集成到现有应用当中 | -| [pc-chat](https://github.com/wildfirechat/pc-chat) | 基于[Electron](https://electronjs.org/)开发的PC平台应用 | | -| [proto](https://github.com/wildfirechat/proto) | 野火IM的协议栈实现 | | -| [server](https://github.com/wildfirechat/server) | IM server | | -| [app server](https://github.com/wildfirechat/app_server) | 应用服务端 | | -| [robot_server](https://github.com/wildfirechat/robot_server) | 机器人服务端 | | -| [push_server](https://github.com/wildfirechat/push_server) | 推送服务器 | | -| [docs](https://github.com/wildfirechat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明 | | +主要特性有:私有部署安全可靠,性能强大,功能齐全,全平台支持,开源率高,部署运维简单,二次开发友好,方便与第三方系统对接或者嵌入现有系统中。详细情况请参考[在线文档](https://docs.wildfirechat.cn)。 +主要包括一下项目: + +| [GitHub仓库地址(主站)](https://github.com/wildfirechat) | [码云仓库地址(镜像)](https://gitee.com/wfchat) | 说明 | 备注 | +| ------------------------------------------------------------ | ----------------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------- | +| [im-server](https://github.com/wildfirechat/im-server) | [server](https://gitee.com/wfchat/im-server) | IM Server | | +| [android-chat](https://github.com/wildfirechat/android-chat) | [android-chat](https://gitee.com/wfchat/android-chat) | 野火IM Android SDK源码和App源码 | 可以很方便地进行二次开发,或集成到现有应用当中 | +| [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS SDK源码和App源码 | 可以很方便地进行二次开发,或集成到现有应用当中 | +| [pc-chat](https://github.com/wildfirechat/vue-pc-chat) | [pc-chat](https://gitee.com/wfchat/vue-pc-chat) | 基于[Electron](https://electronjs.org/)开发的PC 端 | | +| [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](http://web.wildfirechat.cn) | | +| [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台) | | +| [app server](https://github.com/wildfirechat/app_server) | [app server](https://gitee.com/wfchat/app_server) | 应用服务端 | | +| [robot_server](https://github.com/wildfirechat/robot_server) | [robot_server](https://gitee.com/wfchat/robot_server) | 机器人服务端 | | +| [push_server](https://github.com/wildfirechat/push_server) | [push_server](https://gitee.com/wfchat/push_server) | 推送服务器 | | +| [docs](https://github.com/wildfirechat/docs) | [docs](https://gitee.com/wfchat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明,[在线查看](https://docs.wildfirechat.cn/) | | -## 说明 -本工程为野火IM Android App,开发过程中,充分考虑了二次开发和集成需求,可作为SDK集成到其他应用中,或者直接进行二次开发,详情可以阅读[docs](http://docs.wildfirechat.cn). +## 说明 +本工程为野火IM Android App,开发过程中,充分考虑了二次开发和集成需求,可作为SDK集成到其他应用中,或者直接进行二次开发。 开发一套IM系统真的很艰辛,请路过的朋友们给点个star,支持我们坚持下去🙏🙏🙏🙏🙏 +## 关于包名/applicationId +1. 开发者开发具体产品时,请勿直接使用本 demo 的包名/applicationId,我们会不定期修改包名/applicationId +2. 禁止将本产品用于非法目的,一经发现,我们将停止任何形式的技术支持 +3. 修改包名时,会导致编译失败,需同步修改`google-services.json`和`agconnect-services.json`文件中的`package_name`字段。对接推送时,需要重新生成对应的`google-services.json`和`agconnect-services.json`文件。 +4. 如果需要修改`client`、`mars-core-release`或`avenginekit.aar`等的包名,请联系我们。 + ## 开发调试说明 +1. JDK: 17 +2. 我们采用最新稳定版Android Studio及对应的gradle进行开发,对于旧版本的IDE,我们没有测试,编译之类问题,需自行解决。 -我们采用最新稳定版Android Studio及对应的gradle进行开发,对于旧版本的IDE,我们没有测试,编译之类问题,需自行解决。 +## 关于 minSdkVersion 设置为 21 时, debug 版 apk 可能不能进行音视频通话的特殊说明 +1. 关闭混淆时,命令行下,通过`./gradlew clean aDebug` 或 Android Studio 里面,通过 `Build App Bundle(s)/APK(s) -> Build APK(s)` 生成的 debug 版本 apk,不支持音视频通话,具体原因请参考[useFullClasspathForDexingTransform](https://issuetracker.google.com/issues/333107832) +2. 开启混淆,debug 版 apk 一切正常,将`chat/build.gradle#buildTypes#debug#minifyEnabled`置为 true,即为 debug 版也开启混淆 +3. 命令行下,通过`./gradlew clean aR`或 Android Studio 里面,通过`Generate Signed App Bundle/APK...`可生成 release 版 apk,release 版 apk,一切正常 ## 二次开发说明 +野火IM采用bugly作为日志手机工具,大家二次开发时,务必将```MyApp.java```中的 ```bugly id``` 替换为你们自己的,否则错误日志都跑我们这儿来了,你们收集不到错误日志,我们也会受到干扰。 + +## 混淆说明 +1. 确保所依赖的```lifecycle```版本在2.2.0或以上。 +2. 参考```chat/proguard-rules.pro```进行配置。 -野火IM采用bugly作为日志手机工具,大家二次开发时,务必将```MyApp.java```中的 ```bugly id``` 替换为你们自己的,否则错误日志都跑我们这儿来了,你们收集不到错误日志,我们也会收到干扰。 +## 安全说明 +为了方便开发者部署、测试,默认允许`HTTP`进行网络请求,为了提高安全性,上线之前,请进行以下操作: +1. 为`app-server`配置`HTTPS`支持,并将`APP_SERVER_ADDRESS`配置为`HTTPS`地址 +2. 如果支持开放平台的话,为开发平台配置`HTTPS`支持,并将`WORKSPACE_URL`配置为`HTTPS`地址 +3. 如果支持组织结构的话,为组织结构服务配置`HTTPS`支持,并将`ORG_SERVER_ADDRESS`配置为`HTTPS`地址 +4. 将`AndroidManifest.xml`里面的`usesCleartextTraffic`置为`false` -另外,如果可以请告知我们,我们会在案例参考把项目加上。 +## 敏感权限说明 +1. `android.permission.PROCESS_OUTGOING_CALLS`,音视频通话时,允许普通电话打断音视频通话,默认未申请 +2. `android.permission.SYSTEM_ALERT_WINDOW`,允许音视频通话窗口最小化,并悬浮在其他窗口之上 +3. `android.permission.BLUETOOTH`、`android.permission.BLUETOOTH_ADMIN`,音视频通话时,允许使用蓝牙耳机 +## Android 4.x 说明 +请使用[api-19](https://github.com/wildfirechat/android-chat/tree/api-19)分支,如果编译失败等,可能是4.x版本的协议栈版本没有及时更新所导致,请微信联系 `wfchat` 进行更新。 ### 联系我们 -问题讨论请加群:822762829 - +> 商务合作请优先采用邮箱和我们联系。技术问题请到[野火IM论坛](http://bbs.wildfirechat.cn/)发帖交流。 +1. heavyrain.lee 邮箱: heavyrain.lee@wildfirechat.cn 微信:wildfirechat +2. imndx 邮箱: imndx@wildfirechat.cn 微信:wfchat + +### 问题交流 + +1. 如果大家发现bug,请在GitHub提issue +2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习 +3. 微信公众号 + + + +> 强烈建议关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。 ## 体验Demo 我们提供了体验demo,请使用微信扫码下载安装体验 @@ -45,39 +86,54 @@ ![野火IM](http://static.wildfirechat.cn/download_qrcode.png) ## 应用截图 -![ios-demo1](http://static.wildfirechat.cn/android-deomo1.gif) +[点击查看 Android Demo 视频演示](https://static.wildfirechat.cn/wf-android-demo-live.mp4) + + -![ios-demo2](http://static.wildfirechat.cn/android-deomo1.gif) + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + -## 协议栈的编译 -工程中已经包括了编译好的协议栈,你也可以自己编译[协议栈](https://github.com/wildfirechat/proto),编译方法请参考协议栈工程。 ## 集成 -1. client部分,支持快速集成,具体参考[jitpack-wildfire.chat](https://jitpack.io/#wildfirechat/android-chat/) -2. UI(chat)部分,目前不支持快速集成,需要你自行下载,并将代码移动到你自己的项目,且必须是application module,不能作为library module引入的原因是注解中使用了R.xx.yyyy, -而library module中,R.xx.yyy并不是一个常量。后续会采用butterKnife的方式,引入R2.xx.yyyy。 +1. client部分,自行下载代码,并将client module引入你们自己的项目。 +2. uikit部分,自行下载代码,并将uikit module引入你们自己的项目。 +3. push部分,自行下载代码,将push module引入你们自己的项目。 + +## 推送 +当应用在后台后,不同手机厂家有着不同的后台策略,可能很快或者最终会被冻结和杀掉,此时收到消息需要厂商的推送通知服务。请部署推送服务,推送服务代码可以在[Github](https://github.com/wildfirechat/push_server)和[码云](https://gitee.com/wfchat/push_server)下载。具体使用方式,请参考推送服务项目上的说明。 ## 贡献 欢迎提交pull request,一起打造一个更好的开源IM。 @@ -91,11 +147,6 @@ 如果有什么地方侵犯了您的权益,请联系我们删除🙏🙏🙏 -## 案例参考 - -todo - ## License -1. Under the MIT license. See the [LICENSE](https://github.com/wildfirechat/mars/blob/firechat/LICENSE) file for details. -2. Under the 996ICU License. See the [LICENSE](https://github.com/996icu/996.ICU/blob/master/LICENSE) file for details. +1. Under the Creative Commons Attribution-NoDerivs 3.0 Unported license. See the [LICENSE](https://github.com/wildfirechat/android-chat/blob/master/LICENSE) file for details. diff --git a/avenginekit/avenginekit.aar b/avenginekit/avenginekit.aar index ef1067cbef0fdadf3226888cb9bb682e0b7f7b89..e4cc4c25fbbef7cd517f7e1999c94dad25308971 100644 Binary files a/avenginekit/avenginekit.aar and b/avenginekit/avenginekit.aar differ diff --git a/badgeview/build.gradle b/badgeview/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..b431d936d49de20c1d9a91e6519a997dabdeea1f --- /dev/null +++ b/badgeview/build.gradle @@ -0,0 +1,2 @@ +configurations.maybeCreate("default") +artifacts.add("default", file('jetified-badgeview-1.1.3.aar')) diff --git a/badgeview/jetified-badgeview-1.1.3.aar b/badgeview/jetified-badgeview-1.1.3.aar new file mode 100644 index 0000000000000000000000000000000000000000..41b3b0212e2f4508ea3b657e10e4d330175d72b5 Binary files /dev/null and b/badgeview/jetified-badgeview-1.1.3.aar differ diff --git a/badgeview/readme.md b/badgeview/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..13ebcfc706e0dadf17e43098110a09cbe3212d51 --- /dev/null +++ b/badgeview/readme.md @@ -0,0 +1,5 @@ +## 说明 +由于作者不在维护,有时不能正常从仓库下载,故将`aar`直接放在这儿 + +源码代码在[BadgeView](https://github.com/qstumn/BadgeView) + diff --git a/build.gradle b/build.gradle index 2f35eba7f6d50017611c29c86da2ad943bc2cb3b..3f494cf59c479551620f4de2d4e2ec398bf05318 100644 --- a/build.gradle +++ b/build.gradle @@ -1,27 +1,30 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + // ext.kotlin_version = '1.4.30-M1' repositories { google() - jcenter() mavenCentral() + maven {url 'https://developer.huawei.com/repo/'} } dependencies { - classpath 'com.android.tools.build:gradle:3.4.0' - classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' + classpath 'com.android.tools.build:gradle:8.7.2' + classpath 'com.huawei.agconnect:agcp:1.9.1.300' + // classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files + classpath 'com.google.gms:google-services:4.4.2' } } allprojects { repositories { google() - jcenter() + mavenCentral() maven { - url "http://developer.huawei.com/repo/" + url "https://developer.huawei.com/repo/" } maven { url 'https://jitpack.io' } diff --git a/cameraview/.gitignore b/cameraview/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8 --- /dev/null +++ b/cameraview/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/cameraview/README.md b/cameraview/README.md new file mode 100644 index 0000000000000000000000000000000000000000..67fa2980f5b02a7bd8c8f54eb0e91231a2c9fcbf --- /dev/null +++ b/cameraview/README.md @@ -0,0 +1,5 @@ +本项目原始代码,拷贝自[CameraView](https://github.com/CJT2325/CameraView) + +相对原始代码的修改,可以通过提交日志查阅 + +感谢原作者 diff --git a/cameraview/build.gradle b/cameraview/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..6537219dac54938279796d4407f86a4fb2768764 --- /dev/null +++ b/cameraview/build.gradle @@ -0,0 +1,35 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.cjt2325.cameralibrary' + compileSdk 34 + + defaultConfig { + minSdk 24 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.12.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' +} \ No newline at end of file diff --git a/cameraview/consumer-rules.pro b/cameraview/consumer-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/cameraview/proguard-rules.pro b/cameraview/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce --- /dev/null +++ b/cameraview/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/cameraview/src/androidTest/java/cn/wildfirechat/cameraview/ExampleInstrumentedTest.java b/cameraview/src/androidTest/java/cn/wildfirechat/cameraview/ExampleInstrumentedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d907c9ad9ea6499820d0b4c1d097d6f59344e294 --- /dev/null +++ b/cameraview/src/androidTest/java/cn/wildfirechat/cameraview/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package cn.wildfirechat.cameraview; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("cn.wildfirechat.cameraview.test", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/cameraview/src/main/AndroidManifest.xml b/cameraview/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..856257ac718709b3d21efe8e5bd0ed019eced79c --- /dev/null +++ b/cameraview/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/CameraInterface.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/CameraInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..676e7e05968953bda79ab29f70d5d310aba27ce4 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/CameraInterface.java @@ -0,0 +1,782 @@ +package com.cjt2325.cameralibrary; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.YuvImage; +import android.hardware.Camera; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.media.MediaRecorder; +import android.os.Build; +import android.os.Environment; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.widget.ImageView; + +import com.cjt2325.cameralibrary.listener.ErrorListener; +import com.cjt2325.cameralibrary.util.AngleUtil; +import com.cjt2325.cameralibrary.util.CameraParamUtil; +import com.cjt2325.cameralibrary.util.CheckPermission; +import com.cjt2325.cameralibrary.util.DeviceUtil; +import com.cjt2325.cameralibrary.util.FileUtil; +import com.cjt2325.cameralibrary.util.LogUtil; +import com.cjt2325.cameralibrary.util.ScreenUtils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static android.graphics.Bitmap.createBitmap; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/4/25 + * 描 述:camera操作单例 + * ===================================== + */ +@SuppressWarnings("deprecation") +public class CameraInterface implements Camera.PreviewCallback { + + private static final String TAG = "CJT"; + + private volatile static CameraInterface mCameraInterface; + + public static void destroyCameraInterface() { + if (mCameraInterface != null) { + mCameraInterface = null; + } + } + + private Camera mCamera; + private Camera.Parameters mParams; + private boolean isPreviewing = false; + + private int SELECTED_CAMERA = -1; + private int CAMERA_POST_POSITION = -1; + private int CAMERA_FRONT_POSITION = -1; + + private SurfaceHolder mHolder = null; + private float screenProp = -1.0f; + + private boolean isRecorder = false; + private MediaRecorder mediaRecorder; + private String videoFileName; + private String saveVideoPath; + private String videoFileAbsPath; + private Bitmap videoFirstFrame = null; + + private ErrorListener errorLisenter; + + private ImageView mSwitchView; + private ImageView mFlashLamp; + + private int preview_width; + private int preview_height; + + private int angle = 0; + private int cameraAngle = 90;//摄像头角度 默认为90度 + private int rotation = 0; + private byte[] firstFrame_data; + + public static final int TYPE_RECORDER = 0x090; + public static final int TYPE_CAPTURE = 0x091; + private int nowScaleRate = 0; + private int recordScleRate = 0; + + //视频质量 + private int mediaQuality = JCameraView.MEDIA_QUALITY_MIDDLE; + private SensorManager sm = null; + + //获取CameraInterface单例 + public static synchronized CameraInterface getInstance() { + if (mCameraInterface == null) + synchronized (CameraInterface.class) { + if (mCameraInterface == null) + mCameraInterface = new CameraInterface(); + } + return mCameraInterface; + } + + public void setSwitchView(ImageView mSwitchView, ImageView mFlashLamp) { + this.mSwitchView = mSwitchView; + this.mFlashLamp = mFlashLamp; + if (mSwitchView != null) { + cameraAngle = CameraParamUtil.getInstance().getCameraDisplayOrientation(mSwitchView.getContext(), + SELECTED_CAMERA); + } + } + + private SensorEventListener sensorEventListener = new SensorEventListener() { + public void onSensorChanged(SensorEvent event) { + if (Sensor.TYPE_ACCELEROMETER != event.sensor.getType()) { + return; + } + float[] values = event.values; + angle = AngleUtil.getSensorAngle(values[0], values[1]); + rotationAnimation(); + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + }; + + //切换摄像头icon跟随手机角度进行旋转 + private void rotationAnimation() { + if (mSwitchView == null) { + return; + } + if (rotation != angle) { + int start_rotaion = 0; + int end_rotation = 0; + switch (rotation) { + case 0: + start_rotaion = 0; + switch (angle) { + case 90: + end_rotation = -90; + break; + case 270: + end_rotation = 90; + break; + } + break; + case 90: + start_rotaion = -90; + switch (angle) { + case 0: + end_rotation = 0; + break; + case 180: + end_rotation = -180; + break; + } + break; + case 180: + start_rotaion = 180; + switch (angle) { + case 90: + end_rotation = 270; + break; + case 270: + end_rotation = 90; + break; + } + break; + case 270: + start_rotaion = 90; + switch (angle) { + case 0: + end_rotation = 0; + break; + case 180: + end_rotation = 180; + break; + } + break; + } + ObjectAnimator animC = ObjectAnimator.ofFloat(mSwitchView, "rotation", start_rotaion, end_rotation); + ObjectAnimator animF = ObjectAnimator.ofFloat(mFlashLamp, "rotation", start_rotaion, end_rotation); + AnimatorSet set = new AnimatorSet(); + set.playTogether(animC, animF); + set.setDuration(500); + set.start(); + rotation = angle; + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + void setSaveVideoPath(String saveVideoPath) { + this.saveVideoPath = saveVideoPath; + File file = new File(saveVideoPath); + if (!file.exists()) { + file.mkdirs(); + } + } + + + public void setZoom(float zoom, int type) { + if (mCamera == null) { + return; + } + if (mParams == null) { + mParams = mCamera.getParameters(); + } + if (!mParams.isZoomSupported() || !mParams.isSmoothZoomSupported()) { + return; + } + switch (type) { + case TYPE_RECORDER: + //如果不是录制视频中,上滑不会缩放 + if (!isRecorder) { + return; + } + if (zoom >= 0) { + //每移动50个像素缩放一个级别 + int scaleRate = (int) (zoom / 40); + if (scaleRate <= mParams.getMaxZoom() && scaleRate >= nowScaleRate && recordScleRate != scaleRate) { + mParams.setZoom(scaleRate); + mCamera.setParameters(mParams); + recordScleRate = scaleRate; + } + } + break; + case TYPE_CAPTURE: + if (isRecorder) { + return; + } + //每移动50个像素缩放一个级别 + int scaleRate = (int) (zoom / 50); + if (scaleRate < mParams.getMaxZoom()) { + nowScaleRate += scaleRate; + if (nowScaleRate < 0) { + nowScaleRate = 0; + } else if (nowScaleRate > mParams.getMaxZoom()) { + nowScaleRate = mParams.getMaxZoom(); + } + mParams.setZoom(nowScaleRate); + mCamera.setParameters(mParams); + } + LogUtil.i("setZoom = " + nowScaleRate); + break; + } + + } + + void setMediaQuality(int quality) { + this.mediaQuality = quality; + } + + + @Override + public void onPreviewFrame(byte[] data, Camera camera) { + firstFrame_data = data; + } + + public void setFlashMode(String flashMode) { + if (mCamera == null) + return; + Camera.Parameters params = mCamera.getParameters(); + params.setFlashMode(flashMode); + mCamera.setParameters(params); + } + + + public interface CameraOpenOverCallback { + void cameraHasOpened(); + } + + private CameraInterface() { + findAvailableCameras(); + SELECTED_CAMERA = CAMERA_POST_POSITION; + saveVideoPath = ""; + } + + + /** + * open Camera + */ + void doOpenCamera(CameraOpenOverCallback callback) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + if (!CheckPermission.isCameraUseable(SELECTED_CAMERA) && this.errorLisenter != null) { + this.errorLisenter.onError(); + return; + } + } + if (mCamera == null) { + openCamera(SELECTED_CAMERA); + } + callback.cameraHasOpened(); + } + + private void setFlashModel() { + mParams = mCamera.getParameters(); + mParams.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); //设置camera参数为Torch模式 + mCamera.setParameters(mParams); + } + + private synchronized void openCamera(int id) { + try { + this.mCamera = Camera.open(id); + } catch (Exception var3) { + var3.printStackTrace(); + if (this.errorLisenter != null) { + this.errorLisenter.onError(); + } + } + + if (Build.VERSION.SDK_INT > 17 && this.mCamera != null) { + try { + this.mCamera.enableShutterSound(false); + } catch (Exception e) { + e.printStackTrace(); + Log.e("CJT", "enable shutter sound faild"); + } + } + } + + public synchronized void switchCamera(SurfaceHolder holder, float screenProp) { + if (SELECTED_CAMERA == CAMERA_POST_POSITION) { + SELECTED_CAMERA = CAMERA_FRONT_POSITION; + } else { + SELECTED_CAMERA = CAMERA_POST_POSITION; + } + doDestroyCamera(); + LogUtil.i("open start"); + openCamera(SELECTED_CAMERA); +// mCamera = Camera.open(); + if (Build.VERSION.SDK_INT > 17 && this.mCamera != null) { + try { + this.mCamera.enableShutterSound(false); + } catch (Exception e) { + e.printStackTrace(); + } + } + LogUtil.i("open end"); + doStartPreview(holder, screenProp); + } + + /** + * doStartPreview + */ + public void doStartPreview(SurfaceHolder holder, float screenProp) { + if (isPreviewing) { + LogUtil.i("doStartPreview isPreviewing"); + } + if (this.screenProp < 0) { + this.screenProp = screenProp; + } + if (holder == null) { + return; + } + this.mHolder = holder; + if (mCamera != null) { + try { + mParams = mCamera.getParameters(); + Camera.Size previewSize = CameraParamUtil.getInstance().getPreviewSize(mParams + .getSupportedPreviewSizes(), 1000, screenProp); + Camera.Size pictureSize = CameraParamUtil.getInstance().getPictureSize(mParams + .getSupportedPictureSizes(), 1200, screenProp); + + mParams.setPreviewSize(previewSize.width, previewSize.height); + + preview_width = previewSize.width; + preview_height = previewSize.height; + + mParams.setPictureSize(pictureSize.width, pictureSize.height); + + if (CameraParamUtil.getInstance().isSupportedFocusMode( + mParams.getSupportedFocusModes(), + Camera.Parameters.FOCUS_MODE_AUTO)) { + mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); + } + if (CameraParamUtil.getInstance().isSupportedPictureFormats(mParams.getSupportedPictureFormats(), + ImageFormat.JPEG)) { + mParams.setPictureFormat(ImageFormat.JPEG); + mParams.setJpegQuality(100); + } + mCamera.setParameters(mParams); + mParams = mCamera.getParameters(); + mCamera.setPreviewDisplay(holder); //SurfaceView + mCamera.setDisplayOrientation(cameraAngle);//浏览角度 + mCamera.setPreviewCallback(this); //每一帧回调 + mCamera.startPreview();//启动浏览 + isPreviewing = true; + Log.i(TAG, "=== Start Preview ==="); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 停止预览 + */ + public void doStopPreview() { + if (null != mCamera) { + try { + mCamera.setPreviewCallback(null); + mCamera.stopPreview(); + //这句要在stopPreview后执行,不然会卡顿或者花屏 + mCamera.setPreviewDisplay(null); + isPreviewing = false; + Log.i(TAG, "=== Stop Preview ==="); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 销毁Camera + */ + void doDestroyCamera() { + errorLisenter = null; + if (null != mCamera) { + try { + mCamera.setPreviewCallback(null); + mSwitchView = null; + mFlashLamp = null; + mCamera.stopPreview(); + //这句要在stopPreview后执行,不然会卡顿或者花屏 + mCamera.setPreviewDisplay(null); + mHolder = null; + isPreviewing = false; + mCamera.release(); + mCamera = null; +// destroyCameraInterface(); + Log.i(TAG, "=== Destroy Camera ==="); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + Log.i(TAG, "=== Camera Null==="); + } + } + + /** + * 拍照 + */ + private int nowAngle; + + public void takePicture(final TakePictureCallback callback) { + if (mCamera == null) { + return; + } + switch (cameraAngle) { + case 90: + nowAngle = Math.abs(angle + cameraAngle) % 360; + break; + case 270: + nowAngle = Math.abs(cameraAngle - angle); + break; + } +// + Log.i("CJT", angle + " = " + cameraAngle + " = " + nowAngle); + mCamera.takePicture(null, null, new Camera.PictureCallback() { + @Override + public void onPictureTaken(byte[] data, Camera camera) { + Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); + Matrix matrix = new Matrix(); + if (SELECTED_CAMERA == CAMERA_POST_POSITION) { + matrix.setRotate(nowAngle); + } else if (SELECTED_CAMERA == CAMERA_FRONT_POSITION) { + matrix.setRotate(360 - nowAngle); + matrix.postScale(-1, 1); + } + + bitmap = createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + if (callback != null) { + if (nowAngle == 90 || nowAngle == 270) { + callback.captureResult(bitmap, true); + } else { + callback.captureResult(bitmap, false); + } + } + } + }); + } + + //启动录像 + public void startRecord(Surface surface, float screenProp, ErrorCallback callback) { + mCamera.setPreviewCallback(null); + final int nowAngle = (angle + 90) % 360; + //获取第一帧图片 + Camera.Parameters parameters = mCamera.getParameters(); + int width = parameters.getPreviewSize().width; + int height = parameters.getPreviewSize().height; + YuvImage yuv = new YuvImage(firstFrame_data, parameters.getPreviewFormat(), width, height, null); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + yuv.compressToJpeg(new Rect(0, 0, width, height), 50, out); + byte[] bytes = out.toByteArray(); + videoFirstFrame = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + Matrix matrix = new Matrix(); + if (SELECTED_CAMERA == CAMERA_POST_POSITION) { + matrix.setRotate(nowAngle); + } else if (SELECTED_CAMERA == CAMERA_FRONT_POSITION) { + matrix.setRotate(270); + } + videoFirstFrame = createBitmap(videoFirstFrame, 0, 0, videoFirstFrame.getWidth(), videoFirstFrame + .getHeight(), matrix, true); + + if (isRecorder) { + return; + } + if (mCamera == null) { + openCamera(SELECTED_CAMERA); + } + if (mediaRecorder == null) { + mediaRecorder = new MediaRecorder(); + } + if (mParams == null) { + mParams = mCamera.getParameters(); + } + List focusModes = mParams.getSupportedFocusModes(); + if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { + mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); + } + mCamera.setParameters(mParams); + mCamera.unlock(); + mediaRecorder.reset(); + mediaRecorder.setCamera(mCamera); + mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); + + mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + + Camera.Size videoSize; + if (mParams.getSupportedVideoSizes() == null) { + videoSize = CameraParamUtil.getInstance().getPreviewSize(mParams.getSupportedPreviewSizes(), 600, + screenProp); + } else { + videoSize = CameraParamUtil.getInstance().getPreviewSize(mParams.getSupportedVideoSizes(), 600, + screenProp); + } + Log.i(TAG, "setVideoSize width = " + videoSize.width + "height = " + videoSize.height); + if (videoSize.width == videoSize.height) { + mediaRecorder.setVideoSize(preview_width, preview_height); + } else { + mediaRecorder.setVideoSize(videoSize.width, videoSize.height); + } +// if (SELECTED_CAMERA == CAMERA_FRONT_POSITION) { +// mediaRecorder.setOrientationHint(270); +// } else { +// mediaRecorder.setOrientationHint(nowAngle); +//// mediaRecorder.setOrientationHint(90); +// } + + if (SELECTED_CAMERA == CAMERA_FRONT_POSITION) { + //手机预览倒立的处理 + if (cameraAngle == 270) { + //横屏 + if (nowAngle == 0) { + mediaRecorder.setOrientationHint(180); + } else if (nowAngle == 270) { + mediaRecorder.setOrientationHint(270); + } else { + mediaRecorder.setOrientationHint(90); + } + } else { + if (nowAngle == 90) { + mediaRecorder.setOrientationHint(270); + } else if (nowAngle == 270) { + mediaRecorder.setOrientationHint(90); + } else { + mediaRecorder.setOrientationHint(nowAngle); + } + } + } else { + mediaRecorder.setOrientationHint(nowAngle); + } + + + if (DeviceUtil.isHuaWeiRongyao()) { + mediaRecorder.setVideoEncodingBitRate(4 * 100000); + } else { + mediaRecorder.setVideoEncodingBitRate(mediaQuality); + } + mediaRecorder.setPreviewDisplay(surface); + + videoFileName = "video_" + System.currentTimeMillis() + ".mp4"; + if (saveVideoPath.equals("")) { + saveVideoPath = Environment.getExternalStorageDirectory().getPath(); + } + videoFileAbsPath = saveVideoPath + File.separator + videoFileName; + mediaRecorder.setOutputFile(videoFileAbsPath); + try { + mediaRecorder.prepare(); + mediaRecorder.start(); + isRecorder = true; + } catch (IllegalStateException e) { + e.printStackTrace(); + Log.i("CJT", "startRecord IllegalStateException"); + if (this.errorLisenter != null) { + this.errorLisenter.onError(); + } + } catch (IOException e) { + e.printStackTrace(); + Log.i("CJT", "startRecord IOException"); + if (this.errorLisenter != null) { + this.errorLisenter.onError(); + } + } catch (RuntimeException e) { + Log.i("CJT", "startRecord RuntimeException"); + } + } + + //停止录像 + public void stopRecord(boolean isShort, StopRecordCallback callback) { + if (!isRecorder) { + return; + } + if (mediaRecorder != null) { + mediaRecorder.setOnErrorListener(null); + mediaRecorder.setOnInfoListener(null); + mediaRecorder.setPreviewDisplay(null); + try { + mediaRecorder.stop(); + } catch (RuntimeException e) { + e.printStackTrace(); + mediaRecorder = null; + mediaRecorder = new MediaRecorder(); + } finally { + if (mediaRecorder != null) { + mediaRecorder.release(); + } + mediaRecorder = null; + isRecorder = false; + } + if (isShort) { + if (FileUtil.deleteFile(videoFileAbsPath)) { + if(callback != null){ + callback.recordResult(null, null); + } + } + return; + } + doStopPreview(); + String fileName = saveVideoPath + File.separator + videoFileName; + callback.recordResult(fileName, videoFirstFrame); + } + } + + private void findAvailableCameras() { + Camera.CameraInfo info = new Camera.CameraInfo(); + int cameraNum = Camera.getNumberOfCameras(); + for (int i = 0; i < cameraNum; i++) { + Camera.getCameraInfo(i, info); + switch (info.facing) { + case Camera.CameraInfo.CAMERA_FACING_FRONT: + CAMERA_FRONT_POSITION = info.facing; + break; + case Camera.CameraInfo.CAMERA_FACING_BACK: + CAMERA_POST_POSITION = info.facing; + break; + } + } + } + + int handlerTime = 0; + + public void handleFocus(final Context context, final float x, final float y, final FocusCallback callback) { + if (mCamera == null) { + return; + } + final Camera.Parameters params = mCamera.getParameters(); + Rect focusRect = calculateTapArea(x, y, 1f, context); + mCamera.cancelAutoFocus(); + if (params.getMaxNumFocusAreas() > 0) { + List focusAreas = new ArrayList<>(); + focusAreas.add(new Camera.Area(focusRect, 800)); + params.setFocusAreas(focusAreas); + } else { + Log.i(TAG, "focus areas not supported"); + callback.focusSuccess(); + return; + } + final String currentFocusMode = params.getFocusMode(); + try { + params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); + mCamera.setParameters(params); + mCamera.autoFocus(new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean success, Camera camera) { + if (success || handlerTime > 10) { + Camera.Parameters params = camera.getParameters(); + params.setFocusMode(currentFocusMode); + camera.setParameters(params); + handlerTime = 0; + callback.focusSuccess(); + } else { + handlerTime++; + handleFocus(context, x, y, callback); + } + } + }); + } catch (Exception e) { + Log.e(TAG, "autoFocus failer"); + } + } + + + private static Rect calculateTapArea(float x, float y, float coefficient, Context context) { + float focusAreaSize = 300; + int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue(); + int centerX = (int) (x / ScreenUtils.getScreenWidth(context) * 2000 - 1000); + int centerY = (int) (y / ScreenUtils.getScreenHeight(context) * 2000 - 1000); + int left = clamp(centerX - areaSize / 2, -1000, 1000); + int top = clamp(centerY - areaSize / 2, -1000, 1000); + RectF rectF = new RectF(left, top, left + areaSize, top + areaSize); + return new Rect(Math.round(rectF.left), Math.round(rectF.top), Math.round(rectF.right), Math.round(rectF + .bottom)); + } + + private static int clamp(int x, int min, int max) { + if (x > max) { + return max; + } + if (x < min) { + return min; + } + return x; + } + + void setErrorLinsenter(ErrorListener errorLisenter) { + this.errorLisenter = errorLisenter; + } + + + public interface StopRecordCallback { + void recordResult(String url, Bitmap firstFrame); + } + + interface ErrorCallback { + void onError(); + } + + public interface TakePictureCallback { + void captureResult(Bitmap bitmap, boolean isVertical); + } + + public interface FocusCallback { + void focusSuccess(); + + } + + + void registerSensorManager(Context context) { + if (sm == null) { + sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + } + sm.registerListener(sensorEventListener, sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager + .SENSOR_DELAY_NORMAL); + } + + void unregisterSensorManager(Context context) { + if (sm == null) { + sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + } + sm.unregisterListener(sensorEventListener); + } + + void isPreview(boolean res) { + this.isPreviewing = res; + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/CaptureButton.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/CaptureButton.java new file mode 100644 index 0000000000000000000000000000000000000000..aa3bc0b2b245e583870adcd42dd3c502c226367b --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/CaptureButton.java @@ -0,0 +1,372 @@ +package com.cjt2325.cameralibrary; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.CountDownTimer; +import android.view.MotionEvent; +import android.view.View; + +import com.cjt2325.cameralibrary.listener.CaptureListener; +import com.cjt2325.cameralibrary.util.CheckPermission; +import com.cjt2325.cameralibrary.util.LogUtil; + +import static com.cjt2325.cameralibrary.JCameraView.BUTTON_STATE_BOTH; +import static com.cjt2325.cameralibrary.JCameraView.BUTTON_STATE_ONLY_CAPTURE; +import static com.cjt2325.cameralibrary.JCameraView.BUTTON_STATE_ONLY_RECORDER; + + +/** + * ===================================== + * 作 者: 陈嘉桐 445263848@qq.com + * 版 本:1.1.4 + * 创建日期:2017/4/25 + * 描 述:拍照按钮 + * ===================================== + */ +public class CaptureButton extends View { + + private int state; //当前按钮状态 + private int button_state; //按钮可执行的功能状态(拍照,录制,两者) + + public static final int STATE_IDLE = 0x001; //空闲状态 + public static final int STATE_PRESS = 0x002; //按下状态 + public static final int STATE_LONG_PRESS = 0x003; //长按状态 + public static final int STATE_RECORDERING = 0x004; //录制状态 + public static final int STATE_BAN = 0x005; //禁止状态 + + private int progress_color = 0xEE16AE16; //进度条颜色 + private int outside_color = 0xEEDCDCDC; //外圆背景色 + private int inside_color = 0xFFFFFFFF; //内圆背景色 + + + private float event_Y; //Touch_Event_Down时候记录的Y值 + + + private Paint mPaint; + + private float strokeWidth; //进度条宽度 + private int outside_add_size; //长按外圆半径变大的Size + private int inside_reduce_size; //长安内圆缩小的Size + + //中心坐标 + private float center_X; + private float center_Y; + + private float button_radius; //按钮半径 + private float button_outside_radius; //外圆半径 + private float button_inside_radius; //内圆半径 + private int button_size; //按钮大小 + + private float progress; //录制视频的进度 + private int duration; //录制视频最大时间长度 + private int min_duration; //最短录制时间限制 + private int recorded_time; //记录当前录制的时间 + + private RectF rectF; + + private LongPressRunnable longPressRunnable; //长按后处理的逻辑Runnable + private CaptureListener captureLisenter; //按钮回调接口 + private RecordCountDownTimer timer; //计时器 + + public CaptureButton(Context context) { + super(context); + } + + public CaptureButton(Context context, int size) { + super(context); + this.button_size = size; + button_radius = size / 2.0f; + + button_outside_radius = button_radius; + button_inside_radius = button_radius * 0.75f; + + strokeWidth = size / 15; + outside_add_size = size / 5; + inside_reduce_size = size / 8; + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + + progress = 0; + longPressRunnable = new LongPressRunnable(); + + state = STATE_IDLE; //初始化为空闲状态 + button_state = BUTTON_STATE_BOTH; //初始化按钮为可录制可拍照 + LogUtil.i("CaptureButtom start"); + duration = 10 * 1000; //默认最长录制时间为10s + LogUtil.i("CaptureButtom end"); + min_duration = 1500; //默认最短录制时间为1.5s + + center_X = (button_size + outside_add_size * 2) / 2; + center_Y = (button_size + outside_add_size * 2) / 2; + + rectF = new RectF( + center_X - (button_radius + outside_add_size - strokeWidth / 2), + center_Y - (button_radius + outside_add_size - strokeWidth / 2), + center_X + (button_radius + outside_add_size - strokeWidth / 2), + center_Y + (button_radius + outside_add_size - strokeWidth / 2)); + + timer = new RecordCountDownTimer(duration, duration / 360); //录制定时器 + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(button_size + outside_add_size * 2, button_size + outside_add_size * 2); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + mPaint.setStyle(Paint.Style.FILL); + + mPaint.setColor(outside_color); //外圆(半透明灰色) + canvas.drawCircle(center_X, center_Y, button_outside_radius, mPaint); + + mPaint.setColor(inside_color); //内圆(白色) + canvas.drawCircle(center_X, center_Y, button_inside_radius, mPaint); + + //如果状态为录制状态,则绘制录制进度条 + if (state == STATE_RECORDERING) { + mPaint.setColor(progress_color); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(strokeWidth); + canvas.drawArc(rectF, -90, progress, false, mPaint); + } + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + LogUtil.i("state = " + state); + if (event.getPointerCount() > 1 || state != STATE_IDLE) + break; + event_Y = event.getY(); //记录Y值 + state = STATE_PRESS; //修改当前状态为点击按下 + + //判断按钮状态是否为可录制状态 + if ((button_state == BUTTON_STATE_ONLY_RECORDER || button_state == BUTTON_STATE_BOTH)) + postDelayed(longPressRunnable, 500); //同时延长500启动长按后处理的逻辑Runnable + break; + case MotionEvent.ACTION_MOVE: + if (captureLisenter != null + && state == STATE_RECORDERING + && (button_state == BUTTON_STATE_ONLY_RECORDER || button_state == BUTTON_STATE_BOTH)) { + //记录当前Y值与按下时候Y值的差值,调用缩放回调接口 + captureLisenter.recordZoom(event_Y - event.getY()); + } + break; + case MotionEvent.ACTION_UP: + //根据当前按钮的状态进行相应的处理 + handlerUnpressByState(); + break; + } + return true; + } + + //当手指松开按钮时候处理的逻辑 + private void handlerUnpressByState() { + removeCallbacks(longPressRunnable); //移除长按逻辑的Runnable + //根据当前状态处理 + switch (state) { + //当前是点击按下 + case STATE_PRESS: + if (captureLisenter != null && (button_state == BUTTON_STATE_ONLY_CAPTURE || button_state == + BUTTON_STATE_BOTH)) { + startCaptureAnimation(button_inside_radius); + } else { + state = STATE_IDLE; + } + break; + //当前是长按状态 + case STATE_RECORDERING: + timer.cancel(); //停止计时器 + recordEnd(); //录制结束 + break; + } + } + + //录制结束 + private void recordEnd() { + if (captureLisenter != null) { + if (recorded_time < min_duration) + captureLisenter.recordShort(recorded_time);//回调录制时间过短 + else + captureLisenter.recordEnd(recorded_time); //回调录制结束 + } + resetRecordAnim(); //重制按钮状态 + } + + //重制状态 + private void resetRecordAnim() { + state = STATE_BAN; + progress = 0; //重制进度 + invalidate(); + //还原按钮初始状态动画 + startRecordAnimation( + button_outside_radius, + button_radius, + button_inside_radius, + button_radius * 0.75f + ); + } + + //内圆动画 + private void startCaptureAnimation(float inside_start) { + ValueAnimator inside_anim = ValueAnimator.ofFloat(inside_start, inside_start * 0.75f, inside_start); + inside_anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + button_inside_radius = (float) animation.getAnimatedValue(); + invalidate(); + } + }); + inside_anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + //回调拍照接口 + captureLisenter.takePictures(); + state = STATE_BAN; + } + }); + inside_anim.setDuration(100); + inside_anim.start(); + } + + //内外圆动画 + private void startRecordAnimation(float outside_start, float outside_end, float inside_start, float inside_end) { + ValueAnimator outside_anim = ValueAnimator.ofFloat(outside_start, outside_end); + ValueAnimator inside_anim = ValueAnimator.ofFloat(inside_start, inside_end); + //外圆动画监听 + outside_anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + button_outside_radius = (float) animation.getAnimatedValue(); + invalidate(); + } + }); + //内圆动画监听 + inside_anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + button_inside_radius = (float) animation.getAnimatedValue(); + invalidate(); + } + }); + AnimatorSet set = new AnimatorSet(); + //当动画结束后启动录像Runnable并且回调录像开始接口 + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + //设置为录制状态 + if (state == STATE_LONG_PRESS) { + if (captureLisenter != null) + captureLisenter.recordStart(); + state = STATE_RECORDERING; + timer.start(); + } + } + }); + set.playTogether(outside_anim, inside_anim); + set.setDuration(100); + set.start(); + } + + + //更新进度条 + private void updateProgress(long millisUntilFinished) { + recorded_time = (int) (duration - millisUntilFinished); + progress = 360f - millisUntilFinished / (float) duration * 360f; + invalidate(); + } + + //录制视频计时器 + private class RecordCountDownTimer extends CountDownTimer { + RecordCountDownTimer(long millisInFuture, long countDownInterval) { + super(millisInFuture, countDownInterval); + } + + @Override + public void onTick(long millisUntilFinished) { + updateProgress(millisUntilFinished); + } + + @Override + public void onFinish() { + updateProgress(0); + recordEnd(); + } + } + + //长按线程 + private class LongPressRunnable implements Runnable { + @Override + public void run() { + state = STATE_LONG_PRESS; //如果按下后经过500毫秒则会修改当前状态为长按状态 + //没有录制权限 + if (CheckPermission.getRecordState() != CheckPermission.STATE_SUCCESS) { + state = STATE_IDLE; + if (captureLisenter != null) { + captureLisenter.recordError(); + return; + } + } + //启动按钮动画,外圆变大,内圆缩小 + startRecordAnimation( + button_outside_radius, + button_outside_radius + outside_add_size, + button_inside_radius, + button_inside_radius - inside_reduce_size + ); + } + } + + /************************************************** + * 对外提供的API * + **************************************************/ + + //设置最长录制时间 + public void setDuration(int duration) { + this.duration = duration; + timer = new RecordCountDownTimer(duration, duration / 360); //录制定时器 + } + + //设置最短录制时间 + public void setMinDuration(int duration) { + this.min_duration = duration; + } + + //设置回调接口 + public void setCaptureLisenter(CaptureListener captureLisenter) { + this.captureLisenter = captureLisenter; + } + + //设置按钮功能(拍照和录像) + public void setButtonFeatures(int state) { + this.button_state = state; + } + + //是否空闲状态 + public boolean isIdle() { + return state == STATE_IDLE ? true : false; + } + + //设置状态 + public void resetState() { + state = STATE_IDLE; + button_outside_radius = button_radius; + button_inside_radius = button_radius * 0.75f; + progress = 0; + timer.cancel(); + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/CaptureLayout.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/CaptureLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..f3de74d3f53b5eb9f20f68d103c51416d82072d5 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/CaptureLayout.java @@ -0,0 +1,367 @@ +package com.cjt2325.cameralibrary; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.Configuration; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.cjt2325.cameralibrary.listener.CaptureListener; +import com.cjt2325.cameralibrary.listener.ClickListener; +import com.cjt2325.cameralibrary.listener.ReturnListener; +import com.cjt2325.cameralibrary.listener.TypeListener; + + +/** + * ===================================== + * 作 者: 陈嘉桐 445263848@qq.com + * 版 本:1.0.4 + * 创建日期:2017/4/26 + * 描 述:集成各个控件的布局 + * ===================================== + */ + +public class CaptureLayout extends FrameLayout { + + private CaptureListener captureLisenter; //拍照按钮监听 + private TypeListener typeLisenter; //拍照或录制后接结果按钮监听 + private ReturnListener returnListener; //退出按钮监听 + private ClickListener leftClickListener; //左边按钮监听 + private ClickListener rightClickListener; //右边按钮监听 + + public void setTypeLisenter(TypeListener typeLisenter) { + this.typeLisenter = typeLisenter; + } + + public void setCaptureLisenter(CaptureListener captureLisenter) { + this.captureLisenter = captureLisenter; + } + + public void setReturnLisenter(ReturnListener returnListener) { + this.returnListener = returnListener; + } + + private CaptureButton btn_capture; //拍照按钮 + private TypeButton btn_confirm; //确认按钮 + private TypeButton btn_cancel; //取消按钮 + private ReturnButton btn_return; //返回按钮 + private ImageView iv_custom_left; //左边自定义按钮 + private ImageView iv_custom_right; //右边自定义按钮 + private TextView txt_tip; //提示文本 + + private int layout_width; + private int layout_height; + private int button_size; + private int iconLeft = 0; + private int iconRight = 0; + + private boolean isFirst = true; + + public CaptureLayout(Context context) { + this(context, null); + } + + public CaptureLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CaptureLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics outMetrics = new DisplayMetrics(); + manager.getDefaultDisplay().getMetrics(outMetrics); + + if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + layout_width = outMetrics.widthPixels; + } else { + layout_width = outMetrics.widthPixels / 2; + } + button_size = (int) (layout_width / 4.5f); + layout_height = button_size + (button_size / 5) * 2 + 100; + + initView(); + initEvent(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(layout_width, layout_height); + } + + public void initEvent() { + //默认Typebutton为隐藏 + iv_custom_right.setVisibility(GONE); + btn_cancel.setVisibility(GONE); + btn_confirm.setVisibility(GONE); + } + + public void startTypeBtnAnimator() { + //拍照录制结果后的动画 + if (this.iconLeft != 0) + iv_custom_left.setVisibility(GONE); + else + btn_return.setVisibility(GONE); + if (this.iconRight != 0) + iv_custom_right.setVisibility(GONE); + btn_capture.setVisibility(GONE); + btn_cancel.setVisibility(VISIBLE); + btn_confirm.setVisibility(VISIBLE); + btn_cancel.setClickable(false); + btn_confirm.setClickable(false); + ObjectAnimator animator_cancel = ObjectAnimator.ofFloat(btn_cancel, "translationX", layout_width / 4, 0); + ObjectAnimator animator_confirm = ObjectAnimator.ofFloat(btn_confirm, "translationX", -layout_width / 4, 0); + + AnimatorSet set = new AnimatorSet(); + set.playTogether(animator_cancel, animator_confirm); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + btn_cancel.setClickable(true); + btn_confirm.setClickable(true); + } + }); + set.setDuration(200); + set.start(); + } + + + private void initView() { + setWillNotDraw(false); + //拍照按钮 + btn_capture = new CaptureButton(getContext(), button_size); + LayoutParams btn_capture_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + btn_capture_param.gravity = Gravity.CENTER; + btn_capture.setLayoutParams(btn_capture_param); + btn_capture.setCaptureLisenter(new CaptureListener() { + @Override + public void takePictures() { + if (captureLisenter != null) { + captureLisenter.takePictures(); + } + } + + @Override + public void recordShort(long time) { + if (captureLisenter != null) { + captureLisenter.recordShort(time); + } + startAlphaAnimation(); + } + + @Override + public void recordStart() { + if (captureLisenter != null) { + captureLisenter.recordStart(); + } + startAlphaAnimation(); + } + + @Override + public void recordEnd(long time) { + if (captureLisenter != null) { + captureLisenter.recordEnd(time); + } + startAlphaAnimation(); + startTypeBtnAnimator(); + } + + @Override + public void recordZoom(float zoom) { + if (captureLisenter != null) { + captureLisenter.recordZoom(zoom); + } + } + + @Override + public void recordError() { + if (captureLisenter != null) { + captureLisenter.recordError(); + } + } + }); + + //取消按钮 + btn_cancel = new TypeButton(getContext(), TypeButton.TYPE_CANCEL, button_size); + final LayoutParams btn_cancel_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + btn_cancel_param.gravity = Gravity.CENTER_VERTICAL; + btn_cancel_param.setMargins((layout_width / 4) - button_size / 2, 0, 0, 0); + btn_cancel.setLayoutParams(btn_cancel_param); + btn_cancel.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (typeLisenter != null) { + typeLisenter.cancel(); + } + startAlphaAnimation(); +// resetCaptureLayout(); + } + }); + + //确认按钮 + btn_confirm = new TypeButton(getContext(), TypeButton.TYPE_CONFIRM, button_size); + LayoutParams btn_confirm_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + btn_confirm_param.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT; + btn_confirm_param.setMargins(0, 0, (layout_width / 4) - button_size / 2, 0); + btn_confirm.setLayoutParams(btn_confirm_param); + btn_confirm.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (typeLisenter != null) { + typeLisenter.confirm(); + } + startAlphaAnimation(); +// resetCaptureLayout(); + } + }); + + //返回按钮 + btn_return = new ReturnButton(getContext(), (int) (button_size / 2.5f)); + LayoutParams btn_return_param = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + btn_return_param.gravity = Gravity.CENTER_VERTICAL; + btn_return_param.setMargins(layout_width / 6, 0, 0, 0); + btn_return.setLayoutParams(btn_return_param); + btn_return.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (leftClickListener != null) { + leftClickListener.onClick(); + } + } + }); + //左边自定义按钮 + iv_custom_left = new ImageView(getContext()); + LayoutParams iv_custom_param_left = new LayoutParams((int) (button_size / 2.5f), (int) (button_size / 2.5f)); + iv_custom_param_left.gravity = Gravity.CENTER_VERTICAL; + iv_custom_param_left.setMargins(layout_width / 6, 0, 0, 0); + iv_custom_left.setLayoutParams(iv_custom_param_left); + iv_custom_left.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (leftClickListener != null) { + leftClickListener.onClick(); + } + } + }); + + //右边自定义按钮 + iv_custom_right = new ImageView(getContext()); + LayoutParams iv_custom_param_right = new LayoutParams((int) (button_size / 2.5f), (int) (button_size / 2.5f)); + iv_custom_param_right.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT; + iv_custom_param_right.setMargins(0, 0, layout_width / 6, 0); + iv_custom_right.setLayoutParams(iv_custom_param_right); + iv_custom_right.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (rightClickListener != null) { + rightClickListener.onClick(); + } + } + }); + + txt_tip = new TextView(getContext()); + LayoutParams txt_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + txt_param.gravity = Gravity.CENTER_HORIZONTAL; + txt_param.setMargins(0, 0, 0, 0); + txt_tip.setText("轻触拍照,长按摄像"); + txt_tip.setTextColor(0xFFFFFFFF); + txt_tip.setGravity(Gravity.CENTER); + txt_tip.setLayoutParams(txt_param); + + this.addView(btn_capture); + this.addView(btn_cancel); + this.addView(btn_confirm); + this.addView(btn_return); + this.addView(iv_custom_left); + this.addView(iv_custom_right); + this.addView(txt_tip); + + } + + /************************************************** + * 对外提供的API * + **************************************************/ + public void resetCaptureLayout() { + btn_capture.resetState(); + btn_cancel.setVisibility(GONE); + btn_confirm.setVisibility(GONE); + btn_capture.setVisibility(VISIBLE); + if (this.iconLeft != 0) + iv_custom_left.setVisibility(VISIBLE); + else + btn_return.setVisibility(VISIBLE); + if (this.iconRight != 0) + iv_custom_right.setVisibility(VISIBLE); + } + + + public void startAlphaAnimation() { + if (isFirst) { + ObjectAnimator animator_txt_tip = ObjectAnimator.ofFloat(txt_tip, "alpha", 1f, 0f); + animator_txt_tip.setDuration(500); + animator_txt_tip.start(); + isFirst = false; + } + } + + public void setTextWithAnimation(String tip) { + txt_tip.setText(tip); + ObjectAnimator animator_txt_tip = ObjectAnimator.ofFloat(txt_tip, "alpha", 0f, 1f, 1f, 0f); + animator_txt_tip.setDuration(2500); + animator_txt_tip.start(); + } + + public void setDuration(int duration) { + btn_capture.setDuration(duration); + } + + public void setButtonFeatures(int state) { + btn_capture.setButtonFeatures(state); + } + + public void setTip(String tip) { + txt_tip.setText(tip); + } + + public void showTip() { + txt_tip.setVisibility(VISIBLE); + } + + public void setIconSrc(int iconLeft, int iconRight) { + this.iconLeft = iconLeft; + this.iconRight = iconRight; + if (this.iconLeft != 0) { + iv_custom_left.setImageResource(iconLeft); + iv_custom_left.setVisibility(VISIBLE); + btn_return.setVisibility(GONE); + } else { + iv_custom_left.setVisibility(GONE); + btn_return.setVisibility(VISIBLE); + } + if (this.iconRight != 0) { + iv_custom_right.setImageResource(iconRight); + iv_custom_right.setVisibility(VISIBLE); + } else { + iv_custom_right.setVisibility(GONE); + } + } + + public void setLeftClickListener(ClickListener leftClickListener) { + this.leftClickListener = leftClickListener; + } + + public void setRightClickListener(ClickListener rightClickListener) { + this.rightClickListener = rightClickListener; + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/FoucsView.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/FoucsView.java new file mode 100644 index 0000000000000000000000000000000000000000..3f93275186ed8985dcd88f7fac367ca48901159e --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/FoucsView.java @@ -0,0 +1,65 @@ +package com.cjt2325.cameralibrary; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.Nullable; + +import com.cjt2325.cameralibrary.util.ScreenUtils; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/4/26 + * 描 述:对焦框 + * ===================================== + */ +public class FoucsView extends View { + private int size; + private int center_x; + private int center_y; + private int length; + private Paint mPaint; + + public FoucsView(Context context) { + this(context, null); + } + + public FoucsView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public FoucsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.size = ScreenUtils.getScreenWidth(context) / 3; + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setDither(true); + mPaint.setColor(0xEE16AE16); + mPaint.setStrokeWidth(4); + mPaint.setStyle(Paint.Style.STROKE); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + center_x = (int) (size / 2.0); + center_y = (int) (size / 2.0); + length = (int) (size / 2.0) - 2; + setMeasuredDimension(size, size); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawRect(center_x - length, center_y - length, center_x + length, center_y + length, mPaint); + canvas.drawLine(2, getHeight() / 2, size / 10, getHeight() / 2, mPaint); + canvas.drawLine(getWidth() - 2, getHeight() / 2, getWidth() - size / 10, getHeight() / 2, mPaint); + canvas.drawLine(getWidth() / 2, 2, getWidth() / 2, size / 10, mPaint); + canvas.drawLine(getWidth() / 2, getHeight() - 2, getWidth() / 2, getHeight() - size / 10, mPaint); + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/JCameraView.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/JCameraView.java new file mode 100644 index 0000000000000000000000000000000000000000..2768b8bc4d87e9af5a8c55d03ccd37ea3e7539a6 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/JCameraView.java @@ -0,0 +1,608 @@ +package com.cjt2325.cameralibrary; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.hardware.Camera; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.VideoView; + +import com.cjt2325.cameralibrary.listener.CaptureListener; +import com.cjt2325.cameralibrary.listener.ClickListener; +import com.cjt2325.cameralibrary.listener.ErrorListener; +import com.cjt2325.cameralibrary.listener.JCameraListener; +import com.cjt2325.cameralibrary.listener.TypeListener; +import com.cjt2325.cameralibrary.state.CameraMachine; +import com.cjt2325.cameralibrary.util.FileUtil; +import com.cjt2325.cameralibrary.util.LogUtil; +import com.cjt2325.cameralibrary.util.ScreenUtils; +import com.cjt2325.cameralibrary.view.CameraView; + +import java.io.IOException; + + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.0.4 + * 创建日期:2017/4/25 + * 描 述: + * ===================================== + */ +public class JCameraView extends FrameLayout implements CameraInterface.CameraOpenOverCallback, SurfaceHolder + .Callback, CameraView { + private static final String TAG = "JCameraView"; + + //Camera状态机 + private CameraMachine machine; + + //闪关灯状态 + private static final int TYPE_FLASH_AUTO = 0x021; + private static final int TYPE_FLASH_ON = 0x022; + private static final int TYPE_FLASH_OFF = 0x023; + private int type_flash = TYPE_FLASH_OFF; + + //拍照浏览时候的类型 + public static final int TYPE_PICTURE = 0x001; + public static final int TYPE_VIDEO = 0x002; + public static final int TYPE_SHORT = 0x003; + public static final int TYPE_DEFAULT = 0x004; + + //录制视频比特率 + public static final int MEDIA_QUALITY_HIGH = 20 * 100000; + public static final int MEDIA_QUALITY_MIDDLE = 16 * 100000; + public static final int MEDIA_QUALITY_LOW = 12 * 100000; + public static final int MEDIA_QUALITY_POOR = 8 * 100000; + public static final int MEDIA_QUALITY_FUNNY = 4 * 100000; + public static final int MEDIA_QUALITY_DESPAIR = 2 * 100000; + public static final int MEDIA_QUALITY_SORRY = 1 * 80000; + + + public static final int BUTTON_STATE_ONLY_CAPTURE = 0x101; //只能拍照 + public static final int BUTTON_STATE_ONLY_RECORDER = 0x102; //只能录像 + public static final int BUTTON_STATE_BOTH = 0x103; //两者都可以 + + + //回调监听 + private JCameraListener jCameraLisenter; + private ClickListener leftClickListener; + private ClickListener rightClickListener; + + private Context mContext; + private VideoView mVideoView; + private ImageView mPhoto; + private ImageView mSwitchCamera; + private ImageView mFlashLamp; + private CaptureLayout mCaptureLayout; + private FoucsView mFoucsView; + private MediaPlayer mMediaPlayer; + + private int layout_width; + private float screenProp = 0f; + + private Bitmap captureBitmap; //捕获的图片 + private Bitmap firstFrame; //第一帧图片 + private String videoUrl; //视频URL + + + //切换摄像头按钮的参数 + private int iconSize = 0; //图标大小 + private int iconMargin = 0; //右上边距 + private int iconSrc = 0; //图标资源 + private int iconLeft = 0; //左图标 + private int iconRight = 0; //右图标 + private int duration = 0; //录制时间 + + //缩放梯度 + private int zoomGradient = 0; + + private boolean firstTouch = true; + private float firstTouchLength = 0; + + public JCameraView(Context context) { + this(context, null); + } + + public JCameraView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public JCameraView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mContext = context; + //get AttributeSet + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.JCameraView, defStyleAttr, 0); + iconSize = a.getDimensionPixelSize(R.styleable.JCameraView_iconSize, (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, 35, getResources().getDisplayMetrics())); + iconMargin = a.getDimensionPixelSize(R.styleable.JCameraView_iconMargin, (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, 15, getResources().getDisplayMetrics())); + iconSrc = a.getResourceId(R.styleable.JCameraView_iconSrc, R.drawable.ic_camera); + iconLeft = a.getResourceId(R.styleable.JCameraView_iconLeft, 0); + iconRight = a.getResourceId(R.styleable.JCameraView_iconRight, 0); + duration = a.getInteger(R.styleable.JCameraView_duration_max, 10 * 1000); //没设置默认为10s + a.recycle(); + initData(); + initView(); + } + + private void initData() { + layout_width = ScreenUtils.getScreenWidth(mContext); + //缩放梯度 + zoomGradient = (int) (layout_width / 16f); + LogUtil.i("zoom = " + zoomGradient); + machine = new CameraMachine(getContext(), this, this); + } + + private void initView() { + setWillNotDraw(false); + View view = LayoutInflater.from(mContext).inflate(R.layout.camera_view, this); + mVideoView = (VideoView) view.findViewById(R.id.video_preview); + mPhoto = (ImageView) view.findViewById(R.id.image_photo); + mSwitchCamera = (ImageView) view.findViewById(R.id.image_switch); + mSwitchCamera.setImageResource(iconSrc); + mFlashLamp = (ImageView) view.findViewById(R.id.image_flash); + setFlashRes(); + mFlashLamp.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + type_flash++; + if (type_flash > 0x023) + type_flash = TYPE_FLASH_AUTO; + setFlashRes(); + } + }); + mCaptureLayout = (CaptureLayout) view.findViewById(R.id.capture_layout); + mCaptureLayout.setDuration(duration); + mCaptureLayout.setIconSrc(iconLeft, iconRight); + mFoucsView = (FoucsView) view.findViewById(R.id.fouce_view); + mVideoView.getHolder().addCallback(this); + //切换摄像头 + mSwitchCamera.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + machine.swtich(mVideoView.getHolder(), screenProp); + } + }); + //拍照 录像 + mCaptureLayout.setCaptureLisenter(new CaptureListener() { + @Override + public void takePictures() { + mSwitchCamera.setVisibility(INVISIBLE); + mFlashLamp.setVisibility(INVISIBLE); + machine.capture(); + } + + @Override + public void recordStart() { + mSwitchCamera.setVisibility(INVISIBLE); + mFlashLamp.setVisibility(INVISIBLE); + machine.record(mVideoView.getHolder().getSurface(), screenProp); + } + + @Override + public void recordShort(final long time) { + mCaptureLayout.setTextWithAnimation("录制时间过短"); + mSwitchCamera.setVisibility(VISIBLE); + mFlashLamp.setVisibility(VISIBLE); + postDelayed(new Runnable() { + @Override + public void run() { + machine.stopRecord(true, time); + } + }, 1500 - time); + } + + @Override + public void recordEnd(long time) { + machine.stopRecord(false, time); + } + + @Override + public void recordZoom(float zoom) { + LogUtil.i("recordZoom"); + machine.zoom(zoom, CameraInterface.TYPE_RECORDER); + } + + @Override + public void recordError() { + if (errorLisenter != null) { + errorLisenter.AudioPermissionError(); + } + } + }); + //确认 取消 + mCaptureLayout.setTypeLisenter(new TypeListener() { + @Override + public void cancel() { + machine.cancle(mVideoView.getHolder(), screenProp); + } + + @Override + public void confirm() { + machine.confirm(); + } + }); + //退出 +// mCaptureLayout.setReturnLisenter(new ReturnListener() { +// @Override +// public void onReturn() { +// if (jCameraLisenter != null) { +// jCameraLisenter.quit(); +// } +// } +// }); + mCaptureLayout.setLeftClickListener(new ClickListener() { + @Override + public void onClick() { + if (jCameraLisenter != null) { + jCameraLisenter.quit(); + } + } + }); + mCaptureLayout.setRightClickListener(new ClickListener() { + @Override + public void onClick() { + if (rightClickListener != null) { + rightClickListener.onClick(); + } + } + }); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + float widthSize = mVideoView.getMeasuredWidth(); + float heightSize = mVideoView.getMeasuredHeight(); + if (screenProp == 0) { + screenProp = heightSize / widthSize; + } + } + + @Override + public void cameraHasOpened() { + CameraInterface.getInstance().doStartPreview(mVideoView.getHolder(), screenProp); + } + + //生命周期onResume + public void onResume() { + LogUtil.i("JCameraView onResume"); + resetState(TYPE_DEFAULT); //重置状态 + CameraInterface.getInstance().registerSensorManager(mContext); + CameraInterface.getInstance().setSwitchView(mSwitchCamera, mFlashLamp); + machine.start(mVideoView.getHolder(), screenProp); + } + + //生命周期onPause + public void onPause() { + LogUtil.i("JCameraView onPause"); + stopVideo(); + resetState(TYPE_PICTURE); + CameraInterface.getInstance().stopRecord(true, null); + CameraInterface.getInstance().isPreview(false); + CameraInterface.getInstance().unregisterSensorManager(mContext); + } + + //SurfaceView生命周期 + @Override + public void surfaceCreated(SurfaceHolder holder) { + LogUtil.i("JCameraView SurfaceCreated"); + new Thread() { + @Override + public void run() { + CameraInterface.getInstance().doOpenCamera(JCameraView.this); + } + }.start(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + LogUtil.i("JCameraView SurfaceDestroyed"); + CameraInterface.getInstance().doDestroyCamera(); + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (event.getPointerCount() == 1) { + //显示对焦指示器 + setFocusViewWidthAnimation(event.getX(), event.getY()); + } + if (event.getPointerCount() == 2) { + Log.i("CJT", "ACTION_DOWN = " + 2); + } + break; + case MotionEvent.ACTION_MOVE: + if (event.getPointerCount() == 1) { + firstTouch = true; + } + if (event.getPointerCount() == 2) { + //第一个点 + float point_1_X = event.getX(0); + float point_1_Y = event.getY(0); + //第二个点 + float point_2_X = event.getX(1); + float point_2_Y = event.getY(1); + + float result = (float) Math.sqrt(Math.pow(point_1_X - point_2_X, 2) + Math.pow(point_1_Y - + point_2_Y, 2)); + + if (firstTouch) { + firstTouchLength = result; + firstTouch = false; + } + if ((int) (result - firstTouchLength) / zoomGradient != 0) { + firstTouch = true; + machine.zoom(result - firstTouchLength, CameraInterface.TYPE_CAPTURE); + } +// Log.i("CJT", "result = " + (result - firstTouchLength)); + } + break; + case MotionEvent.ACTION_UP: + firstTouch = true; + break; + } + return true; + } + + //对焦框指示器动画 + private void setFocusViewWidthAnimation(float x, float y) { + machine.foucs(x, y, new CameraInterface.FocusCallback() { + @Override + public void focusSuccess() { + mFoucsView.setVisibility(INVISIBLE); + } + }); + } + + private void updateVideoViewSize(float videoWidth, float videoHeight) { + if (videoWidth > videoHeight) { + LayoutParams videoViewParam; + int height = (int) ((videoHeight / videoWidth) * getWidth()); + videoViewParam = new LayoutParams(LayoutParams.MATCH_PARENT, height); + videoViewParam.gravity = Gravity.CENTER; + mVideoView.setLayoutParams(videoViewParam); + } + } + + /************************************************** + * 对外提供的API * + **************************************************/ + + public void setSaveVideoPath(String path) { + CameraInterface.getInstance().setSaveVideoPath(path); + } + + + public void setJCameraLisenter(JCameraListener jCameraLisenter) { + this.jCameraLisenter = jCameraLisenter; + } + + + private ErrorListener errorLisenter; + + //启动Camera错误回调 + public void setErrorLisenter(ErrorListener errorLisenter) { + this.errorLisenter = errorLisenter; + CameraInterface.getInstance().setErrorLinsenter(errorLisenter); + } + + //设置CaptureButton功能(拍照和录像) + public void setFeatures(int state) { + this.mCaptureLayout.setButtonFeatures(state); + } + + //设置录制质量 + public void setMediaQuality(int quality) { + CameraInterface.getInstance().setMediaQuality(quality); + } + + @Override + public void resetState(int type) { + switch (type) { + case TYPE_VIDEO: + stopVideo(); //停止播放 + //初始化VideoView + FileUtil.deleteFile(videoUrl); + mVideoView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + machine.start(mVideoView.getHolder(), screenProp); + break; + case TYPE_PICTURE: + mPhoto.setVisibility(INVISIBLE); + break; + case TYPE_SHORT: + break; + case TYPE_DEFAULT: + mVideoView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + break; + } + mSwitchCamera.setVisibility(VISIBLE); + mFlashLamp.setVisibility(VISIBLE); + mCaptureLayout.resetCaptureLayout(); + } + + @Override + public void confirmState(int type) { + switch (type) { + case TYPE_VIDEO: + stopVideo(); //停止播放 + mVideoView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + machine.start(mVideoView.getHolder(), screenProp); + if (jCameraLisenter != null) { + jCameraLisenter.recordSuccess(videoUrl, firstFrame); + } + break; + case TYPE_PICTURE: + mPhoto.setVisibility(INVISIBLE); + if (jCameraLisenter != null) { + jCameraLisenter.captureSuccess(captureBitmap); + } + break; + case TYPE_SHORT: + break; + case TYPE_DEFAULT: + break; + } + mCaptureLayout.resetCaptureLayout(); + } + + @Override + public void showPicture(Bitmap bitmap, boolean isVertical) { + if (isVertical) { + mPhoto.setScaleType(ImageView.ScaleType.FIT_XY); + } else { + mPhoto.setScaleType(ImageView.ScaleType.FIT_CENTER); + } + captureBitmap = bitmap; + mPhoto.setImageBitmap(bitmap); + mPhoto.setVisibility(VISIBLE); + mCaptureLayout.startAlphaAnimation(); + mCaptureLayout.startTypeBtnAnimator(); + } + + @Override + public void playVideo(Bitmap firstFrame, final String url) { + videoUrl = url; + JCameraView.this.firstFrame = firstFrame; + new Thread(new Runnable() { + @Override + public void run() { + try { + if (!mVideoView.getHolder().getSurface().isValid()) { + LogUtil.e(TAG, "Surface is not valid"); + return; + } + if (mMediaPlayer == null) { + mMediaPlayer = new MediaPlayer(); + } else { + mMediaPlayer.reset(); + } + mMediaPlayer.setDataSource(url); + mMediaPlayer.setSurface(mVideoView.getHolder().getSurface()); + mMediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer + .OnVideoSizeChangedListener() { + @Override + public void + onVideoSizeChanged(MediaPlayer mp, int width, int height) { + updateVideoViewSize(mMediaPlayer.getVideoWidth(), mMediaPlayer + .getVideoHeight()); + } + }); + mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + mMediaPlayer.start(); + } + }); + mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + LogUtil.e(TAG, "onError: " + what + ", " + extra); + return false; + } + }); + mMediaPlayer.setLooping(true); + mMediaPlayer.prepare(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + } + + @Override + public void stopVideo() { + if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + } + + @Override + public void setTip(String tip) { + mCaptureLayout.setTip(tip); + } + + @Override + public void startPreviewCallback() { + LogUtil.i("startPreviewCallback"); + handlerFoucs(mFoucsView.getWidth() / 2, mFoucsView.getHeight() / 2); + } + + @Override + public boolean handlerFoucs(float x, float y) { + if (y > mCaptureLayout.getTop()) { + return false; + } + mFoucsView.setVisibility(VISIBLE); + if (x < mFoucsView.getWidth() / 2) { + x = mFoucsView.getWidth() / 2; + } + if (x > layout_width - mFoucsView.getWidth() / 2) { + x = layout_width - mFoucsView.getWidth() / 2; + } + if (y < mFoucsView.getWidth() / 2) { + y = mFoucsView.getWidth() / 2; + } + if (y > mCaptureLayout.getTop() - mFoucsView.getWidth() / 2) { + y = mCaptureLayout.getTop() - mFoucsView.getWidth() / 2; + } + mFoucsView.setX(x - mFoucsView.getWidth() / 2); + mFoucsView.setY(y - mFoucsView.getHeight() / 2); + ObjectAnimator scaleX = ObjectAnimator.ofFloat(mFoucsView, "scaleX", 1, 0.6f); + ObjectAnimator scaleY = ObjectAnimator.ofFloat(mFoucsView, "scaleY", 1, 0.6f); + ObjectAnimator alpha = ObjectAnimator.ofFloat(mFoucsView, "alpha", 1f, 0.4f, 1f, 0.4f, 1f, 0.4f, 1f); + AnimatorSet animSet = new AnimatorSet(); + animSet.play(scaleX).with(scaleY).before(alpha); + animSet.setDuration(400); + animSet.start(); + return true; + } + + public void setLeftClickListener(ClickListener clickListener) { + this.leftClickListener = clickListener; + } + + public void setRightClickListener(ClickListener clickListener) { + this.rightClickListener = clickListener; + } + + private void setFlashRes() { + switch (type_flash) { + case TYPE_FLASH_AUTO: + mFlashLamp.setImageResource(R.drawable.ic_flash_auto); + machine.flash(Camera.Parameters.FLASH_MODE_AUTO); + break; + case TYPE_FLASH_ON: + mFlashLamp.setImageResource(R.drawable.ic_flash_on); + machine.flash(Camera.Parameters.FLASH_MODE_ON); + break; + case TYPE_FLASH_OFF: + mFlashLamp.setImageResource(R.drawable.ic_flash_off); + machine.flash(Camera.Parameters.FLASH_MODE_OFF); + break; + } + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/ReturnButton.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/ReturnButton.java new file mode 100644 index 0000000000000000000000000000000000000000..3765c4b0b49a9f050fcf3017fcd8d7201419657b --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/ReturnButton.java @@ -0,0 +1,63 @@ +package com.cjt2325.cameralibrary; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.view.View; + +/** + * ===================================== + * 作 者: 陈嘉桐 445263848@qq.com + * 版 本:1.0.4 + * 创建日期:2017/4/26 + * 描 述:向下箭头的退出按钮 + * ===================================== + */ +public class ReturnButton extends View { + + private int size; + + private int center_X; + private int center_Y; + private float strokeWidth; + + private Paint paint; + Path path; + + public ReturnButton(Context context, int size) { + this(context); + this.size = size; + center_X = size / 2; + center_Y = size / 2; + + strokeWidth = size / 15f; + + paint = new Paint(); + paint.setAntiAlias(true); + paint.setColor(Color.WHITE); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(strokeWidth); + + path = new Path(); + } + + public ReturnButton(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(size, size / 2); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + path.moveTo(strokeWidth, strokeWidth/2); + path.lineTo(center_X, center_Y - strokeWidth/2); + path.lineTo(size - strokeWidth, strokeWidth/2); + canvas.drawPath(path, paint); + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/TypeButton.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/TypeButton.java new file mode 100644 index 0000000000000000000000000000000000000000..4877ee4b181b5b55c8617d3a8bcba01425fcdea4 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/TypeButton.java @@ -0,0 +1,109 @@ +package com.cjt2325.cameralibrary; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.view.View; + +/** + * ===================================== + * 作 者: 陈嘉桐 445263848@qq.com + * 版 本:1.0.4 + * 创建日期:2017/4/26 + * 描 述:拍照或录制完成后弹出的确认和返回按钮 + * ===================================== + */ +public class TypeButton extends View{ + public static final int TYPE_CANCEL = 0x001; + public static final int TYPE_CONFIRM = 0x002; + private int button_type; + private int button_size; + + private float center_X; + private float center_Y; + private float button_radius; + + private Paint mPaint; + private Path path; + private float strokeWidth; + + private float index; + private RectF rectF; + + public TypeButton(Context context) { + super(context); + } + + public TypeButton(Context context, int type, int size) { + super(context); + this.button_type = type; + button_size = size; + button_radius = size / 2.0f; + center_X = size / 2.0f; + center_Y = size / 2.0f; + + mPaint = new Paint(); + path = new Path(); + strokeWidth = size / 50f; + index = button_size / 12f; + rectF = new RectF(center_X, center_Y - index, center_X + index * 2, center_Y + index); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(button_size, button_size); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + //如果类型为取消,则绘制内部为返回箭头 + if (button_type == TYPE_CANCEL) { + mPaint.setAntiAlias(true); + mPaint.setColor(0xEEDCDCDC); + mPaint.setStyle(Paint.Style.FILL); + canvas.drawCircle(center_X, center_Y, button_radius, mPaint); + + mPaint.setColor(Color.BLACK); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(strokeWidth); + + path.moveTo(center_X - index / 7, center_Y + index); + path.lineTo(center_X + index, center_Y + index); + + path.arcTo(rectF, 90, -180); + path.lineTo(center_X - index, center_Y - index); + canvas.drawPath(path, mPaint); + mPaint.setStyle(Paint.Style.FILL); + path.reset(); + path.moveTo(center_X - index, (float) (center_Y - index * 1.5)); + path.lineTo(center_X - index, (float) (center_Y - index / 2.3)); + path.lineTo((float) (center_X - index * 1.6), center_Y - index); + path.close(); + canvas.drawPath(path, mPaint); + + } + //如果类型为确认,则绘制绿色勾 + if (button_type == TYPE_CONFIRM) { + mPaint.setAntiAlias(true); + mPaint.setColor(0xFFFFFFFF); + mPaint.setStyle(Paint.Style.FILL); + canvas.drawCircle(center_X, center_Y, button_radius, mPaint); + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setColor(0xFF00CC00); + mPaint.setStrokeWidth(strokeWidth); + + path.moveTo(center_X - button_size / 6f, center_Y); + path.lineTo(center_X - button_size / 21.2f, center_Y + button_size / 7.7f); + path.lineTo(center_X + button_size / 4.0f, center_Y - button_size / 8.5f); + path.lineTo(center_X - button_size / 21.2f, center_Y + button_size / 9.4f); + path.close(); + canvas.drawPath(path, mPaint); + } + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/CaptureListener.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/CaptureListener.java new file mode 100644 index 0000000000000000000000000000000000000000..029f22a7c2b14cc96657de9554541ba72fecac9b --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/CaptureListener.java @@ -0,0 +1,20 @@ +package com.cjt2325.cameralibrary.listener; + +/** + * create by CJT2325 + * 445263848@qq.com. + */ + +public interface CaptureListener { + void takePictures(); + + void recordShort(long time); + + void recordStart(); + + void recordEnd(long time); + + void recordZoom(float zoom); + + void recordError(); +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ClickListener.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ClickListener.java new file mode 100644 index 0000000000000000000000000000000000000000..81cb1b7531925f925e1cb215bcc30ad556b63f4b --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ClickListener.java @@ -0,0 +1,13 @@ +package com.cjt2325.cameralibrary.listener; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.9 + * 创建日期:2017/10/7 + * 描 述: + * ===================================== + */ +public interface ClickListener { + void onClick(); +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ErrorListener.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ErrorListener.java new file mode 100644 index 0000000000000000000000000000000000000000..1b37d7d9c27217c5bd72ea5690c07afa9b3a1dcf --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ErrorListener.java @@ -0,0 +1,14 @@ +package com.cjt2325.cameralibrary.listener; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/6/5 + * 描 述: + * ===================================== + */ +public interface ErrorListener { + void onError(); + void AudioPermissionError(); +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/JCameraListener.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/JCameraListener.java new file mode 100644 index 0000000000000000000000000000000000000000..1cd9102e30ff86510d63ca250d220c535fba1bf6 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/JCameraListener.java @@ -0,0 +1,21 @@ +package com.cjt2325.cameralibrary.listener; + +import android.graphics.Bitmap; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/4/26 + * 描 述: + * ===================================== + */ +public interface JCameraListener { + + void captureSuccess(Bitmap bitmap); + + void recordSuccess(String url, Bitmap firstFrame); + + void quit(); + +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ResultListener.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ResultListener.java new file mode 100644 index 0000000000000000000000000000000000000000..d748472ec2f57441f2702c318565d4d0e2cf3d0d --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ResultListener.java @@ -0,0 +1,13 @@ +package com.cjt2325.cameralibrary.listener; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/9/8 + * 描 述: + * ===================================== + */ +public interface ResultListener { + void callback(); +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ReturnListener.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ReturnListener.java new file mode 100644 index 0000000000000000000000000000000000000000..631e2d75c039dd95c776ad387e73e835c7dcdcb9 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/ReturnListener.java @@ -0,0 +1,13 @@ +package com.cjt2325.cameralibrary.listener; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/4/26 + * 描 述: + * ===================================== + */ +public interface ReturnListener { + void onReturn(); +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/TypeListener.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/TypeListener.java new file mode 100644 index 0000000000000000000000000000000000000000..2a805c859b470da7a7f954eff2e2fc373ff5ce25 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/listener/TypeListener.java @@ -0,0 +1,15 @@ +package com.cjt2325.cameralibrary.listener; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/4/25 + * 描 述: + * ===================================== + */ +public interface TypeListener { + void cancel(); + + void confirm(); +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/state/BorrowPictureState.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/state/BorrowPictureState.java new file mode 100644 index 0000000000000000000000000000000000000000..e950a7e5bfce2e69a804c5aeae82329399aa2f40 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/state/BorrowPictureState.java @@ -0,0 +1,89 @@ +package com.cjt2325.cameralibrary.state; + +import android.view.Surface; +import android.view.SurfaceHolder; + +import com.cjt2325.cameralibrary.CameraInterface; +import com.cjt2325.cameralibrary.JCameraView; +import com.cjt2325.cameralibrary.util.LogUtil; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/9/8 + * 描 述: + * ===================================== + */ +public class BorrowPictureState implements State { + private final String TAG = "BorrowPictureState"; + private CameraMachine machine; + + public BorrowPictureState(CameraMachine machine) { + this.machine = machine; + } + + @Override + public void start(SurfaceHolder holder, float screenProp) { + CameraInterface.getInstance().doStartPreview(holder, screenProp); + machine.setState(machine.getPreviewState()); + } + + @Override + public void stop() { + + } + + + @Override + public void foucs(float x, float y, CameraInterface.FocusCallback callback) { + } + + @Override + public void swtich(SurfaceHolder holder, float screenProp) { + + } + + @Override + public void restart() { + + } + + @Override + public void capture() { + + } + + @Override + public void record(Surface surface,float screenProp) { + + } + + @Override + public void stopRecord(boolean isShort, long time) { + } + + @Override + public void cancle(SurfaceHolder holder, float screenProp) { + CameraInterface.getInstance().doStartPreview(holder, screenProp); + machine.getView().resetState(JCameraView.TYPE_PICTURE); + machine.setState(machine.getPreviewState()); + } + + @Override + public void confirm() { + machine.getView().confirmState(JCameraView.TYPE_PICTURE); + machine.setState(machine.getPreviewState()); + } + + @Override + public void zoom(float zoom, int type) { + LogUtil.i(TAG, "zoom"); + } + + @Override + public void flash(String mode) { + + } + +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/state/BorrowVideoState.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/state/BorrowVideoState.java new file mode 100644 index 0000000000000000000000000000000000000000..c21c9fae799ff1fd3271e48d306beaf2086521ea --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/state/BorrowVideoState.java @@ -0,0 +1,89 @@ +package com.cjt2325.cameralibrary.state; + +import android.view.Surface; +import android.view.SurfaceHolder; + +import com.cjt2325.cameralibrary.CameraInterface; +import com.cjt2325.cameralibrary.JCameraView; +import com.cjt2325.cameralibrary.util.LogUtil; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/9/8 + * 描 述: + * ===================================== + */ +public class BorrowVideoState implements State { + private final String TAG = "BorrowVideoState"; + private CameraMachine machine; + + public BorrowVideoState(CameraMachine machine) { + this.machine = machine; + } + + @Override + public void start(SurfaceHolder holder, float screenProp) { + CameraInterface.getInstance().doStartPreview(holder, screenProp); + machine.setState(machine.getPreviewState()); + } + + @Override + public void stop() { + + } + + @Override + public void foucs(float x, float y, CameraInterface.FocusCallback callback) { + + } + + + @Override + public void swtich(SurfaceHolder holder, float screenProp) { + + } + + @Override + public void restart() { + + } + + @Override + public void capture() { + + } + + @Override + public void record(Surface surface, float screenProp) { + + } + + @Override + public void stopRecord(boolean isShort, long time) { + + } + + @Override + public void cancle(SurfaceHolder holder, float screenProp) { + machine.getView().resetState(JCameraView.TYPE_VIDEO); + machine.setState(machine.getPreviewState()); + } + + @Override + public void confirm() { + machine.getView().confirmState(JCameraView.TYPE_VIDEO); + machine.setState(machine.getPreviewState()); + } + + @Override + public void zoom(float zoom, int type) { + LogUtil.i(TAG, "zoom"); + } + + @Override + public void flash(String mode) { + + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/state/CameraMachine.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/state/CameraMachine.java new file mode 100644 index 0000000000000000000000000000000000000000..2c1f297ba29bfb80a5c1d41fd208725eacda3493 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/state/CameraMachine.java @@ -0,0 +1,133 @@ +package com.cjt2325.cameralibrary.state; + +import android.content.Context; +import android.view.Surface; +import android.view.SurfaceHolder; + +import com.cjt2325.cameralibrary.CameraInterface; +import com.cjt2325.cameralibrary.view.CameraView; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/9/8 + * 描 述: + * ===================================== + */ +public class CameraMachine implements State { + + + private Context context; + private State state; + private CameraView view; +// private CameraInterface.CameraOpenOverCallback cameraOpenOverCallback; + + private State previewState; //浏览状态(空闲) + private State borrowPictureState; //浏览图片 + private State borrowVideoState; //浏览视频 + + public CameraMachine(Context context, CameraView view, CameraInterface.CameraOpenOverCallback + cameraOpenOverCallback) { + this.context = context; + previewState = new PreviewState(this); + borrowPictureState = new BorrowPictureState(this); + borrowVideoState = new BorrowVideoState(this); + //默认设置为空闲状态 + this.state = previewState; +// this.cameraOpenOverCallback = cameraOpenOverCallback; + this.view = view; + } + + public CameraView getView() { + return view; + } + + public Context getContext() { + return context; + } + + public void setState(State state) { + this.state = state; + } + + //获取浏览图片状态 + State getBorrowPictureState() { + return borrowPictureState; + } + + //获取浏览视频状态 + State getBorrowVideoState() { + return borrowVideoState; + } + + //获取空闲状态 + State getPreviewState() { + return previewState; + } + + @Override + public void start(SurfaceHolder holder, float screenProp) { + state.start(holder, screenProp); + } + + @Override + public void stop() { + state.stop(); + } + + @Override + public void foucs(float x, float y, CameraInterface.FocusCallback callback) { + state.foucs(x, y, callback); + } + + @Override + public void swtich(SurfaceHolder holder, float screenProp) { + state.swtich(holder, screenProp); + } + + @Override + public void restart() { + state.restart(); + } + + @Override + public void capture() { + state.capture(); + } + + @Override + public void record(Surface surface, float screenProp) { + state.record(surface, screenProp); + } + + @Override + public void stopRecord(boolean isShort, long time) { + state.stopRecord(isShort, time); + } + + @Override + public void cancle(SurfaceHolder holder, float screenProp) { + state.cancle(holder, screenProp); + } + + @Override + public void confirm() { + state.confirm(); + } + + + @Override + public void zoom(float zoom, int type) { + state.zoom(zoom, type); + } + + @Override + public void flash(String mode) { + state.flash(mode); + } + + public State getState() { + return this.state; + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/state/PreviewState.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/state/PreviewState.java new file mode 100644 index 0000000000000000000000000000000000000000..f073c9ddd801510e66d4df88ba799caf8ae22c6d --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/state/PreviewState.java @@ -0,0 +1,109 @@ +package com.cjt2325.cameralibrary.state; + +import android.graphics.Bitmap; +import android.view.Surface; +import android.view.SurfaceHolder; + +import com.cjt2325.cameralibrary.CameraInterface; +import com.cjt2325.cameralibrary.JCameraView; +import com.cjt2325.cameralibrary.util.LogUtil; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/9/8 + * 描 述:空闲状态 + * ===================================== + */ +class PreviewState implements State { + public static final String TAG = "PreviewState"; + + private CameraMachine machine; + + PreviewState(CameraMachine machine) { + this.machine = machine; + } + + @Override + public void start(SurfaceHolder holder, float screenProp) { + CameraInterface.getInstance().doStartPreview(holder, screenProp); + } + + @Override + public void stop() { + CameraInterface.getInstance().doStopPreview(); + } + + + @Override + public void foucs(float x, float y, CameraInterface.FocusCallback callback) { + LogUtil.i("preview state foucs"); + if (machine.getView().handlerFoucs(x, y)) { + CameraInterface.getInstance().handleFocus(machine.getContext(), x, y, callback); + } + } + + @Override + public void swtich(SurfaceHolder holder, float screenProp) { + CameraInterface.getInstance().switchCamera(holder, screenProp); + } + + @Override + public void restart() { + + } + + @Override + public void capture() { + CameraInterface.getInstance().takePicture(new CameraInterface.TakePictureCallback() { + @Override + public void captureResult(Bitmap bitmap, boolean isVertical) { + machine.getView().showPicture(bitmap, isVertical); + machine.setState(machine.getBorrowPictureState()); + LogUtil.i("capture"); + } + }); + } + + @Override + public void record(Surface surface, float screenProp) { + CameraInterface.getInstance().startRecord(surface, screenProp, null); + } + + @Override + public void stopRecord(final boolean isShort, long time) { + CameraInterface.getInstance().stopRecord(isShort, new CameraInterface.StopRecordCallback() { + @Override + public void recordResult(String url, Bitmap firstFrame) { + if (isShort) { + machine.getView().resetState(JCameraView.TYPE_SHORT); + } else { + machine.getView().playVideo(firstFrame, url); + machine.setState(machine.getBorrowVideoState()); + } + } + }); + } + + @Override + public void cancle(SurfaceHolder holder, float screenProp) { + LogUtil.i("浏览状态下,没有 cancle 事件"); + } + + @Override + public void confirm() { + LogUtil.i("浏览状态下,没有 confirm 事件"); + } + + @Override + public void zoom(float zoom, int type) { + LogUtil.i(TAG, "zoom"); + CameraInterface.getInstance().setZoom(zoom, type); + } + + @Override + public void flash(String mode) { + CameraInterface.getInstance().setFlashMode(mode); + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/state/State.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/state/State.java new file mode 100644 index 0000000000000000000000000000000000000000..26c6900ab578c647cc788f9a7a9c1612f8878bcb --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/state/State.java @@ -0,0 +1,41 @@ +package com.cjt2325.cameralibrary.state; + +import android.view.Surface; +import android.view.SurfaceHolder; + +import com.cjt2325.cameralibrary.CameraInterface; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/9/8 + * 描 述: + * ===================================== + */ +public interface State { + + void start(SurfaceHolder holder, float screenProp); + + void stop(); + + void foucs(float x, float y, CameraInterface.FocusCallback callback); + + void swtich(SurfaceHolder holder, float screenProp); + + void restart(); + + void capture(); + + void record(Surface surface, float screenProp); + + void stopRecord(boolean isShort, long time); + + void cancle(SurfaceHolder holder, float screenProp); + + void confirm(); + + void zoom(float zoom, int type); + + void flash(String mode); +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/util/AngleUtil.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/AngleUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..9e4dba92b388ed9945a288b2865a0cc05440f0df --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/AngleUtil.java @@ -0,0 +1,52 @@ +package com.cjt2325.cameralibrary.util; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/5/2 + * 描 述: + * ===================================== + */ +public class AngleUtil { + public static int getSensorAngle(float x, float y) { + if (Math.abs(x) > Math.abs(y)) { + /** + * 横屏倾斜角度比较大 + */ + if (x > 4) { + /** + * 左边倾斜 + */ + return 270; + } else if (x < -4) { + /** + * 右边倾斜 + */ + return 90; + } else { + /** + * 倾斜角度不够大 + */ + return 0; + } + } else { + if (y > 7) { + /** + * 左边倾斜 + */ + return 0; + } else if (y < -7) { + /** + * 右边倾斜 + */ + return 180; + } else { + /** + * 倾斜角度不够大 + */ + return 0; + } + } + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/util/AudioUtil.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/AudioUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..bb1f7f90e34588dc91cf7661f7a4fdc6c4bfce10 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/AudioUtil.java @@ -0,0 +1,24 @@ +package com.cjt2325.cameralibrary.util; + +import android.content.Context; +import android.media.AudioManager; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/4/26 + * 描 述: + * ===================================== + */ +public class AudioUtil { + public static void setAudioManage(Context context) { + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + audioManager.setStreamMute(AudioManager.STREAM_SYSTEM, true); + audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); + audioManager.setStreamVolume(AudioManager.STREAM_ALARM, 0, 0); + audioManager.setStreamVolume(AudioManager.STREAM_DTMF, 0, 0); + audioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION, 0, 0); + audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0); + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/util/CameraParamUtil.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/CameraParamUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..a4a699723626131441e460cea7eebb4803131453 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/CameraParamUtil.java @@ -0,0 +1,158 @@ +package com.cjt2325.cameralibrary.util; + +import android.content.Context; +import android.hardware.Camera; +import android.util.Log; +import android.view.Surface; +import android.view.WindowManager; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/4/25 + * 描 述: + * ===================================== + */ +public class CameraParamUtil { + private static final String TAG = "JCameraView"; + private CameraSizeComparator sizeComparator = new CameraSizeComparator(); + private static CameraParamUtil cameraParamUtil = null; + + private CameraParamUtil() { + + } + + public static CameraParamUtil getInstance() { + if (cameraParamUtil == null) { + cameraParamUtil = new CameraParamUtil(); + return cameraParamUtil; + } else { + return cameraParamUtil; + } + } + + public Camera.Size getPreviewSize(List list, int th, float rate) { + Collections.sort(list, sizeComparator); + int i = 0; + for (Camera.Size s : list) { + if ((s.width > th) && equalRate(s, rate)) { + Log.i(TAG, "MakeSure Preview :w = " + s.width + " h = " + s.height); + break; + } + i++; + } + if (i == list.size()) { + return getBestSize(list, rate); + } else { + return list.get(i); + } + } + + public Camera.Size getPictureSize(List list, int th, float rate) { + Collections.sort(list, sizeComparator); + int i = 0; + for (Camera.Size s : list) { + if ((s.width > th) && equalRate(s, rate)) { + Log.i(TAG, "MakeSure Picture :w = " + s.width + " h = " + s.height); + break; + } + i++; + } + if (i == list.size()) { + return getBestSize(list, rate); + } else { + return list.get(i); + } + } + + private Camera.Size getBestSize(List list, float rate) { + float previewDisparity = 100; + int index = 0; + for (int i = 0; i < list.size(); i++) { + Camera.Size cur = list.get(i); + float prop = (float) cur.width / (float) cur.height; + if (Math.abs(rate - prop) < previewDisparity) { + previewDisparity = Math.abs(rate - prop); + index = i; + } + } + return list.get(index); + } + + + private boolean equalRate(Camera.Size s, float rate) { + float r = (float) (s.width) / (float) (s.height); + return Math.abs(r - rate) <= 0.2; + } + + public boolean isSupportedFocusMode(List focusList, String focusMode) { + for (int i = 0; i < focusList.size(); i++) { + if (focusMode.equals(focusList.get(i))) { + Log.i(TAG, "FocusMode supported " + focusMode); + return true; + } + } + Log.i(TAG, "FocusMode not supported " + focusMode); + return false; + } + + public boolean isSupportedPictureFormats(List supportedPictureFormats, int jpeg) { + for (int i = 0; i < supportedPictureFormats.size(); i++) { + if (jpeg == supportedPictureFormats.get(i)) { + Log.i(TAG, "Formats supported " + jpeg); + return true; + } + } + Log.i(TAG, "Formats not supported " + jpeg); + return false; + } + + private class CameraSizeComparator implements Comparator { + public int compare(Camera.Size lhs, Camera.Size rhs) { + if (lhs.width == rhs.width) { + return 0; + } else if (lhs.width > rhs.width) { + return 1; + } else { + return -1; + } + } + + } + + public int getCameraDisplayOrientation(Context context, int cameraId) { + android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); + android.hardware.Camera.getCameraInfo(cameraId, info); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + int rotation = wm.getDefaultDisplay().getRotation(); + int degrees = 0; + switch (rotation) { + case Surface.ROTATION_0: + degrees = 0; + break; + case Surface.ROTATION_90: + degrees = 90; + break; + case Surface.ROTATION_180: + degrees = 180; + break; + case Surface.ROTATION_270: + degrees = 270; + break; + } + int result; + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + result = (info.orientation + degrees) % 360; + result = (360 - result) % 360; // compensate the mirror + } else { + // back-facing + result = (info.orientation - degrees + 360) % 360; + } + return result; + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/util/CheckPermission.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/CheckPermission.java new file mode 100644 index 0000000000000000000000000000000000000000..3d3900fa97bf402a040d264cd287e47e5919efb5 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/CheckPermission.java @@ -0,0 +1,104 @@ +package com.cjt2325.cameralibrary.util; + +import android.hardware.Camera; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder; +import android.util.Log; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/6/8 + * 描 述: + * ===================================== + */ +public class CheckPermission { + public static final int STATE_RECORDING = -1; + public static final int STATE_NO_PERMISSION = -2; + public static final int STATE_SUCCESS = 1; + + /** + * 用于检测是否具有录音权限 + * + * @return + */ + public static int getRecordState() { + int minBuffer = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat + .ENCODING_PCM_16BIT); + AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, 44100, AudioFormat + .CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, (minBuffer * 100)); + short[] point = new short[minBuffer]; + int readSize = 0; + try { + + audioRecord.startRecording();//检测是否可以进入初始化状态 + } catch (Exception e) { + if (audioRecord != null) { + audioRecord.release(); + audioRecord = null; + } + return STATE_NO_PERMISSION; + } + if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) { + //6.0以下机型都会返回此状态,故使用时需要判断bulid版本 + //检测是否在录音中 + if (audioRecord != null) { + audioRecord.stop(); + audioRecord.release(); + audioRecord = null; + Log.d("CheckAudioPermission", "录音机被占用"); + } + return STATE_RECORDING; + } else { + //检测是否可以获取录音结果 + + readSize = audioRecord.read(point, 0, point.length); + + + if (readSize <= 0) { + if (audioRecord != null) { + audioRecord.stop(); + audioRecord.release(); + audioRecord = null; + + } + Log.d("CheckAudioPermission", "录音的结果为空"); + return STATE_NO_PERMISSION; + + } else { + if (audioRecord != null) { + audioRecord.stop(); + audioRecord.release(); + audioRecord = null; + + } + + return STATE_SUCCESS; + } + } + } + + public synchronized static boolean isCameraUseable(int cameraID) { + boolean canUse = true; + Camera mCamera = null; + try { + mCamera = Camera.open(cameraID); + // setParameters 是针对魅族MX5。MX5通过Camera.open()拿到的Camera对象不为null + Camera.Parameters mParameters = mCamera.getParameters(); + mCamera.setParameters(mParameters); + } catch (Exception e) { + e.printStackTrace(); + canUse = false; + } finally { + if (mCamera != null) { + mCamera.release(); + } else { + canUse = false; + } + mCamera = null; + } + return canUse; + } +} \ No newline at end of file diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/util/DeviceUtil.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/DeviceUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..8f4b35dbed24aa7e403e343dda9b7a2dcc6d8146 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/DeviceUtil.java @@ -0,0 +1,46 @@ +package com.cjt2325.cameralibrary.util; + +import android.os.Build; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/6/9 + * 描 述: + * ===================================== + */ +public class DeviceUtil { + + private static String[] huaweiRongyao = { + "hwH60", //荣耀6 + "hwPE", //荣耀6 plus + "hwH30", //3c + "hwHol", //3c畅玩版 + "hwG750", //3x + "hw7D", //x1 + "hwChe2", //x1 + }; + + public static String getDeviceInfo() { + String handSetInfo = + "手机型号:" + Build.DEVICE + + "\n系统版本:" + Build.VERSION.RELEASE + + "\nSDK版本:" + Build.VERSION.SDK_INT; + return handSetInfo; + } + + public static String getDeviceModel() { + return Build.DEVICE; + } + + public static boolean isHuaWeiRongyao() { + int length = huaweiRongyao.length; + for (int i = 0; i < length; i++) { + if (huaweiRongyao[i].equals(getDeviceModel())) { + return true; + } + } + return false; + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/util/FileUtil.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/FileUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..ee3b1c8d44d7cd7c646dc0321643d625247feb3e --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/FileUtil.java @@ -0,0 +1,70 @@ +package com.cjt2325.cameralibrary.util; + +import android.graphics.Bitmap; +import android.os.Environment; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/4/25 + * 描 述: + * ===================================== + */ +public class FileUtil { + private static final String TAG = "CJT"; + private static final File parentPath = Environment.getExternalStorageDirectory(); + private static String storagePath = ""; + private static String DST_FOLDER_NAME = "JCamera"; + + private static String initPath() { + if (storagePath.equals("")) { + storagePath = parentPath.getAbsolutePath() + File.separator + DST_FOLDER_NAME; + File f = new File(storagePath); + if (!f.exists()) { + f.mkdir(); + } + } + return storagePath; + } + + public static String saveBitmap(String dir, Bitmap b) { + DST_FOLDER_NAME = dir; + String path = initPath(); + long dataTake = System.currentTimeMillis(); + String jpegName = path + File.separator + "picture_" + dataTake + ".jpg"; + try { + FileOutputStream fout = new FileOutputStream(jpegName); + BufferedOutputStream bos = new BufferedOutputStream(fout); + b.compress(Bitmap.CompressFormat.JPEG, 100, bos); + bos.flush(); + bos.close(); + return jpegName; + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + } + + public static boolean deleteFile(String url) { + boolean result = false; + File file = new File(url); + if (file.exists()) { + result = file.delete(); + } + return result; + } + + public static boolean isExternalStorageWritable() { + String state = Environment.getExternalStorageState(); + if (Environment.MEDIA_MOUNTED.equals(state)) { + return true; + } + return false; + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/util/LogUtil.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/LogUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..b12d3c2efda92bbf0546621c5020e7db529bcb31 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/LogUtil.java @@ -0,0 +1,56 @@ +package com.cjt2325.cameralibrary.util; + +import android.util.Log; + +//import static com.cjt2325.cameralibrary.BuildConfig.DEBUG; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/9/7 + * 描 述: + * ===================================== + */ +public class LogUtil { + + private static boolean DEBUG = true; + + private static final String DEFAULT_TAG = "CJT"; + + public static void i(String tag, String msg) { +// if (DEBUG) + Log.i(tag, msg); + } + + public static void v(String tag, String msg) { + if (DEBUG) + Log.v(tag, msg); + } + + public static void d(String tag, String msg) { + if (DEBUG) + Log.d(tag, msg); + } + + public static void e(String tag, String msg) { + if (DEBUG) + Log.e(tag, msg); + } + + public static void i(String msg) { + i(DEFAULT_TAG, msg); + } + + public static void v(String msg) { + v(DEFAULT_TAG, msg); + } + + public static void d(String msg) { + d(DEFAULT_TAG, msg); + } + + public static void e(String msg) { + e(DEFAULT_TAG, msg); + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/util/ScreenUtils.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/ScreenUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..477493f991a05f8fa5475364957cb515557abafe --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/util/ScreenUtils.java @@ -0,0 +1,29 @@ +package com.cjt2325.cameralibrary.util; + +import android.content.Context; +import android.util.DisplayMetrics; +import android.view.WindowManager; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/5/25 + * 描 述: + * ===================================== + */ +public class ScreenUtils { + public static int getScreenHeight(Context context) { + DisplayMetrics metric = new DisplayMetrics(); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getMetrics(metric); + return metric.heightPixels; + } + + public static int getScreenWidth(Context context) { + DisplayMetrics metric = new DisplayMetrics(); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getMetrics(metric); + return metric.widthPixels; + } +} diff --git a/cameraview/src/main/java/com/cjt2325/cameralibrary/view/CameraView.java b/cameraview/src/main/java/com/cjt2325/cameralibrary/view/CameraView.java new file mode 100644 index 0000000000000000000000000000000000000000..97be7187cf9da9974807eea9bd51a41940824314 --- /dev/null +++ b/cameraview/src/main/java/com/cjt2325/cameralibrary/view/CameraView.java @@ -0,0 +1,29 @@ +package com.cjt2325.cameralibrary.view; + +import android.graphics.Bitmap; + +/** + * ===================================== + * 作 者: 陈嘉桐 + * 版 本:1.1.4 + * 创建日期:2017/9/8 + * 描 述: + * ===================================== + */ +public interface CameraView { + void resetState(int type); + + void confirmState(int type); + + void showPicture(Bitmap bitmap, boolean isVertical); + + void playVideo(Bitmap firstFrame, String url); + + void stopVideo(); + + void setTip(String tip); + + void startPreviewCallback(); + + boolean handlerFoucs(float x, float y); +} diff --git a/cameraview/src/main/res/drawable/ic_camera.xml b/cameraview/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000000000000000000000000000000000000..f8535a5f0383161bca8979ef4b3a4d3067175bdc --- /dev/null +++ b/cameraview/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,9 @@ + + + diff --git a/cameraview/src/main/res/drawable/ic_flash_auto.xml b/cameraview/src/main/res/drawable/ic_flash_auto.xml new file mode 100644 index 0000000000000000000000000000000000000000..d11caebdfab2e29672db08412bf186fd6e563a53 --- /dev/null +++ b/cameraview/src/main/res/drawable/ic_flash_auto.xml @@ -0,0 +1,9 @@ + + + diff --git a/cameraview/src/main/res/drawable/ic_flash_off.xml b/cameraview/src/main/res/drawable/ic_flash_off.xml new file mode 100644 index 0000000000000000000000000000000000000000..e771850185ce9f84dde165aa77da52e62617a0f6 --- /dev/null +++ b/cameraview/src/main/res/drawable/ic_flash_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/cameraview/src/main/res/drawable/ic_flash_on.xml b/cameraview/src/main/res/drawable/ic_flash_on.xml new file mode 100644 index 0000000000000000000000000000000000000000..e9364686d180b68f713b5c78bf4ffaeefcef601c --- /dev/null +++ b/cameraview/src/main/res/drawable/ic_flash_on.xml @@ -0,0 +1,9 @@ + + + diff --git a/cameraview/src/main/res/drawable/ic_photo.xml b/cameraview/src/main/res/drawable/ic_photo.xml new file mode 100644 index 0000000000000000000000000000000000000000..0b56f71ae49dd00df98e30a2c297b5089271b922 --- /dev/null +++ b/cameraview/src/main/res/drawable/ic_photo.xml @@ -0,0 +1,9 @@ + + + diff --git a/cameraview/src/main/res/layout/camera_view.xml b/cameraview/src/main/res/layout/camera_view.xml new file mode 100644 index 0000000000000000000000000000000000000000..587ebd5ed144036fee3a32536636f26554a602bd --- /dev/null +++ b/cameraview/src/main/res/layout/camera_view.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cameraview/src/main/res/values/attrs.xml b/cameraview/src/main/res/values/attrs.xml new file mode 100644 index 0000000000000000000000000000000000000000..fbd47bed9dac5484914e4341fa37ac7d21086a72 --- /dev/null +++ b/cameraview/src/main/res/values/attrs.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cameraview/src/main/res/values/strings.xml b/cameraview/src/main/res/values/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..73b9f1ccc4bd8243d9b55a57a67552fbdbd9fdc1 --- /dev/null +++ b/cameraview/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + CameraLibrary + diff --git a/cameraview/src/test/java/cn/wildfirechat/cameraview/ExampleUnitTest.java b/cameraview/src/test/java/cn/wildfirechat/cameraview/ExampleUnitTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9149a260acc1eeed2368d3d35e6873e760489861 --- /dev/null +++ b/cameraview/src/test/java/cn/wildfirechat/cameraview/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package cn.wildfirechat.cameraview; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/chat/.gitignore b/chat/.gitignore index 796b96d1c402326528b4ba3c12ee9d92d0e212e9..42afabfd2abebf31384ca7797186a27a4b7dbee8 100644 --- a/chat/.gitignore +++ b/chat/.gitignore @@ -1 +1 @@ -/build +/build \ No newline at end of file diff --git a/chat/agconnect-services.json b/chat/agconnect-services.json new file mode 100644 index 0000000000000000000000000000000000000000..ba85fae047e3b5b09f69c477515d0c92aa974137 --- /dev/null +++ b/chat/agconnect-services.json @@ -0,0 +1,27 @@ +{ + "agcgw":{ + "backurl":"connect-drcn.dbankcloud.cn", + "url":"connect-drcn.hispace.hicloud.com" + }, + "client":{ + "cp_id":"890086000102114683", + "product_id":"9105385871708008303", + "client_id":"363427384201839680", + "client_secret":"D438F2D56536240581DC197C468900E6EA0E0FF37F563B79D7765B34A5077B7F", + "app_id":"100221325", + "package_name":"cn.wildfirechat.chat.open", + "api_key":"CV7m3BB7QS4BJqCKyrCkda06gp0tWFCMYzZP+a7I5NJHi7l4dTk0rP87aDCDh3dRU1sDrLOMiZr2bMNNKQ34zuwJ6KjT" + }, + "service":{ + "analytics":{ + "collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", + "resource_id":"p1", + "channel_id":"" + }, + "ml":{ + "mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn" + } + }, + "region":"CN", + "configuration_version":"1.0" +} \ No newline at end of file diff --git a/chat/build.gradle b/chat/build.gradle index 747e9fda66a7cad3e7cf9b3a48e3793f719f98b9..d30d59009a89740eb7e264171f57b48623a3bec3 100644 --- a/chat/build.gradle +++ b/chat/build.gradle @@ -1,4 +1,8 @@ apply plugin: 'com.android.application' +// 华为推送,agconnect-services.json 文件也得在在application module的根目录 +apply plugin: 'com.huawei.agconnect' + +apply plugin: 'com.google.gms.google-services' android { signingConfigs { @@ -9,116 +13,123 @@ android { storePassword 'wildfirechat' } } - compileSdkVersion 28 - aaptOptions.cruncherEnabled = false - aaptOptions.useNewCruncher = false + compileSdkVersion 34 + ndkVersion '18.1.5063045' defaultConfig { - applicationId "cn.wildfirechat.chat" - minSdkVersion 16 - targetSdkVersion 28 //当targetversion大于23时,需要使用fileprovider - versionCode 8 - versionName "0.5" + applicationId "cn.wildfirechat.chat.open" + minSdkVersion 24 + targetSdkVersion 34 + versionCode 60 + versionName '1.3.5' multiDexEnabled true javaCompileOptions { annotationProcessorOptions { - includeCompileClasspath true +// includeCompileClasspath true } } - manifestPlaceholders = [ - APP_ID : "cn.wildfirechat.chat", - - MI_APP_ID : "2882303761517722456", - MI_APP_KEY : "5731772292456", - - HMS_APP_ID : "100221325", - - MEIZU_APP_ID : "113616", - MEIZU_APP_KEY: "fcd886f51c144b45b87a67a28e2934d1" - ] - signingConfig signingConfigs.wfc - ndk { - abiFilters "armeabi-v7a", 'x86', 'x86_64' // ,'armeabi', 'arm64-v8a', 'x86', 'x86_64' + abiFilters 'armeabi-v7a', 'x86', 'x86_64', 'arm64-v8a' // ,'armeabi', 'arm64-v8a', 'x86', 'x86_64' } - } buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.wfc + //signingConfig signingConfigs.wfc } debug { - signingConfig signingConfigs.wfc + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - lintOptions { - abortOnError false + productFlavors { } - sourceSets { - main { - jniLibs.srcDirs = ['libs'] - res.srcDirs = ['src/main/res', 'src/main/res-av'] - } + namespace 'cn.wildfirechat.chat' + lint { + abortOnError false } - repositories { flatDir { dirs 'libs' } } - productFlavors { + buildFeatures { + buildConfig true } - compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 - } + // 如果提示 libc++_shared.so 冲突,打开下面的注释 +// packagingOptions { +// pickFirst 'lib/x86/libc++_shared.so' +// pickFirst 'lib/arm64-v8a/libc++_shared.so' +// pickFirst 'lib/armeabi-v7a/libc++_shared.so' +// pickFirst 'lib/x86_64/libc++_shared.so' +// } } dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'androidx.appcompat:appcompat:1.0.0-beta01' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'com.google.android.material:material:1.0.0-beta01' - implementation 'androidx.cardview:cardview:1.0.0' + implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"]) + implementation fileTree(dir: "../push-aar-dep", include: ["*.aar"]) + compileOnly fileTree(dir: "../uikit-aar-dep", include: ["zxing-lite-1.1.1.aar"]) + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' + implementation 'com.tencent.bugly:crashreport:latest.release' + implementation 'androidx.multidex:multidex:2.0.1' + + implementation project(':push') + + implementation 'com.github.tiann:FreeReflection:3.1.0' + + implementation project(':uvccamera') + + // 采用 library module 的方式依赖 uikit,如果需要采用 aar 方式集成,请仔细下面的注释 + implementation project(':uikit') + + // 采用 aar 方式依赖 uikit, + /* + implementation fileTree(dir: "uikit_aars", include: ["*.aar"]) + // 采用 aar 方式集成时,还需要下面这些依赖 + // wfc kit start + // project 相关的依赖,已编译成 aar 放到了 uikit_aars 目录 +// implementation project(':webrtc') +// implementation project(':client') +// implementation project(':avenginekit') +// implementation project(':emojilibrary') +// implementation project(':imagepicker') +// implementation project(':pttclient') + + implementation 'com.lqr.adapter:library:1.0.2' - implementation 'com.lqr.optionitemview:library:1.0.7' - implementation 'cjt.library.wheel:camera:1.1.9' implementation 'com.jaeger.statusbaruitl:library:1.3.5' - implementation 'com.kyleduo.switchbutton:library:1.4.4' - implementation 'com.lovedise:permissiongen:0.0.6' - implementation 'com.squareup.okhttp3:logging-interceptor:3.3.1' - implementation 'com.squareup.okhttp3:okhttp:3.10.0' - implementation 'com.squareup.okio:okio:1.14.0' - implementation 'com.jakewharton:butterknife:7.0.1' - implementation 'com.github.bumptech.glide:glide:4.8.0' - annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0' + + implementation 'androidx.appcompat:appcompat:1.3.0-alpha01' + implementation 'com.google.android.material:material:1.3.0-alpha01' + implementation 'cjt.library.wheel:camera:1.1.9' + implementation 'com.github.bumptech.glide:glide:4.9.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' implementation 'com.github.chrisbanes:PhotoView:2.3.0' - implementation 'androidx.multidex:multidex:2.0.1' - implementation files('libs/TencentLocationSDK_v4.9.7.12_r247861_161205_1104.jar') - implementation files('libs/TencentMapSDK_Raster_v_1.2.7_51ae0e7.jar') - implementation files('libs/TencentSearch1.1.3.jar') - implementation 'org.webrtc:google-webrtc:1.0.21929' implementation 'com.afollestad.material-dialogs:core:0.9.6.0' - implementation 'q.rorbin:badgeview:1.1.3' - implementation 'com.google.code.gson:gson:2.8.2' + implementation 'com.google.code.gson:gson:2.8.5' - // ViewModel and LiveData - def lifecycle_version = '2.0.0-beta01' - implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-android:2.8.7" + implementation "androidx.lifecycle:lifecycle-livedata:2.8.7" + implementation 'com.king.zxing:zxing-lite:1.1.1' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + implementation 'androidx.gridlayout:gridlayout:1.0.0' + implementation 'com.google.android:flexbox:2.0.1' - implementation project(':client') - implementation project(':push') - implementation project(':avenginekit') - implementation project(':emojilibrary') - implementation project(':imagepicker') + implementation 'com.tencent.map.geolocation:TencentLocationSdk-openplatform:7.2.6' + implementation 'io.kvh:amr:1.1.1' + implementation 'com.squareup.okhttp3:okhttp:3.11.0' + implementation 'com.squareup.okio:okio:1.14.0' + implementation 'com.tbuonomo.andrui:viewpagerdotsindicator:2.1.2' + implementation 'cn.aigestudio.wheelpicker:WheelPicker:1.1.3' - implementation 'com.tencent.bugly:crashreport:2.8.6.0' - implementation 'com.tencent.bugly:nativecrashreport:3.6.0.1' + // moment start + implementation project(':momentclient') + // moment end - implementation 'com.king.zxing:zxing-lite:1.0.6' - api 'com.google.zxing:core:3.3.3' + // kit wfc end + */ } diff --git a/chat/google-services.json b/chat/google-services.json new file mode 100644 index 0000000000000000000000000000000000000000..300781b85b2201166052dd0a46472a638d14aacf --- /dev/null +++ b/chat/google-services.json @@ -0,0 +1,39 @@ +{ + "project_info": { + "project_number": "387942673236", + "project_id": "wildfirechat-4753c", + "storage_bucket": "wildfirechat-4753c.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:387942673236:android:74dd044e8c21c0e4d776c1", + "android_client_info": { + "package_name": "cn.wildfirechat.chat.open" + } + }, + "oauth_client": [ + { + "client_id": "387942673236-0i2tbnt6dedng72ic7f4shbb7424lse1.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBe61zvx-avA6pADahZg5A2c1EWidhoNdc" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "387942673236-0i2tbnt6dedng72ic7f4shbb7424lse1.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/chat/libs/TencentLocationSDK_v4.9.7.12_r247861_161205_1104.jar b/chat/libs/TencentLocationSDK_v4.9.7.12_r247861_161205_1104.jar deleted file mode 100644 index 17b9e69f3efcdc49c242b7f23ac39f581787ee68..0000000000000000000000000000000000000000 Binary files a/chat/libs/TencentLocationSDK_v4.9.7.12_r247861_161205_1104.jar and /dev/null differ diff --git a/chat/libs/TencentMapSDK_Raster_v_1.2.7_51ae0e7.jar b/chat/libs/TencentMapSDK_Raster_v_1.2.7_51ae0e7.jar deleted file mode 100644 index 49a68d04025e84ad0fe5126aa2f7da65afc876e5..0000000000000000000000000000000000000000 Binary files a/chat/libs/TencentMapSDK_Raster_v_1.2.7_51ae0e7.jar and /dev/null differ diff --git a/chat/libs/armeabi-v7a/libtencentloc.so b/chat/libs/armeabi-v7a/libtencentloc.so deleted file mode 100755 index 490c0b6b3b87d87fdbcbca777384d5cb70e29e85..0000000000000000000000000000000000000000 Binary files a/chat/libs/armeabi-v7a/libtencentloc.so and /dev/null differ diff --git a/chat/libs/x86/libtencentloc.so b/chat/libs/x86/libtencentloc.so deleted file mode 100755 index 4126da80be87ad008ac4b26ab0d01b7051c47bc5..0000000000000000000000000000000000000000 Binary files a/chat/libs/x86/libtencentloc.so and /dev/null differ diff --git a/chat/proguard-rules.pro b/chat/proguard-rules.pro index 99e22b0ce0d72896b488a43f32f7f5360c224028..5d2a4d5f7092ff4250b8572a0757b0cd00221b27 100644 --- a/chat/proguard-rules.pro +++ b/chat/proguard-rules.pro @@ -26,3 +26,79 @@ -dontwarn com.tencent.bugly.** -keep public class com.tencent.bugly.**{*;} + +-dontshrink +-keep class org.webrtc.** { *; } +-keep class com.serenegiant.** { *; } +-keepclasseswithmembernames class * { native ; } + +-keep class okhttp3.** {*;} +-keepclassmembers class okhttp3.** { + *; +} + +-keep class com.tencent.**{*;} +-keepclassmembers class com.tenncent.mars.** { + *; +} + +#-keep class !cn.wildfire.chat.moment.**,!cn.wildfirechat.moment.**, **{ *; } +-keep class cn.wildfirechat.moment.MomentClient { + public void init(***); +} + +-keep class cn.wildfire.chat.app.login.model.** {*;} +-keepclassmembers class cn.wildfire.chat.app.login.model.** { + *; +} + +-keep class cn.wildfire.chat.kit.net.base.** {*;} +-keepclassmembers class cn.wildfire.chat.kit.net.base.** { + *; +} + +-keep class cn.wildfire.chat.kit.voip.conference.model.** {*;} +-keepclassmembers class cn.wildfire.chat.kit.voip.conference.model.** { + *; +} + +-keep class cn.wildfire.chat.kit.group.GroupAnnouncement {*;} +-keepclassmembers class cn.wildfire.chat.kit.group.GroupAnnouncement { + *; +} + +-keep class cn.wildfirechat.model.** {*;} +-keepclassmembers class cn.wildfirechat.model.** { + *; +} + +-keep class cn.wildfire.chat.kit.organization.model.** {*;} +-keepclassmembers class cn.wildfire.chat.kit.organization.model.** { + *; +} + +-keepclassmembers class cn.wildfirechat.** { + (...); +} + +-keepclassmembers class cn.wildfire.** { + (...); +} + +-keepclassmembers class cn.wildfirechat.message.MessageContent { + encode(); +} + +-keep class net.sourceforge.pinyin4j.** { *;} + + +#huawei push +-ignorewarnings +-keepattributes *Annotation* +-keepattributes Exceptions +-keepattributes InnerClasses +-keepattributes Signature +-keepattributes SourceFile,LineNumberTable +-keep class com.hianalytics.android.**{*;} +-keep class com.huawei.updatesdk.**{*;} +-keep class com.huawei.hms.**{*;} diff --git a/chat/src/main/AndroidManifest.xml b/chat/src/main/AndroidManifest.xml index f23e1aaa8fe0162559d48160d0b1d5cd546cf6be..70917de798da3c980522ac9857cd654e8bdafd31 100644 --- a/chat/src/main/AndroidManifest.xml +++ b/chat/src/main/AndroidManifest.xml @@ -1,21 +1,32 @@ + xmlns:tools="http://schemas.android.com/tools"> - + + + + + + + - - + @@ -23,10 +34,6 @@ - - - - @@ -34,189 +41,102 @@ - - + + + - - - - - - - + + + android:exported="true" + android:screenOrientation="portrait"> - - - - - - - - + android:name="cn.wildfire.chat.app.main.MainActivity" + android:exported="true" + android:label="野火IM" + android:launchMode="singleTask"> - + - + + + + + + + - - - - - - - - - - - - - - - - - - - + android:name="cn.wildfire.chat.app.main.PCLoginActivity" + android:exported="false" + android:label="PC登录"> - + + - - - - + + - + android:name="cn.wildfire.chat.app.setting.ChangePasswordActivity" + android:label="@string/set_new_password" /> + android:name="cn.wildfire.chat.app.setting.ResetPasswordActivity" + android:label="@string/set_new_password" /> + + - - - - diff --git "a/chat/src/main/assets/sticker/\347\250\213\345\272\217\345\221\230/\344\275\240\344\273\254\345\260\275\347\256\241\345\237\271\350\256\255\343\200\202\346\211\276\345\210\260\345\267\245\344\275\234\347\256\227\346\210\221\350\276\223.jpg" "b/chat/src/main/assets/sticker/\347\250\213\345\272\217\345\221\230/\344\275\240\344\273\254\345\260\275\347\256\241\345\237\271\350\256\255\343\200\202\346\211\276\345\210\260\345\267\245\344\275\234\347\256\227\346\210\221\350\276\223.jpg" deleted file mode 100644 index dd03488f918a3303357068939844a8d8aee7b74f..0000000000000000000000000000000000000000 Binary files "a/chat/src/main/assets/sticker/\347\250\213\345\272\217\345\221\230/\344\275\240\344\273\254\345\260\275\347\256\241\345\237\271\350\256\255\343\200\202\346\211\276\345\210\260\345\267\245\344\275\234\347\256\227\346\210\221\350\276\223.jpg" and /dev/null differ diff --git "a/chat/src/main/assets/sticker/\347\250\213\345\272\217\345\221\230/\346\212\212\351\222\261\350\277\230\347\273\231\346\210\221\357\274\214\346\210\221\344\270\215\345\255\246Java\344\272\206.jpg" "b/chat/src/main/assets/sticker/\347\250\213\345\272\217\345\221\230/\346\212\212\351\222\261\350\277\230\347\273\231\346\210\221\357\274\214\346\210\221\344\270\215\345\255\246Java\344\272\206.jpg" deleted file mode 100644 index c3cc1a828304eea9ed8d01d2305c17c5ddd51b21..0000000000000000000000000000000000000000 Binary files "a/chat/src/main/assets/sticker/\347\250\213\345\272\217\345\221\230/\346\212\212\351\222\261\350\277\230\347\273\231\346\210\221\357\274\214\346\210\221\344\270\215\345\255\246Java\344\272\206.jpg" and /dev/null differ diff --git "a/chat/src/main/assets/sticker/\347\250\213\345\272\217\345\221\230/\350\276\276\345\206\205\345\237\271\350\256\255.jpg" "b/chat/src/main/assets/sticker/\347\250\213\345\272\217\345\221\230/\350\276\276\345\206\205\345\237\271\350\256\255.jpg" deleted file mode 100644 index b7bf225e79a62680a351fd4260f23c6bc6ebe956..0000000000000000000000000000000000000000 Binary files "a/chat/src/main/assets/sticker/\347\250\213\345\272\217\345\221\230/\350\276\276\345\206\205\345\237\271\350\256\255.jpg" and /dev/null differ diff --git a/chat/src/main/java/cn/wildfire/chat/app/AppService.java b/chat/src/main/java/cn/wildfire/chat/app/AppService.java new file mode 100644 index 0000000000000000000000000000000000000000..6448659ffef918ad0066a1f378f744e39e4aca08 --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/AppService.java @@ -0,0 +1,885 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Handler; +import android.text.TextUtils; +import android.widget.Toast; + +import androidx.core.util.Pair; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import cn.wildfire.chat.app.login.model.LoginResult; +import cn.wildfire.chat.app.login.model.PCSession; +import cn.wildfire.chat.kit.AppServiceProvider; +import cn.wildfire.chat.kit.ChatManagerHolder; +import cn.wildfire.chat.kit.Config; +import cn.wildfire.chat.kit.WfcUIKit; +import cn.wildfire.chat.kit.favorite.FavoriteItem; +import cn.wildfire.chat.kit.group.GroupAnnouncement; +import cn.wildfire.chat.kit.net.BooleanCallback; +import cn.wildfire.chat.kit.net.OKHttpHelper; +import cn.wildfire.chat.kit.net.SimpleCallback; +import cn.wildfire.chat.kit.net.base.StatusResult; +import cn.wildfire.chat.kit.voip.conference.model.ConferenceInfo; +import cn.wildfirechat.chat.BuildConfig; +import cn.wildfirechat.model.Conversation; +import cn.wildfirechat.model.UserIdNamePortrait; +import cn.wildfirechat.remote.ChatManager; +import cn.wildfirechat.remote.GeneralCallback; +import cn.wildfirechat.remote.GeneralCallback2; +import okhttp3.MediaType; + +public class AppService implements AppServiceProvider { + private static final AppService Instance = new AppService(); + + /** + * App Server默认使用的是8888端口,替换为自己部署的服务时需要注意端口别填错了 + *
+ * 这是个 http 地址,http 前缀不能省略,否则会提示配置错误,然后直接退出 + *
+ * 正式商用时,建议用https,确保token安全 + *
+ *
+ */ + + // ipv6 + // public static String APP_SERVER_ADDRESS/*请仔细阅读上面的注释*/ = "http://[2409:8a00:32c0:1ee8:782d:fBc8:2e1b:4d10]:8888"; + // ipv4 + // public static String APP_SERVER_ADDRESS/*请仔细阅读上面的注释,http 前缀不能省略*/ = "http://wildfirechat.net:8888"; + public static String APP_SERVER_ADDRESS/*请仔细阅读上面的注释*/ = "https://app.wildfirechat.net"; + + private AppService() { + + } + + public static AppService Instance() { + return Instance; + } + + public interface LoginCallback { + void onUiSuccess(LoginResult loginResult); + + void onUiFailure(int code, String msg); + } + + public void passwordLogin(String mobile, String password, LoginCallback callback) { + + String url = APP_SERVER_ADDRESS + "/login_pwd"; + Map params = new HashMap<>(); + params.put("mobile", mobile); + params.put("password", password); + + //如果是android pad设备,需要改这里,另外需要在ClientService对象中修改设备类型,请在ClientService代码中搜索"android pad" + //if(当前设备是android pad) + // params.put("platform", new Integer(9)); + //else + params.put("platform", new Integer(2)); + + try { + params.put("clientId", ChatManagerHolder.gChatManager.getClientId()); + } catch (Exception e) { + e.printStackTrace(); + callback.onUiFailure(-1, "网络出来问题了。。。"); + return; + } + + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(LoginResult loginResult) { + callback.onUiSuccess(loginResult); + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + public void smsLogin(String phoneNumber, String authCode, LoginCallback callback) { + + String url = APP_SERVER_ADDRESS + "/login"; + Map params = new HashMap<>(); + params.put("mobile", phoneNumber); + params.put("code", authCode); + + + //Platform_iOS = 1, + //Platform_Android = 2, + //Platform_Windows = 3, + //Platform_OSX = 4, + //Platform_WEB = 5, + //Platform_WX = 6, + //Platform_linux = 7, + //Platform_iPad = 8, + //Platform_APad = 9, + + //如果是android pad设备,需要改这里,另外需要在ClientService对象中修改设备类型,请在ClientService代码中搜索"android pad" + //if(当前设备是android pad) + // params.put("platform", new Integer(9)); + //else + params.put("platform", new Integer(2)); + + try { + params.put("clientId", ChatManagerHolder.gChatManager.getClientId()); + } catch (Exception e) { + e.printStackTrace(); + callback.onUiFailure(-1, "获取clientId失败"); + return; + } + + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(LoginResult loginResult) { + callback.onUiSuccess(loginResult); + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + + public void resetPassword(String mobile, String code, String password, SimpleCallback callback) { + String url = APP_SERVER_ADDRESS + "/reset_pwd"; + Map params = new HashMap<>(); + if (!TextUtils.isEmpty(mobile)) { + params.put("mobile", mobile); + } + params.put("resetCode", code); + params.put("newPassword", password); + + OKHttpHelper.post(url, params, callback); + } + + public void changePassword(String oldPassword, String newPassword, SimpleCallback callback) { + String url = APP_SERVER_ADDRESS + "/change_pwd"; + Map params = new HashMap<>(); + params.put("oldPassword", oldPassword); + params.put("newPassword", newPassword); + + OKHttpHelper.post(url, params, callback); + + } + + public interface SendCodeCallback { + void onUiSuccess(); + + void onUiFailure(int code, String msg); + } + + public void requestAuthCode(String phoneNumber, SendCodeCallback callback) { + + String url = APP_SERVER_ADDRESS + "/send_code"; + Map params = new HashMap<>(); + params.put("mobile", phoneNumber); + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(StatusResult statusResult) { + if (statusResult.getCode() == 0) { + callback.onUiSuccess(); + } else { + callback.onUiFailure(statusResult.getCode(), ""); + } + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + + } + + public void requestResetAuthCode(String phoneNumber, SendCodeCallback callback) { + + String url = APP_SERVER_ADDRESS + "/send_reset_code"; + Map params = new HashMap<>(); + if (!TextUtils.isEmpty(phoneNumber)) { + params.put("mobile", phoneNumber); + } + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(StatusResult statusResult) { + if (statusResult.getCode() == 0) { + callback.onUiSuccess(); + } else { + callback.onUiFailure(statusResult.getCode(), ""); + } + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + + } + + public interface ScanPCCallback { + void onUiSuccess(PCSession pcSession); + + void onUiFailure(int code, String msg); + } + + public void scanPCLogin(String token, ScanPCCallback callback) { + String url = APP_SERVER_ADDRESS + "/scan_pc"; + url += "/" + token; + OKHttpHelper.post(url, null, new SimpleCallback() { + @Override + public void onUiSuccess(PCSession pcSession) { + if (pcSession.getStatus() == 1) { + callback.onUiSuccess(pcSession); + } else { + callback.onUiFailure(pcSession.getStatus(), ""); + } + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + public interface PCLoginCallback { + void onUiSuccess(); + + void onUiFailure(int code, String msg); + } + + public void confirmPCLogin(String token, String userId, PCLoginCallback callback) { + String url = APP_SERVER_ADDRESS + "/confirm_pc"; + + Map params = new HashMap<>(3); + params.put("user_id", userId); + params.put("token", token); + params.put("quick_login", 1); + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(PCSession pcSession) { + if (pcSession.getStatus() == 2) { + callback.onUiSuccess(); + } else { + callback.onUiFailure(pcSession.getStatus(), ""); + } + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + public void cancelPCLogin(String token, PCLoginCallback callback) { + String url = APP_SERVER_ADDRESS + "/cancel_pc"; + + Map params = new HashMap<>(3); + params.put("token", token); + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(PCSession pcSession) { + if (pcSession.getStatus() == 2) { + callback.onUiSuccess(); + } else { + callback.onUiFailure(pcSession.getStatus(), ""); + } + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + + @Override + public void getGroupAnnouncement(String groupId, AppServiceProvider.GetGroupAnnouncementCallback callback) { + //从SP中获取到历史数据callback回去,然后再从网络刷新 + String url = APP_SERVER_ADDRESS + "/get_group_announcement"; + + Map params = new HashMap<>(2); + params.put("groupId", groupId); + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(GroupAnnouncement announcement) { + callback.onUiSuccess(announcement); + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + @Override + public void updateGroupAnnouncement(String groupId, String announcement, AppServiceProvider.UpdateGroupAnnouncementCallback callback) { + //更新到应用服务,再保存到本地SP中 + String url = APP_SERVER_ADDRESS + "/put_group_announcement"; + + Map params = new HashMap<>(2); + params.put("groupId", groupId); + params.put("author", ChatManagerHolder.gChatManager.getUserId()); + params.put("text", announcement); + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(GroupAnnouncement announcement) { + callback.onUiSuccess(announcement); + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + @Override + public void showPCLoginActivity(String userId, String token, int platform) { + Intent intent = new Intent(BuildConfig.APPLICATION_ID + ".pc.login"); + intent.putExtra("token", token); + intent.putExtra("isConfirmPcLogin", true); + intent.putExtra("platform", platform); + WfcUIKit.startActivity(ChatManager.Instance().getApplicationContext(), intent); + } + + @Override + public void uploadLog(SimpleCallback callback) { + List filePaths = ChatManager.Instance().getLogFilesPath(); + if (filePaths == null || filePaths.isEmpty()) { + if (callback != null) { + callback.onUiFailure(-1, "没有日志文件"); + } + return; + } + Context context = ChatManager.Instance().getApplicationContext(); + if (context == null) { + if (callback != null) { + callback.onUiFailure(-1, "not init"); + } + return; + } + SharedPreferences sp = context.getSharedPreferences("log_history", Context.MODE_PRIVATE); + + String userId = ChatManager.Instance().getUserId(); + String url = APP_SERVER_ADDRESS + "/logs/" + userId + "/upload"; + + int toUploadCount = 0; + Collections.sort(filePaths); + for (int i = 0; i < filePaths.size(); i++) { + String path = filePaths.get(i); + File file = new File(path); + if (!file.exists()) { + continue; + } + // 重复上传最后一个日志文件,因为上传之后,还会追加内容 + if (!sp.contains(path) || i == filePaths.size() - 1) { + toUploadCount++; + OKHttpHelper.upload(url, null, file, MediaType.get("application/octet-stream"), new SimpleCallback() { + @Override + public void onUiSuccess(Void aVoid) { + if (callback != null) { + callback.onSuccess(url); + } + sp.edit().putBoolean(path, true).commit(); + } + + @Override + public void onUiFailure(int code, String msg) { + if (callback != null) { + callback.onUiFailure(code, msg); + } + } + }); + } + } + if (toUploadCount == 0) { + if (callback != null) { + callback.onUiFailure(-1, "所有日志都已上传"); + } + } + } + + @Override + public void changeName(String newName, SimpleCallback callback) { + String url = APP_SERVER_ADDRESS + "/change_name"; + + Map params = new HashMap<>(2); + params.put("newName", newName); + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(Void aVoid) { + callback.onUiSuccess(null); + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + @Override + public void getFavoriteItems(int startId, int count, GetFavoriteItemCallback callback) { + if (callback == null) { + return; + } + + String url = APP_SERVER_ADDRESS + "/fav/list"; + Map params = new HashMap<>(); + params.put("id", startId); + params.put("count", count); + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(String s) { + try { + JSONObject obj = new JSONObject(s); + JSONObject result = obj.getJSONObject("result"); + boolean hasMore = result.getBoolean("hasMore"); + JSONArray items = result.getJSONArray("items"); + + List favoriteItems = new ArrayList<>(); + for (int i = 0; i < items.length(); i++) { + JSONObject itemObj = items.getJSONObject(i); + Conversation conversation = new Conversation(Conversation.ConversationType.type(itemObj.getInt("convType")), itemObj.getString("convTarget"), itemObj.getInt("convLine")); + FavoriteItem item = new FavoriteItem(itemObj.getInt("id"), + itemObj.optLong("messageUid"), + itemObj.getInt("type"), + itemObj.getLong("timestamp"), + conversation, + itemObj.getString("origin"), + itemObj.getString("sender"), + itemObj.getString("title"), + itemObj.getString("url"), + itemObj.getString("thumbUrl"), + itemObj.getString("data") + ); + + favoriteItems.add(item); + } + + callback.onUiSuccess(favoriteItems, hasMore); + + } catch (JSONException e) { + e.printStackTrace(); + callback.onUiFailure(-1, e.getMessage()); + } + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + @Override + public void addFavoriteItem(FavoriteItem item, SimpleCallback callback) { + String url = APP_SERVER_ADDRESS + "/fav/add"; + Map params = new HashMap<>(); + params.put("messageUid", item.getMessageUid()); + params.put("type", item.getFavType()); + params.put("convType", item.getConversation().type.getValue()); + params.put("convTarget", item.getConversation().target); + params.put("convLine", item.getConversation().line); + params.put("origin", item.getOrigin()); + params.put("sender", item.getSender()); + params.put("title", item.getTitle()); + params.put("url", item.getUrl()); + params.put("thumbUrl", item.getThumbUrl()); + params.put("data", item.getData()); + + OKHttpHelper.post(url, params, callback); + } + + @Override + public void removeFavoriteItem(int favId, SimpleCallback callback) { + String url = APP_SERVER_ADDRESS + "/fav/del/" + favId; + OKHttpHelper.post(url, null, callback); + } + + public static void validateConfig(Context context) { + if (TextUtils.isEmpty(Config.IM_SERVER_HOST) + || Config.IM_SERVER_HOST.startsWith("http") + || Config.IM_SERVER_HOST.contains(":") + || TextUtils.isEmpty(APP_SERVER_ADDRESS) + || (!APP_SERVER_ADDRESS.startsWith("http") && !APP_SERVER_ADDRESS.startsWith("https")) + || Config.IM_SERVER_HOST.equals("127.0.0.1") + || APP_SERVER_ADDRESS.contains("127.0.0.1") + || (!Config.IM_SERVER_HOST.contains("wildfirechat.net") && APP_SERVER_ADDRESS.contains("wildfirechat.net")) + || (Config.IM_SERVER_HOST.contains("wildfirechat.net") && !APP_SERVER_ADDRESS.contains("wildfirechat.net")) + ) { + Toast.makeText(context, "配置错误,请检查配置,应用即将关闭...", Toast.LENGTH_LONG).show(); + new Handler().postDelayed(() -> { + throw new IllegalArgumentException("config error\n 参数配置错误\n请仔细阅读配置相关注释,并检查配置!\n"); + }, 5 * 1000); + } + + if (Config.ICE_SERVERS != null) { + for (String[] ice : Config.ICE_SERVERS) { + if (!ice[0].startsWith("turn")) { + Toast.makeText(context, "Turn配置错误,请检查配置,应用即将关闭...", Toast.LENGTH_LONG).show(); + new Handler().postDelayed(() -> { + throw new IllegalArgumentException("config error\n 参数配置错误\n请仔细阅读配置相关注释,并检查配置!\n"); + }, 5 * 1000); + } + } + } + + if (!BuildConfig.DEBUG && BuildConfig.APPLICATION_ID.startsWith("cn.wildfire")) { + Toast.makeText(context, "上线时,请勿直接使用野火的包名!!!", Toast.LENGTH_LONG).show(); + } + } + + @Override + public void getMyPrivateConferenceId(GeneralCallback2 callback) { + if (callback == null) { + return; + } + String url = APP_SERVER_ADDRESS + "/conference/get_my_id"; + OKHttpHelper.post(url, null, new SimpleCallback() { + + @Override + public void onUiSuccess(String response) { + try { + JSONObject object = new JSONObject(response); + if (object.optInt("code", -1) == 0) { + String conferenceId = object.getString("result"); + callback.onSuccess(conferenceId); + return; + } + } catch (JSONException e) { + e.printStackTrace(); + } + callback.onFail(-1); + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onFail(code); + } + }); + } + + @Override + public void createConference(ConferenceInfo info, GeneralCallback2 callback) { + String url = APP_SERVER_ADDRESS + "/conference/create"; + OKHttpHelper.post(url, info, new SimpleCallback() { + @Override + public void onUiSuccess(String response) { + try { + JSONObject object = new JSONObject(response); + if (object.optInt("code", -1) == 0) { + String conferenceId = object.getString("result"); + callback.onSuccess(conferenceId); + return; + } + } catch (JSONException e) { + e.printStackTrace(); + } + callback.onFail(-1); + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onFail(code); + } + }); + } + + @Override + public void queryConferenceInfo(String conferenceId, String password, QueryConferenceInfoCallback callback) { + if (callback == null) { + return; + } + String url = APP_SERVER_ADDRESS + "/conference/info"; + Map map = new HashMap<>(); + map.put("conferenceId", conferenceId); + if (!TextUtils.isEmpty(password)) { + map.put("password", password); + } + OKHttpHelper.post(url, map, new SimpleCallback() { + + @Override + public void onUiSuccess(ConferenceInfo info) { + callback.onSuccess(info); + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onFail(code, msg); + } + }); + } + + @Override + public void destroyConference(String conferenceId, GeneralCallback callback) { + String url = APP_SERVER_ADDRESS + "/conference/destroy/" + conferenceId; + OKHttpHelper.post(url, null, new SimpleCallback() { + + @Override + public void onUiSuccess(StatusResult statusResult) { + if (statusResult.isSuccess()) { + callback.onSuccess(); + } else { + callback.onFail(statusResult.getCode()); + } + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onFail(code); + } + }); + } + + @Override + public void favConference(String conferenceId, GeneralCallback callback) { + String url = APP_SERVER_ADDRESS + "/conference/fav/" + conferenceId; + OKHttpHelper.post(url, null, new SimpleCallback() { + @Override + public void onUiSuccess(StatusResult statusResult) { + if (callback != null) { + if (statusResult.isSuccess()) { + callback.onSuccess(); + } else { + callback.onFail(statusResult.getCode()); + } + } + + } + + @Override + public void onUiFailure(int code, String msg) { + if (callback != null) { + callback.onFail(code); + } + } + }); + } + + @Override + public void unfavConference(String conferenceId, GeneralCallback callback) { + String url = APP_SERVER_ADDRESS + "/conference/unfav/" + conferenceId; + OKHttpHelper.post(url, null, new SimpleCallback() { + @Override + public void onUiSuccess(StatusResult statusResult) { + if (callback != null) { + if (statusResult.isSuccess()) { + callback.onSuccess(); + } else { + callback.onFail(statusResult.getCode()); + } + } + } + + @Override + public void onUiFailure(int code, String msg) { + if (callback != null) { + callback.onFail(code); + } + } + }); + } + + @Override + public void isFavConference(String conferenceId, BooleanCallback callback) { + String url = APP_SERVER_ADDRESS + "/conference/is_fav/" + conferenceId; + OKHttpHelper.post(url, null, new SimpleCallback() { + @Override + public void onUiSuccess(StatusResult statusResult) { + if (callback != null) { + if (statusResult.getCode() == 0) { + callback.onSuccess(true); + } else if (statusResult.getCode() == 16) { + callback.onSuccess(false); + } else { + callback.onFail(-1, ""); + } + } + } + + @Override + public void onUiFailure(int code, String msg) { + if (callback != null) { + callback.onFail(code, msg); + } + } + }); + } + + @Override + public void getFavConferences(FavConferenceCallback callback) { + String url = APP_SERVER_ADDRESS + "/conference/fav_conferences"; + OKHttpHelper.post(url, null, new SimpleCallback>() { + @Override + public void onUiSuccess(List favConferences) { + if (callback != null) { + callback.onSuccess(favConferences); + } + } + + @Override + public void onUiFailure(int code, String msg) { + if (callback != null) { + callback.onFail(code, msg); + } + } + }); + } + + @Override + public void updateConference(ConferenceInfo conferenceInfo, GeneralCallback callback) { + String url = APP_SERVER_ADDRESS + "/conference/put_info"; + OKHttpHelper.post(url, conferenceInfo, new SimpleCallback() { + @Override + public void onUiSuccess(StatusResult statusResult) { + if (callback != null) { + callback.onSuccess(); + } + } + + @Override + public void onUiFailure(int code, String msg) { + if (callback != null) { + callback.onFail(code); + } + } + }); + } + + @Override + public void recordConference(String conferenceId, boolean record, GeneralCallback callback) { + String url = APP_SERVER_ADDRESS + "/conference/recording/" + conferenceId; + Map params = new HashMap<>(); + params.put("recording", record); + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(StatusResult statusResult) { + if (callback != null) { + callback.onSuccess(); + } + } + + @Override + public void onUiFailure(int code, String msg) { + if (callback != null) { + callback.onFail(code); + } + } + }); + } + + @Override + public void setConferenceFocusUserId(String conferenceId, String userId, GeneralCallback callback) { + String url = APP_SERVER_ADDRESS + "/conference/focus/" + conferenceId; + Map params = new HashMap<>(); + params.put("userId", TextUtils.isEmpty(userId) ? "" : userId); + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(StatusResult statusResult) { + if (callback != null) { + callback.onSuccess(); + } + } + + @Override + public void onUiFailure(int code, String msg) { + if (callback != null) { + callback.onFail(code); + } + } + }); + } + + @Override + public void getGroupPortrait(String groupId, SimpleCallback callback) { + if (callback == null) { + return; + } + getGroupMembersForPortrait(groupId, new GetGroupMemberForPotraitCallback() { + @Override + public void onUiSuccess(List first9Members) { + if (first9Members.size() > 9) { + first9Members = first9Members.subList(0, 9); + } + List> namePortraitPairs = new ArrayList<>(); + for (UserIdNamePortrait userInfo : first9Members) { + namePortraitPairs.add(new Pair<>(userInfo.name, userInfo.portrait)); + } + String portrait = _groupDefaultPortrait(groupId, namePortraitPairs); + callback.onUiSuccess(portrait); + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + + private String _groupDefaultPortrait(String groupId, List> namePortraitPairs) { + JSONObject request = new JSONObject(); + try { + JSONArray reqMembers = new JSONArray(); + if (namePortraitPairs.size() > 9) { + namePortraitPairs = namePortraitPairs.subList(0, 9); + } + for (Pair userInfo : namePortraitPairs) { + JSONObject obj = new JSONObject(); + if (TextUtils.isEmpty(userInfo.first) || userInfo.first.startsWith(AppService.APP_SERVER_ADDRESS)) { + obj.put("name", userInfo.first); + } else { + obj.put("avatarUrl", userInfo.second); + } + reqMembers.put(obj); + } + request.put("members", reqMembers); + } catch (JSONException e) { + e.printStackTrace(); + } + return AppService.APP_SERVER_ADDRESS + "/avatar/group?request=" + Uri.encode(request.toString()); + } + + private void getGroupMembersForPortrait(String groupId, GetGroupMemberForPotraitCallback callback) { + String url = APP_SERVER_ADDRESS + "/group/members_for_portrait"; + + Map params = new HashMap<>(2); + params.put("groupId", groupId); + OKHttpHelper.post(url, params, new SimpleCallback>() { + @Override + public void onUiSuccess(List userIdNamePortraits) { + callback.onUiSuccess(userIdNamePortraits); + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + +} diff --git a/chat/src/main/java/cn/wildfire/chat/app/BaseApp.java b/chat/src/main/java/cn/wildfire/chat/app/BaseApp.java index 5fdae8cde7fc44fe9515b41a7186d30607eb386a..4ba1ee1dbf406b16109c7f2447736c2819be9c0c 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/BaseApp.java +++ b/chat/src/main/java/cn/wildfire/chat/app/BaseApp.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app; import android.content.Context; diff --git a/chat/src/main/java/cn/wildfire/chat/app/Config.java b/chat/src/main/java/cn/wildfire/chat/app/Config.java deleted file mode 100644 index ad3ed69d5e39ec7cc4fa8a4db378fe655df3c30e..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/app/Config.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.wildfire.chat.app; - -import android.os.Environment; - -/** - * Created by heavyrain lee on 2017/11/24. - */ - -public interface Config { - - String IM_SERVER_HOST = "wildfirechat.cn"; - int IM_SERVER_PORT = 80; - - String APP_SERVER_HOST = "wildfirechat.cn"; - int APP_SERVER_PORT = 8888; - - String ICE_ADDRESS = "turn:turn.liyufan.win:3478"; - String ICE_USERNAME = "wfchat"; - String ICE_PASSWORD = "wfchat"; - - int DEFAULT_MAX_AUDIO_RECORD_TIME_SECOND = 120; - - String SP_NAME = "config"; - String SP_KEY_SHOW_GROUP_MEMBER_ALIAS = "show_group_member_alias:%s"; - - String VIDEO_SAVE_DIR = Environment.getExternalStorageDirectory().getPath() + "/wfc/video"; - String AUDIO_SAVE_DIR = Environment.getExternalStorageDirectory().getPath() + "/wfc/audio"; - String PHOTO_SAVE_DIR = Environment.getExternalStorageDirectory().getPath() + "/wfc/photo"; - String FILE_SAVE_DIR = Environment.getExternalStorageDirectory().getPath() + "/wfc/file"; -} diff --git a/chat/src/main/java/cn/wildfire/chat/app/MyApp.java b/chat/src/main/java/cn/wildfire/chat/app/MyApp.java index 6be04973995bf4bd42a6847ed26eea0304bdb41f..962b40347ea8f46a746a77417b427993f1adcb23 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/MyApp.java +++ b/chat/src/main/java/cn/wildfire/chat/app/MyApp.java @@ -1,47 +1,140 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app; -import android.app.ActivityManager; +import android.annotation.SuppressLint; +import android.app.Application; import android.content.Context; +import android.content.SharedPreferences; +import android.os.Build; +import android.text.TextUtils; -import com.tencent.bugly.crashreport.CrashReport; +import java.lang.reflect.Method; -import cn.wildfire.chat.app.third.location.viewholder.LocationMessageContentViewHolder; +import cn.wildfire.chat.app.misc.KeyStoreUtil; +import cn.wildfire.chat.kit.ChatManagerHolder; +import cn.wildfire.chat.kit.Config; import cn.wildfire.chat.kit.WfcUIKit; import cn.wildfire.chat.kit.conversation.message.viewholder.MessageViewHolderManager; +import cn.wildfire.chat.kit.third.location.viewholder.LocationMessageContentViewHolder; +import cn.wildfire.chat.kit.utils.LocaleUtils; +import cn.wildfirechat.chat.BuildConfig; +import cn.wildfirechat.chat.R; +import cn.wildfirechat.push.PushService; +import cn.wildfirechat.remote.ChatManager; +import cn.wildfirechat.remote.OnConnectToServerListener; + +public class MyApp extends BaseApp implements OnConnectToServerListener { + // 一定记得替换为你们自己的,ID请从BUGLY官网申请。关于BUGLY,可以从BUGLY官网了解,或者百度。 + public static String BUGLY_ID = "15dfd5f6d1"; -public class MyApp extends BaseApp { + public static String routeHost; + public static int routePort; - private WfcUIKit wfcUIKit; + public static String longLinkHost; @Override public void onCreate() { super.onCreate(); + AppService.validateConfig(this); // bugly,务必替换为你自己的!!! - CrashReport.initCrashReport(getApplicationContext(), "your bugly key", false); - // 只在主进程初始化 - if (getCurProcessName(this).equals("cn.wildfirechat.chat")) { - wfcUIKit = new WfcUIKit(); + if ("wildfirechat.net".equals(Config.IM_SERVER_HOST)) { +// CrashReport.initCrashReport(getApplicationContext(), BUGLY_ID, false); + } + // 只在主进程初始化,否则会导致重复收到消息 + if (getCurProcessName(this).equals(BuildConfig.APPLICATION_ID)) { + // 如果uikit是以aar的方式引入 ,那么需要在此对Config里面的属性进行配置,如: + // Config.IM_SERVER_HOST = "im.example.com"; + WfcUIKit wfcUIKit = WfcUIKit.getWfcUIKit(); wfcUIKit.init(this); - MessageViewHolderManager.getInstance().registerMessageViewHolder(LocationMessageContentViewHolder.class); + wfcUIKit.setEnableNativeNotification(true); + wfcUIKit.setAppServiceProvider(AppService.Instance()); + PushService.init(this, BuildConfig.APPLICATION_ID); + MessageViewHolderManager.getInstance().registerMessageViewHolder(LocationMessageContentViewHolder.class, R.layout.conversation_item_location_send, R.layout.conversation_item_location_send); + + String id = null; + String token = null; + try { + id = KeyStoreUtil.getData(this, "wf_userId"); + token = KeyStoreUtil.getData(this, "wf_token"); + if (TextUtils.isEmpty(id) || TextUtils.isEmpty(token)) { + SharedPreferences sp = getSharedPreferences(Config.SP_CONFIG_FILE_NAME, Context.MODE_PRIVATE); + id = sp.getString("id", null); + token = sp.getString("token", null); + if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(token)) { + KeyStoreUtil.saveData(this, "wf_userId", id); + KeyStoreUtil.saveData(this, "wf_token", token); + sp.edit().remove("id").remove("token").commit(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(token)) { + //需要注意token跟clientId是强依赖的,一定要调用getClientId获取到clientId,然后用这个clientId获取token,这样connect才能成功,如果随便使用一个clientId获取到的token将无法链接成功。 + //另外不能多次connect,如果需要切换用户请先disconnect,然后3秒钟之后再connect(如果是用户手动登录可以不用等,因为用户操作很难3秒完成,如果程序自动切换请等3秒) + ChatManagerHolder.gChatManager.connect(id, token); + } + + if (!TextUtils.isEmpty(Config.ORG_SERVER_ADDRESS)) { + OrganizationService organizationService = OrganizationService.Instance(); + wfcUIKit.setOrganizationServiceProvider(organizationService); + } + + ChatManager.Instance().setDefaultPortraitProviderClazz(WfcDefaultPortraitProvider.class); + ChatManager.Instance().setUrlRedirectorClazz(TestUrlRedirector.class); + ChatManager.Instance().addConnectToServerListener(this); + SharedPreferences sp = getSharedPreferences(Config.SP_CONFIG_FILE_NAME, Context.MODE_PRIVATE); + Config.ENABLE_AUDIO_MESSAGE_AMPLIFICATION = sp.getBoolean("audioMessageAmplificationEnabled", Config.ENABLE_AUDIO_MESSAGE_AMPLIFICATION); } } + @Override + protected void attachBaseContext(Context base) { + // 获取用户语言偏好 + String language = LocaleUtils.getLanguage(base); + // 应用用户语言设置 + Context context = LocaleUtils.updateResources(base, language); + // 调用父类方法 + super.attachBaseContext(context); + } + public static String getCurProcessName(Context context) { + if (Build.VERSION.SDK_INT >= 28) + return Application.getProcessName(); - int pid = android.os.Process.myPid(); + // Using the same technique as Application.getProcessName() for older devices + // Using reflection since ActivityThread is an internal API - ActivityManager activityManager = (ActivityManager) context - .getSystemService(Context.ACTIVITY_SERVICE); + try { + @SuppressLint("PrivateApi") + Class activityThread = Class.forName("android.app.ActivityThread"); - for (ActivityManager.RunningAppProcessInfo appProcess : activityManager - .getRunningAppProcesses()) { + // Before API 18, the method was incorrectly named "currentPackageName", but it still returned the process name + // See https://github.com/aosp-mirror/platform_frameworks_base/commit/b57a50bd16ce25db441da5c1b63d48721bb90687 + String methodName = Build.VERSION.SDK_INT >= 18 ? "currentProcessName" : "currentPackageName"; - if (appProcess.pid == pid) { - return appProcess.processName; - } + Method getProcessName = activityThread.getDeclaredMethod(methodName); + return (String) getProcessName.invoke(null); + } catch (Exception e) { + e.printStackTrace(); } return null; } + + + @Override + public void onConnectToServer(String host, String ip, int port) { + if (TextUtils.isEmpty(ip)) { + routeHost = host; + routePort = port; + } else { + longLinkHost = host; + } + } } diff --git a/chat/src/main/java/cn/wildfire/chat/app/OrganizationService.java b/chat/src/main/java/cn/wildfire/chat/app/OrganizationService.java new file mode 100644 index 0000000000000000000000000000000000000000..77a72e825b7bc2f5042638378e5e2077f03542ce --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/OrganizationService.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2023 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import cn.wildfire.chat.kit.Config; +import cn.wildfire.chat.kit.net.OKHttpHelper; +import cn.wildfire.chat.kit.net.SimpleCallback; +import cn.wildfire.chat.kit.organization.OrganizationServiceProvider; +import cn.wildfire.chat.kit.organization.model.Employee; +import cn.wildfire.chat.kit.organization.model.EmployeeEx; +import cn.wildfire.chat.kit.organization.model.Organization; +import cn.wildfire.chat.kit.organization.model.OrganizationEx; +import cn.wildfire.chat.kit.organization.model.OrganizationRelationship; +import cn.wildfirechat.remote.ChatManager; +import cn.wildfirechat.remote.GeneralCallback; +import cn.wildfirechat.remote.GeneralCallback2; + +public class OrganizationService implements OrganizationServiceProvider { + + private boolean isServiceAvailable; + + private static final OrganizationService Instance = new OrganizationService(); + + //组织通讯录服务地址,如果没有部署,可以设置为null + public static String ORG_SERVER_ADDRESS/*请仔细阅读上面的注释*/ = Config.ORG_SERVER_ADDRESS; + + private OrganizationService() { + + } + + public static OrganizationService Instance() { + return Instance; + } + + @Override + public void login(GeneralCallback callback) { +// int ApplicationType_Robot = 0; +// int ApplicationType_Channel = 1; +// int ApplicationType_Admin = 2; + if (isServiceAvailable) { + return; + } + ChatManager.Instance().getAuthCode("admin", 2, Config.IM_SERVER_HOST, new GeneralCallback2() { + @Override + public void onSuccess(String result) { + + Map params = new HashMap<>(1); + params.put("authCode", result); + String url = ORG_SERVER_ADDRESS + "/api/user_login"; + OKHttpHelper.post(url, params, new SimpleCallback() { + @Override + public void onUiSuccess(Void r) { + Instance.isServiceAvailable = true; + if (callback != null) { + callback.onSuccess(); + } + } + + @Override + public void onUiFailure(int code, String msg) { + if (callback != null) { + callback.onFail(code); + } + } + }); + } + + @Override + public void onFail(int errorCode) { + if (callback != null) { + callback.onFail(errorCode); + } + } + }); + + } + + public void clearOrgServiceAuthInfos() { + + } + + @Override + public boolean isServiceAvailable() { + return isServiceAvailable; + } + + @Override + public void getRelationship(String employeeId, SimpleCallback> callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + Map params = new HashMap<>(1); + params.put("employeeId", employeeId); + String url = ORG_SERVER_ADDRESS + "/api/relationship/employee"; + OKHttpHelper.post(url, params, callback); + + } + + @Override + public void getRootOrganization(SimpleCallback> callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + Map params = new HashMap<>(1); + String url = ORG_SERVER_ADDRESS + "/api/organization/root"; + OKHttpHelper.post(url, params, callback); + } + + @Override + public void getOrganizationEx(int orgId, SimpleCallback callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + Map params = new HashMap<>(1); + params.put("id", orgId); + String url = ORG_SERVER_ADDRESS + "/api/organization/query_ex"; + OKHttpHelper.post(url, params, callback); + } + + @Override + public void getOrganizations(List orgIds, SimpleCallback> callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + Map params = new HashMap<>(1); + params.put("ids", orgIds); + String url = ORG_SERVER_ADDRESS + "/api/organization/query_list"; + OKHttpHelper.post(url, params, callback); + } + + @Override + public void getOrganizationEmployees(List orgIds, SimpleCallback> callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + getOrgEmployees(orgIds, new SimpleCallback>() { + @Override + public void onUiSuccess(List employeeIds) { + if (employeeIds != null && !employeeIds.isEmpty()) { + getEmployees(employeeIds, callback); + } else { + callback.onUiSuccess(new ArrayList<>()); + } + } + + @Override + public void onUiFailure(int code, String msg) { + callback.onUiFailure(code, msg); + } + }); + } + + + @Override + public void getOrgEmployees(List orgIds, SimpleCallback> callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + Map params = new HashMap<>(1); + params.put("ids", orgIds); + String url = ORG_SERVER_ADDRESS + "/api/organization/batch_employees"; + OKHttpHelper.post(url, params, callback); + } + + @Override + public void getOrgEmployees(int orgId, SimpleCallback> callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + Map params = new HashMap<>(1); + params.put("id", orgId); + String url = ORG_SERVER_ADDRESS + "/api/organization/employees"; + OKHttpHelper.post(url, params, callback); + } + + @Override + public void getEmployee(String employeeId, SimpleCallback callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + Map params = new HashMap<>(1); + params.put("employeeId", employeeId); + String url = ORG_SERVER_ADDRESS + "/api/employee/query"; + OKHttpHelper.post(url, params, callback); + } + + @Override + public void getEmployees(List employeeIds, SimpleCallback> callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + Map params = new HashMap<>(1); + params.put("employeeIds", employeeIds); + String url = ORG_SERVER_ADDRESS + "/api/employee/query_list"; + OKHttpHelper.post(url, params, callback); + } + + @Override + public void getEmployeeEx(String employeeId, SimpleCallback callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + Map params = new HashMap<>(1); + params.put("employeeId", employeeId); + String url = ORG_SERVER_ADDRESS + "/api/employee/query_ex"; + OKHttpHelper.post(url, params, callback); + } + + @Override + public void searchEmployee(int orgId, String keyword, SimpleCallback> callback) { + if (!isServiceAvailable) { + callback.onUiFailure(-1, "未登录,或服务不可用"); + return; + } + Map params = new HashMap<>(1); + params.put("organizationId", orgId); + params.put("keyword", keyword); + String url = ORG_SERVER_ADDRESS + "/api/employee/search"; + OKHttpHelper.post(url, params, callback); + } + + private static final String TAG = "OrgService"; + +} diff --git a/chat/src/main/java/cn/wildfire/chat/app/TestUrlRedirector.java b/chat/src/main/java/cn/wildfire/chat/app/TestUrlRedirector.java new file mode 100644 index 0000000000000000000000000000000000000000..418f82636978d4d9138cfc19978fa050eb76f884 --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/TestUrlRedirector.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app; + +import cn.wildfirechat.remote.UrlRedirector; + +/** + * 网络地址转换参考实现 + */ +public class TestUrlRedirector implements UrlRedirector { + /** + * 双网时,需要根据网络类型进行地址转换 + * + * @param originalUrl 原始 url + * @return 根据网络环境转换之后的 url + */ + @Override + public String urlRedirect(String originalUrl) { + // 未部署双网环境,故直接返回 + return originalUrl; + + // 双网环境时,请参考下面的参考实现 +// if (ChatManager.Instance().isConnectedToMainNetwork()) { +// // 当前连接的是主网络 +// originalUrl = originalUrl.replace("192.168.2.19", "oss.xxxx.com"); +// } else { +// // 当前连接的是备选网络 +// originalUrl = originalUrl.replace("oss.xxxx.com", "192.168.2.19"); +// } +// return originalUrl; + } +} diff --git a/chat/src/main/java/cn/wildfire/chat/app/WfcDefaultPortraitProvider.java b/chat/src/main/java/cn/wildfire/chat/app/WfcDefaultPortraitProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..33f252ea359d0767e3104da403a755eb091d58bc --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/WfcDefaultPortraitProvider.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app; + +import android.net.Uri; +import android.text.TextUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import cn.wildfirechat.model.GroupInfo; +import cn.wildfirechat.model.NullGroupInfo; +import cn.wildfirechat.model.NullUserInfo; +import cn.wildfirechat.model.UserInfo; +import cn.wildfirechat.remote.DefaultPortraitProvider; + +public class WfcDefaultPortraitProvider implements DefaultPortraitProvider { + private final Map groupPortraitMap = new HashMap<>(); + + @Override + public String userDefaultPortrait(UserInfo userInfo) { + if (!TextUtils.isEmpty(userInfo.portrait)) { + return userInfo.portrait; + } else { + return AppService.APP_SERVER_ADDRESS + "/avatar?name=" + Uri.encode(userInfo.displayName); + } + } + + @Override + public String groupDefaultPortrait(GroupInfo groupInfo, List userInfos) { + if (groupInfo instanceof NullGroupInfo || !TextUtils.isEmpty(groupInfo.portrait) || userInfos == null || userInfos.isEmpty()) { + return groupInfo.portrait; + } + String portrait = groupPortraitMap.get(groupInfo.target); + if (portrait != null) { + return portrait; + } + + boolean pending = false; + JSONObject request = new JSONObject(); + try { + JSONArray reqMembers = new JSONArray(); + if (userInfos.size() > 9) { + userInfos = userInfos.subList(0, 9); + } + for (UserInfo userInfo : userInfos) { + if (userInfo instanceof NullUserInfo) { + pending = true; + } + JSONObject obj = new JSONObject(); + if (TextUtils.isEmpty(userInfo.portrait) || userInfo.portrait.startsWith(AppService.APP_SERVER_ADDRESS)) { + obj.put("name", userInfo.displayName); + } else { + obj.put("avatarUrl", userInfo.portrait); + } + reqMembers.put(obj); + } + request.put("members", reqMembers); + } catch (JSONException e) { + e.printStackTrace(); + } + if (pending) { + return null; + } + portrait = AppService.APP_SERVER_ADDRESS + "/avatar/group?request=" + Uri.encode(request.toString()); + groupPortraitMap.put(groupInfo.target, portrait); + return portrait; + } +} diff --git a/chat/src/main/java/cn/wildfire/chat/app/login/LoginActivity.java b/chat/src/main/java/cn/wildfire/chat/app/login/LoginActivity.java index e4fb616f1964ef9d36185e3acdfc34e56683f442..763047751622c00cacc70c3383b0f598b6edce9d 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/login/LoginActivity.java +++ b/chat/src/main/java/cn/wildfire/chat/app/login/LoginActivity.java @@ -1,54 +1,101 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app.login; -import android.content.Context; +import static cn.wildfire.chat.app.BaseApp.getContext; + import android.content.Intent; -import android.content.SharedPreferences; +import android.os.Bundle; import android.text.Editable; import android.text.TextUtils; import android.widget.Button; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.Toast; -import com.afollestad.materialdialogs.MaterialDialog; +import androidx.core.app.ActivityOptionsCompat; -import java.util.HashMap; -import java.util.Map; +import com.afollestad.materialdialogs.MaterialDialog; -import butterknife.Bind; -import butterknife.OnClick; -import butterknife.OnTextChanged; -import cn.wildfire.chat.app.Config; +import cn.wildfire.chat.app.AppService; import cn.wildfire.chat.app.login.model.LoginResult; import cn.wildfire.chat.app.main.MainActivity; +import cn.wildfire.chat.app.misc.KeyStoreUtil; +import cn.wildfire.chat.app.setting.ResetPasswordActivity; import cn.wildfire.chat.kit.ChatManagerHolder; -import cn.wildfire.chat.kit.WfcBaseActivity; -import cn.wildfire.chat.kit.net.OKHttpHelper; -import cn.wildfire.chat.kit.net.SimpleCallback; +import cn.wildfire.chat.kit.Config; +import cn.wildfire.chat.kit.WfcBaseNoToolbarActivity; +import cn.wildfire.chat.kit.WfcWebViewActivity; +import cn.wildfire.chat.kit.widget.SimpleTextWatcher; import cn.wildfirechat.chat.R; -/** - * use {@link SMSLoginActivity} instead - */ -@Deprecated -public class LoginActivity extends WfcBaseActivity { - @Bind(R.id.loginButton) +public class LoginActivity extends WfcBaseNoToolbarActivity { Button loginButton; - @Bind(R.id.accountEditText) EditText accountEditText; - @Bind(R.id.passwordEditText) EditText passwordEditText; + CheckBox checkBox; + + private void bindEvents() { + findViewById(R.id.authCodeLoginTextView).setOnClickListener(v -> authCodeLogin()); + findViewById(R.id.registerTextView).setOnClickListener(v -> register()); + findViewById(R.id.loginButton).setOnClickListener(v -> { + if (checkBox.isChecked()) { + login(); + } else { + Toast.makeText(this, R.string.check_agreement_tip, Toast.LENGTH_SHORT).show(); + } + }); + accountEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + inputAccount(s); + } + }); + passwordEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + inputPassword(s); + } + }); + + findViewById(R.id.privacyAgreementTextView).setOnClickListener(v -> { + WfcWebViewActivity.loadUrl(this, getString(R.string.privacy_agreement), Config.PRIVACY_AGREEMENT_URL); + + }); + findViewById(R.id.userAgreementTextView).setOnClickListener(v -> { + WfcWebViewActivity.loadUrl(this, getString(R.string.user_agreement), Config.USER_AGREEMENT_URL); + }); + } + + private void bindViews() { + loginButton = findViewById(R.id.loginButton); + accountEditText = findViewById(R.id.phoneNumberEditText); + passwordEditText = findViewById(R.id.passwordEditText); + checkBox = findViewById(R.id.agreementCheckBox); + } @Override protected int contentLayout() { - return R.layout.login_activity_account; + return R.layout.login_activity_password; } @Override - protected boolean showHomeMenuItem() { - return false; + protected void afterViews() { + bindViews(); + bindEvents(); + setStatusBarTheme(this, false); + setStatusBarColor(R.color.gray14); + if (getIntent().getBooleanExtra("isKickedOff", false)) { + new MaterialDialog.Builder(this) + .content(R.string.kicked_off_message) + .negativeText(R.string.kicked_off_confirm) + .build() + .show(); + } } - @OnTextChanged(value = R.id.accountEditText, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) void inputAccount(Editable editable) { if (!TextUtils.isEmpty(passwordEditText.getText()) && !TextUtils.isEmpty(editable)) { loginButton.setEnabled(true); @@ -57,7 +104,6 @@ public class LoginActivity extends WfcBaseActivity { } } - @OnTextChanged(value = R.id.passwordEditText, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) void inputPassword(Editable editable) { if (!TextUtils.isEmpty(accountEditText.getText()) && !TextUtils.isEmpty(editable)) { loginButton.setEnabled(true); @@ -66,46 +112,69 @@ public class LoginActivity extends WfcBaseActivity { } } + void authCodeLogin() { + Intent intent = new Intent(this, SMSLoginActivity.class); + startActivity(intent); + finish(); + } + + void register() { + MaterialDialog dialog = new MaterialDialog.Builder(this) + .title(R.string.register_tip_title) + .content(R.string.register_tip_message) + .cancelable(true) + .positiveText(R.string.confirm) + .negativeText(R.string.cancel) + .onPositive((dialog1, which) -> { + Intent intent = new Intent(LoginActivity.this, SMSLoginActivity.class); + Bundle bundle = ActivityOptionsCompat.makeCustomAnimation(getContext(), + android.R.anim.fade_in, android.R.anim.fade_out).toBundle(); + startActivity(intent, bundle); + finish(); + }) + .build(); + dialog.show(); + } - @OnClick(R.id.loginButton) void login() { + String account = accountEditText.getText().toString().trim(); String password = passwordEditText.getText().toString().trim(); - String url = "http://" + Config.APP_SERVER_HOST + ":" + Config.APP_SERVER_PORT + "/api/login"; - Map params = new HashMap<>(); - params.put("name", account); - params.put("password", password); - try { - params.put("clientId", ChatManagerHolder.gChatManager.getClientId()); - } catch (Exception e) { - e.printStackTrace(); - Toast.makeText(LoginActivity.this, "网络出来问题了。。。", Toast.LENGTH_SHORT).show(); - return; - } - MaterialDialog dialog = new MaterialDialog.Builder(this) - .content("登录中...") - .progress(true, 10) - .cancelable(false) - .build(); + .content(R.string.login_progress) + .progress(true, 10) + .cancelable(false) + .build(); dialog.show(); - OKHttpHelper.post(url, params, new SimpleCallback() { + + AppService.Instance().passwordLogin(account, password, new AppService.LoginCallback() { @Override public void onUiSuccess(LoginResult loginResult) { if (isFinishing()) { return; } + + dialog.dismiss(); + //需要注意token跟clientId是强依赖的,一定要调用getClientId获取到clientId,然后用这个clientId获取token,这样connect才能成功,如果随便使用一个clientId获取到的token将无法链接成功。 ChatManagerHolder.gChatManager.connect(loginResult.getUserId(), loginResult.getToken()); - SharedPreferences sp = getSharedPreferences("config", Context.MODE_PRIVATE); - sp.edit() - .putString("id", loginResult.getUserId()) - .putString("token", loginResult.getToken()) - .apply(); + try { + KeyStoreUtil.saveData(LoginActivity.this, "wf_userId", loginResult.getUserId()); + KeyStoreUtil.saveData(LoginActivity.this, "wf_token", loginResult.getToken()); + } catch (Exception e) { + e.printStackTrace(); + } + Intent intent = new Intent(LoginActivity.this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); - dialog.dismiss(); + + // 初始密码(需要 app-server 开启,开启后,默认是手机号后 6 位)登录后,重置密码 + if (!TextUtils.isEmpty(loginResult.getResetCode())) { + Intent resetPasswordIntent = new Intent(LoginActivity.this, ResetPasswordActivity.class); + resetPasswordIntent.putExtra("resetCode", loginResult.getResetCode()); + startActivity(resetPasswordIntent); + } finish(); } @@ -115,7 +184,10 @@ public class LoginActivity extends WfcBaseActivity { return; } dialog.dismiss(); + + Toast.makeText(LoginActivity.this, getString(R.string.login_error_hint, code, msg), Toast.LENGTH_SHORT).show(); } }); + } } diff --git a/chat/src/main/java/cn/wildfire/chat/app/login/SMSLoginActivity.java b/chat/src/main/java/cn/wildfire/chat/app/login/SMSLoginActivity.java index 3be7294961a15f07fc8e9cf2c9452a8c3b9a8f9c..2a2613262b548ca03798d47f55779b513b9c595e 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/login/SMSLoginActivity.java +++ b/chat/src/main/java/cn/wildfire/chat/app/login/SMSLoginActivity.java @@ -1,53 +1,99 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app.login; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.os.Handler; import android.text.Editable; import android.widget.Button; +import android.widget.CheckBox; import android.widget.EditText; +import android.widget.TextView; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; -import java.util.HashMap; -import java.util.Map; - -import butterknife.Bind; -import butterknife.OnClick; -import butterknife.OnTextChanged; -import cn.wildfire.chat.app.Config; +import cn.wildfire.chat.app.AppService; import cn.wildfire.chat.app.login.model.LoginResult; import cn.wildfire.chat.app.main.MainActivity; +import cn.wildfire.chat.app.misc.KeyStoreUtil; +import cn.wildfire.chat.app.setting.ResetPasswordActivity; import cn.wildfire.chat.kit.ChatManagerHolder; -import cn.wildfire.chat.kit.WfcBaseActivity; -import cn.wildfire.chat.kit.net.OKHttpHelper; -import cn.wildfire.chat.kit.net.SimpleCallback; -import cn.wildfire.chat.kit.net.base.StatusResult; +import cn.wildfire.chat.kit.Config; +import cn.wildfire.chat.kit.WfcBaseNoToolbarActivity; +import cn.wildfire.chat.kit.WfcWebViewActivity; +import cn.wildfire.chat.kit.widget.SimpleTextWatcher; import cn.wildfirechat.chat.R; -public class SMSLoginActivity extends WfcBaseActivity { - @Bind(R.id.loginButton) +public class SMSLoginActivity extends WfcBaseNoToolbarActivity { + + private static final String TAG = "SMSLoginActivity"; + Button loginButton; - @Bind(R.id.phoneNumberEditText) EditText phoneNumberEditText; - @Bind(R.id.authCodeEditText) EditText authCodeEditText; - @Bind(R.id.requestAuthCodeButton) - Button requestAuthCodeButton; + TextView requestAuthCodeButton; + CheckBox checkBox; + + private void bindEvents() { + findViewById(R.id.passwordLoginTextView).setOnClickListener(v -> authCodeLogin()); + findViewById(R.id.loginButton).setOnClickListener(v -> { + if (checkBox.isChecked()) { + login(); + } else { + Toast.makeText(this, R.string.check_agreement_tip, Toast.LENGTH_SHORT).show(); + } + }); + findViewById(R.id.requestAuthCodeButton).setOnClickListener(v -> requestAuthCode()); + phoneNumberEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + inputPhoneNumber(s); + } + }); + + authCodeEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + inputAuthCode(s); + } + }); - private String phoneNumber; + findViewById(R.id.privacyAgreementTextView).setOnClickListener(v -> { + WfcWebViewActivity.loadUrl(this, getString(R.string.privacy_agreement), Config.PRIVACY_AGREEMENT_URL); + + }); + findViewById(R.id.userAgreementTextView).setOnClickListener(v -> { + WfcWebViewActivity.loadUrl(this, getString(R.string.user_agreement), Config.USER_AGREEMENT_URL); + }); + } + + private void bindViews() { + loginButton = findViewById(R.id.loginButton); + phoneNumberEditText = findViewById(R.id.phoneNumberEditText); + authCodeEditText = findViewById(R.id.authCodeEditText); + requestAuthCodeButton = findViewById(R.id.requestAuthCodeButton); + checkBox = findViewById(R.id.agreementCheckBox); + } @Override protected int contentLayout() { return R.layout.login_activity_sms; } - @OnTextChanged(value = R.id.phoneNumberEditText, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + @Override + protected void afterViews() { + bindViews(); + bindEvents(); + setStatusBarTheme(this, false); + setStatusBarColor(R.color.gray14); + } + void inputPhoneNumber(Editable editable) { String phone = editable.toString().trim(); - if (phone.length() == 11) { + if (phone.length() == 11 && countdownRunnable == null) { requestAuthCodeButton.setEnabled(true); } else { requestAuthCodeButton.setEnabled(false); @@ -55,57 +101,54 @@ public class SMSLoginActivity extends WfcBaseActivity { } } - @OnTextChanged(value = R.id.authCodeEditText, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) void inputAuthCode(Editable editable) { if (editable.toString().length() > 2) { loginButton.setEnabled(true); } } - @Override - protected boolean showHomeMenuItem() { - return false; + void authCodeLogin() { + Intent intent = new Intent(this, LoginActivity.class); + startActivity(intent); + finish(); } - @OnClick(R.id.loginButton) + void login() { String phoneNumber = phoneNumberEditText.getText().toString().trim(); String authCode = authCodeEditText.getText().toString().trim(); - String url = "http://" + Config.APP_SERVER_HOST + ":" + Config.APP_SERVER_PORT + "/login"; - Map params = new HashMap<>(); - params.put("mobile", phoneNumber); - params.put("code", authCode); - try { - params.put("clientId", ChatManagerHolder.gChatManager.getClientId()); - } catch (Exception e) { - e.printStackTrace(); - Toast.makeText(SMSLoginActivity.this, "网络出来问题了。。。", Toast.LENGTH_SHORT).show(); - return; - } - + loginButton.setEnabled(false); MaterialDialog dialog = new MaterialDialog.Builder(this) - .content("登录中...") - .progress(true, 100) - .cancelable(false) - .build(); + .content(R.string.login_progress) + .progress(true, 100) + .cancelable(false) + .build(); dialog.show(); - OKHttpHelper.post(url, params, new SimpleCallback() { + + AppService.Instance().smsLogin(phoneNumber, authCode, new AppService.LoginCallback() { @Override public void onUiSuccess(LoginResult loginResult) { if (isFinishing()) { return; } dialog.dismiss(); + //需要注意token跟clientId是强依赖的,一定要调用getClientId获取到clientId,然后用这个clientId获取token,这样connect才能成功,如果随便使用一个clientId获取到的token将无法链接成功。 ChatManagerHolder.gChatManager.connect(loginResult.getUserId(), loginResult.getToken()); - SharedPreferences sp = getSharedPreferences("config", Context.MODE_PRIVATE); - sp.edit() - .putString("id", loginResult.getUserId()) - .putString("token", loginResult.getToken()) - .apply(); + try { + KeyStoreUtil.saveData(SMSLoginActivity.this, "wf_userId", loginResult.getUserId()); + KeyStoreUtil.saveData(SMSLoginActivity.this, "wf_token", loginResult.getToken()); + } catch (Exception e) { + e.printStackTrace(); + } Intent intent = new Intent(SMSLoginActivity.this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); + + Intent resetPasswordIntent = new Intent(SMSLoginActivity.this, ResetPasswordActivity.class); + resetPasswordIntent.putExtra("resetCode", loginResult.getResetCode()); + startActivity(resetPasswordIntent); + finish(); } @@ -114,46 +157,93 @@ public class SMSLoginActivity extends WfcBaseActivity { if (isFinishing()) { return; } - Toast.makeText(SMSLoginActivity.this, "登录失败:" + code + " " + msg, Toast.LENGTH_SHORT).show(); + Toast.makeText(SMSLoginActivity.this, getString(R.string.sms_login_failure, code, msg), Toast.LENGTH_SHORT).show(); dialog.dismiss(); + loginButton.setEnabled(true); } }); } private Handler handler = new Handler(); + private int countdownSeconds = 60; + private Runnable countdownRunnable; - @OnClick(R.id.requestAuthCodeButton) void requestAuthCode() { + String phoneNumber = phoneNumberEditText.getText().toString().trim(); + + // Disable button immediately requestAuthCodeButton.setEnabled(false); - handler.postDelayed(new Runnable() { - @Override - public void run() { - if (!isFinishing()) { - requestAuthCodeButton.setEnabled(true); + + // Start countdown + countdownSeconds = 60; + updateCountdownText(); + + // Create countdown runnable if not exists + if (countdownRunnable == null) { + countdownRunnable = new Runnable() { + @Override + public void run() { + if (isFinishing()) return; + + countdownSeconds--; + updateCountdownText(); + + if (countdownSeconds > 0) { + // Continue countdown + handler.postDelayed(this, 1000); + } else { + // Reset button text and enable it + requestAuthCodeButton.setText(getString(R.string.requesting_auth_code)); + requestAuthCodeButton.setEnabled(true); + } } - } - }, 60 * 1000); + }; + } - Toast.makeText(this, "请求验证码...", Toast.LENGTH_SHORT).show(); - String phoneNumber = phoneNumberEditText.getText().toString().trim(); - String url = "http://" + Config.APP_SERVER_HOST + ":" + Config.APP_SERVER_PORT + "/send_code"; - Map params = new HashMap<>(); - params.put("mobile", phoneNumber); - OKHttpHelper.post(url, params, new SimpleCallback() { + // Start the countdown timer + handler.postDelayed(countdownRunnable, 1000); + + // Request the auth code + Toast.makeText(this, getString(R.string.requesting_auth_code), Toast.LENGTH_SHORT).show(); + + AppService.Instance().requestAuthCode(phoneNumber, new AppService.SendCodeCallback() { @Override - public void onUiSuccess(StatusResult statusResult) { - if (statusResult.getCode() == 0) { - Toast.makeText(SMSLoginActivity.this, "发送验证码成功", Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(SMSLoginActivity.this, "发送验证码失败: " + statusResult.getCode(), Toast.LENGTH_SHORT).show(); - } + public void onUiSuccess() { + Toast.makeText(SMSLoginActivity.this, R.string.auth_code_request_success, Toast.LENGTH_SHORT).show(); } @Override public void onUiFailure(int code, String msg) { - Toast.makeText(SMSLoginActivity.this, "发送验证码失败: " + msg, Toast.LENGTH_SHORT).show(); + Toast.makeText(SMSLoginActivity.this, getString(R.string.auth_code_request_failure, code, msg), Toast.LENGTH_SHORT).show(); + // Reset countdown on failure + resetCountdown(); } }); + } + + private void updateCountdownText() { + if (countdownSeconds > 0) { + requestAuthCodeButton.setText(getString(R.string.retry_after_seconds, countdownSeconds)); + } + } + + private void resetCountdown() { + // Remove callbacks + if (countdownRunnable != null) { + handler.removeCallbacks(countdownRunnable); + } + // Reset button + requestAuthCodeButton.setText(R.string.requesting_auth_code); + requestAuthCodeButton.setEnabled(true); + countdownSeconds = 60; + countdownRunnable = null; + } + @Override + protected void onDestroy() { + super.onDestroy(); + if (handler != null && countdownRunnable != null) { + handler.removeCallbacks(countdownRunnable); + } } } diff --git a/chat/src/main/java/cn/wildfire/chat/app/login/model/LoginResult.java b/chat/src/main/java/cn/wildfire/chat/app/login/model/LoginResult.java index 412e1668907b6af7537b806129ec5e586cb9e50e..9f7c13054337a220fba105a2257606780eddf014 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/login/model/LoginResult.java +++ b/chat/src/main/java/cn/wildfire/chat/app/login/model/LoginResult.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app.login.model; @@ -7,6 +11,8 @@ package cn.wildfire.chat.app.login.model; public class LoginResult { private String userId; private String token; + private boolean register; + private String resetCode; public String getUserId() { return userId; @@ -23,4 +29,20 @@ public class LoginResult { public void setToken(String token) { this.token = token; } + + public boolean isRegister() { + return register; + } + + public void setRegister(boolean register) { + this.register = register; + } + + public String getResetCode() { + return resetCode; + } + + public void setResetCode(String resetCode) { + this.resetCode = resetCode; + } } diff --git a/chat/src/main/java/cn/wildfire/chat/app/login/model/PCSession.java b/chat/src/main/java/cn/wildfire/chat/app/login/model/PCSession.java index 170a64dd92da6232526d38a5020a9cbacb84bde9..437a14ab5201da98d00d258f8723a2a65dc1e887 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/login/model/PCSession.java +++ b/chat/src/main/java/cn/wildfire/chat/app/login/model/PCSession.java @@ -1,9 +1,15 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app.login.model; public class PCSession { private String token; private int status; private long expired; + private int platform; + private String device_name; public String getToken() { return token; @@ -28,4 +34,20 @@ public class PCSession { public void setExpired(long expired) { this.expired = expired; } + + public int getPlatform() { + return platform; + } + + public void setPlatform(int platform) { + this.platform = platform; + } + + public String getDevice_name() { + return device_name; + } + + public void setDevice_name(String device_name) { + this.device_name = device_name; + } } diff --git a/chat/src/main/java/cn/wildfire/chat/app/main/AgreementActivity.java b/chat/src/main/java/cn/wildfire/chat/app/main/AgreementActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..0368aa5640ff83c76c0103c356843d8a362c5db5 --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/main/AgreementActivity.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app.main; + +import static cn.wildfire.chat.app.BaseApp.getContext; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; + +import androidx.core.app.ActivityOptionsCompat; + +import cn.wildfire.chat.app.login.LoginActivity; +import cn.wildfire.chat.kit.Config; +import cn.wildfire.chat.kit.WfcBaseNoToolbarActivity; +import cn.wildfire.chat.kit.WfcWebViewActivity; +import cn.wildfirechat.chat.R; + +public class AgreementActivity extends WfcBaseNoToolbarActivity { + + @Override + protected int contentLayout() { + return R.layout.agreement_activity; + } + + @Override + protected void afterViews() { + super.afterViews(); + setStatusBarTheme(this, false); + setStatusBarColor(R.color.gray14); + + SharedPreferences sp = getSharedPreferences(Config.SP_CONFIG_FILE_NAME, Context.MODE_PRIVATE); + sp.edit().putBoolean("hasReadUserAgreement", true).apply(); + + bindEvents(); + } + + private void bindEvents() { + findViewById(R.id.agreeTextView).setOnClickListener(v -> { + showLogin(); + }); + findViewById(R.id.disagreeTextView).setOnClickListener(v -> { + System.exit(0); + }); + + findViewById(R.id.privacyAgreementTextView).setOnClickListener(v -> { + WfcWebViewActivity.loadUrl(this, getString(R.string.privacy_agreement), Config.PRIVACY_AGREEMENT_URL); + + }); + findViewById(R.id.userAgreementTextView).setOnClickListener(v -> { + WfcWebViewActivity.loadUrl(this, getString(R.string.user_agreement), Config.USER_AGREEMENT_URL); + }); + + } + + private void showLogin() { + Intent intent; + intent = new Intent(this, LoginActivity.class); + intent.putExtra("isKickedOff", getIntent().getBooleanExtra("isKickedOff", false)); + Bundle bundle = ActivityOptionsCompat.makeCustomAnimation(getContext(), + android.R.anim.fade_in, android.R.anim.fade_out).toBundle(); + startActivity(intent, bundle); + finish(); + } +} diff --git a/chat/src/main/java/cn/wildfire/chat/app/main/DiscoveryFragment.java b/chat/src/main/java/cn/wildfire/chat/app/main/DiscoveryFragment.java index b3bf348336d01e477eb682dd3e3147f2a1076f9f..d9f499ee24efbeb60309d73ce2aab4e8612c8f89 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/main/DiscoveryFragment.java +++ b/chat/src/main/java/cn/wildfire/chat/app/main/DiscoveryFragment.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app.main; import android.content.Intent; @@ -9,24 +13,112 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import butterknife.ButterKnife; -import butterknife.OnClick; +import androidx.lifecycle.ViewModelProvider; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import cn.wildfire.chat.kit.WfcIntent; +import cn.wildfire.chat.kit.WfcUIKit; +import cn.wildfire.chat.kit.WfcWebViewActivity; +import cn.wildfire.chat.kit.channel.ChannelListActivity; import cn.wildfire.chat.kit.chatroom.ChatRoomListActivity; +import cn.wildfire.chat.kit.conversation.ConversationActivity; +import cn.wildfire.chat.kit.viewmodel.MessageViewModel; +import cn.wildfire.chat.kit.voip.conference.ConferencePortalActivity; +import cn.wildfire.chat.kit.widget.OptionItemView; +import cn.wildfirechat.avenginekit.AVEngineKit; import cn.wildfirechat.chat.R; +import cn.wildfirechat.message.Message; +import cn.wildfirechat.message.core.MessageStatus; +import cn.wildfirechat.model.Conversation; +import cn.wildfirechat.remote.ChatManager; public class DiscoveryFragment extends Fragment { + OptionItemView momentOptionItemView; + OptionItemView conferenceOptionItemView; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.main_fragment_discovery, container, false); - ButterKnife.bind(this, view); + bindViews(view); + bindEvents(view); + initMoment(); + if (!AVEngineKit.isSupportConference()) { + conferenceOptionItemView.setVisibility(View.GONE); + } return view; } - @OnClick(R.id.chatRoomOptionItemView) + private void bindEvents(View view) { + view.findViewById(R.id.chatRoomOptionItemView).setOnClickListener(v -> chatRoom()); + view.findViewById(R.id.robotOptionItemView).setOnClickListener(v -> robot()); + view.findViewById(R.id.channelOptionItemView).setOnClickListener(v -> channel()); + view.findViewById(R.id.cookbookOptionItemView).setOnClickListener(v -> cookbook()); + view.findViewById(R.id.momentOptionItemView).setOnClickListener(v -> moment()); + view.findViewById(R.id.conferenceOptionItemView).setOnClickListener(v -> conference()); + } + + private void bindViews(View view) { + momentOptionItemView = view.findViewById(R.id.momentOptionItemView); + conferenceOptionItemView = view.findViewById(R.id.conferenceOptionItemView); + } + + private void updateMomentBadgeView() { + List messages = ChatManager.Instance().getMessagesEx2(Collections.singletonList(Conversation.ConversationType.Single), Collections.singletonList(1), Arrays.asList(MessageStatus.Unread), 0, true, 100, null); + int count = messages == null ? 0 : messages.size(); + momentOptionItemView.setBadgeCount(count); + } + + @Override + public void onResume() { + super.onResume(); + if (WfcUIKit.getWfcUIKit().isSupportMoment()) { + updateMomentBadgeView(); + } + } + void chatRoom() { Intent intent = new Intent(getActivity(), ChatRoomListActivity.class); startActivity(intent); } + + void robot() { + Intent intent = ConversationActivity.buildConversationIntent(getActivity(), Conversation.ConversationType.Single, "FireRobot", 0); + startActivity(intent); + } + + void channel() { + Intent intent = new Intent(getActivity(), ChannelListActivity.class); + startActivity(intent); + } + + void cookbook() { + WfcWebViewActivity.loadUrl(getContext(), getString(R.string.wfc_doc_title), getString(R.string.wfc_doc_url)); + } + + private void initMoment() { + if (!WfcUIKit.getWfcUIKit().isSupportMoment()) { + momentOptionItemView.setVisibility(View.GONE); + return; + } + MessageViewModel messageViewModel =new ViewModelProvider(this).get(MessageViewModel.class); + messageViewModel.messageLiveData().observe(getViewLifecycleOwner(), uiMessage -> updateMomentBadgeView()); + messageViewModel.clearMessageLiveData().observe(getViewLifecycleOwner(), o -> updateMomentBadgeView()); + } + + void moment() { + Intent intent = new Intent(WfcIntent.ACTION_MOMENT); + // 具体项目中,如果不能隐式启动,可改为下面这种显示启动朋友圈页面 +// Intent intent = new Intent(getActivity(), FeedListActivity.class); + startActivity(intent); + } + + void conference() { + Intent intent = new Intent(getActivity(), ConferencePortalActivity.class); + startActivity(intent); + } + } diff --git a/chat/src/main/java/cn/wildfire/chat/app/main/HomeFragmentPagerAdapter.java b/chat/src/main/java/cn/wildfire/chat/app/main/HomeFragmentPagerAdapter.java index 0c21193ee261503bc0341b0ad2969645a686c539..24349d9e6b046a0b08cc48838e87c965d3154b23 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/main/HomeFragmentPagerAdapter.java +++ b/chat/src/main/java/cn/wildfire/chat/app/main/HomeFragmentPagerAdapter.java @@ -1,28 +1,33 @@ -package cn.wildfire.chat.app.main; +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ -import java.util.List; +package cn.wildfire.chat.app.main; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; -public class HomeFragmentPagerAdapter extends FragmentPagerAdapter { +import java.util.List; + +public class HomeFragmentPagerAdapter extends FragmentStateAdapter { private List mFragments; - public HomeFragmentPagerAdapter(FragmentManager fm, List fragments) { + public HomeFragmentPagerAdapter(FragmentActivity fm, List fragments) { super(fm); mFragments = fragments; } + @NonNull @Override - public Fragment getItem(int position) { + public Fragment createFragment(int position) { return mFragments.get(position); } @Override - public int getCount() { + public int getItemCount() { return mFragments != null ? mFragments.size() : 0; } - } diff --git a/chat/src/main/java/cn/wildfire/chat/app/main/MainActivity.java b/chat/src/main/java/cn/wildfire/chat/app/main/MainActivity.java index 0c890c9e06d53fa2cef50c1a3bbd2a807d8e31c7..acb09e9859ca98b0c00aa12a9b5b2b9dcea1da6a 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/main/MainActivity.java +++ b/chat/src/main/java/cn/wildfire/chat/app/main/MainActivity.java @@ -1,126 +1,337 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app.main; +import android.Manifest; +import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.net.Uri; -import android.os.Build; -import android.os.PowerManager; -import android.provider.Settings; import android.text.TextUtils; +import android.util.Pair; +import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + import androidx.viewpager.widget.ViewPager; +import androidx.viewpager2.widget.ViewPager2; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.google.android.material.bottomnavigation.BottomNavigationMenuView; import com.google.android.material.bottomnavigation.BottomNavigationView; -import com.king.zxing.CaptureActivity; import com.king.zxing.Intents; +import cn.wildfirechat.uikit.permission.PermissionKit; +import cn.wildfirechat.uikit.menu.PopupMenu; + import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; -import butterknife.Bind; +import cn.wildfire.chat.kit.Config; +import cn.wildfire.chat.kit.IMConnectionStatusViewModel; +import cn.wildfire.chat.kit.IMServiceStatusViewModel; import cn.wildfire.chat.kit.WfcBaseActivity; import cn.wildfire.chat.kit.WfcScheme; import cn.wildfire.chat.kit.WfcUIKit; import cn.wildfire.chat.kit.channel.ChannelInfoActivity; -import cn.wildfire.chat.kit.contact.ContactFragment; +import cn.wildfire.chat.kit.contact.ContactListActivity; +import cn.wildfire.chat.kit.contact.ContactListFragment; import cn.wildfire.chat.kit.contact.ContactViewModel; import cn.wildfire.chat.kit.contact.newfriend.SearchUserActivity; +import cn.wildfire.chat.kit.conversation.ConversationActivity; +import cn.wildfire.chat.kit.conversation.ConversationViewModel; import cn.wildfire.chat.kit.conversation.CreateConversationActivity; +import cn.wildfire.chat.kit.conversation.forward.ForwardActivity; +import cn.wildfire.chat.kit.conversation.message.model.UiMessage; import cn.wildfire.chat.kit.conversationlist.ConversationListFragment; import cn.wildfire.chat.kit.conversationlist.ConversationListViewModel; import cn.wildfire.chat.kit.conversationlist.ConversationListViewModelFactory; import cn.wildfire.chat.kit.group.GroupInfoActivity; +import cn.wildfire.chat.kit.net.OKHttpHelper; +import cn.wildfire.chat.kit.qrcode.ScanQRCodeActivity; import cn.wildfire.chat.kit.search.SearchPortalActivity; -import cn.wildfire.chat.kit.third.utils.UIUtils; import cn.wildfire.chat.kit.user.ChangeMyNameActivity; import cn.wildfire.chat.kit.user.UserInfoActivity; import cn.wildfire.chat.kit.user.UserViewModel; -import cn.wildfire.chat.kit.widget.ViewPagerFixed; +import cn.wildfire.chat.kit.utils.FileUtils; +import cn.wildfire.chat.kit.viewmodel.MessageViewModel; +import cn.wildfire.chat.kit.voip.conference.ConferenceInfoActivity; +import cn.wildfire.chat.kit.workspace.WebViewFragment; import cn.wildfirechat.chat.R; +import cn.wildfirechat.client.ConnectionStatus; +import cn.wildfirechat.message.FileMessageContent; +import cn.wildfirechat.message.ImageMessageContent; +import cn.wildfirechat.message.LinkMessageContent; +import cn.wildfirechat.message.Message; +import cn.wildfirechat.message.MessageContent; +import cn.wildfirechat.message.TextMessageContent; +import cn.wildfirechat.message.VideoMessageContent; +import cn.wildfirechat.message.core.MessageContentType; +import cn.wildfirechat.message.core.MessageStatus; import cn.wildfirechat.model.Conversation; import cn.wildfirechat.model.UserInfo; +import cn.wildfirechat.remote.ChatManager; import q.rorbin.badgeview.QBadgeView; -public class MainActivity extends WfcBaseActivity implements ViewPager.OnPageChangeListener { +public class MainActivity extends WfcBaseActivity { private List mFragmentList = new ArrayList<>(4); - @Bind(R.id.bottomNavigationView) BottomNavigationView bottomNavigationView; - @Bind(R.id.vpContent) - ViewPagerFixed mVpContent; + ViewPager2 contentViewPager; + TextView startingTextView; + LinearLayout contentLinearLayout; private QBadgeView unreadMessageUnreadBadgeView; private QBadgeView unreadFriendRequestBadgeView; + private QBadgeView discoveryBadgeView; private static final int REQUEST_CODE_SCAN_QR_CODE = 100; - private static final int REQUEST_IGNORE_BATTERY_CODE = 101; + private static final int REQUEST_CODE_PICK_CONTACT = 101; + private boolean isInitialized = false; + + private ContactListFragment contactListFragment; private ConversationListFragment conversationListFragment; - private ContactFragment contactFragment; - private DiscoveryFragment discoveryFragment; - private MeFragment meFragment; + + private ContactViewModel contactViewModel; + private ConversationListViewModel conversationListViewModel; + private long lastSelectConversatonListItemTimestamp = 0; + private MenuItem secretChatMenuItem; + + protected void bindViews() { + super.bindViews(); + bottomNavigationView = findViewById(R.id.bottomNavigationView); + contentViewPager = findViewById(R.id.contentViewPager); + startingTextView = findViewById(R.id.startingTextView); + contentLinearLayout = findViewById(R.id.contentLinearLayout); + } + + private Observer imStatusLiveDataObserver = status -> { + if (status && !isInitialized) { + init(); + isInitialized = true; + } + }; @Override protected int contentLayout() { return R.layout.main_activity; } + @Override + protected void onResume() { + super.onResume(); + + if (contactViewModel != null) { + contactViewModel.reloadFriendRequestStatus(); + conversationListViewModel.reloadConversationUnreadStatus(); + } + updateMomentBadgeView(); + } + @Override protected void afterViews() { + bottomNavigationView.setItemIconTintList(null); + if (TextUtils.isEmpty(Config.WORKSPACE_URL)) { + bottomNavigationView.getMenu().removeItem(R.id.workspace); + } + IMServiceStatusViewModel imServiceStatusViewModel = new ViewModelProvider(this).get(IMServiceStatusViewModel.class); + imServiceStatusViewModel.imServiceStatusLiveData().observe(this, imStatusLiveDataObserver); + IMConnectionStatusViewModel connectionStatusViewModel = new ViewModelProvider(this).get(IMConnectionStatusViewModel.class); + connectionStatusViewModel.connectionStatusLiveData().observe(this, status -> { + if (status == ConnectionStatus.ConnectionStatusTokenIncorrect + || status == ConnectionStatus.ConnectionStatusSecretKeyMismatch + || status == ConnectionStatus.ConnectionStatusRejected + || status == ConnectionStatus.ConnectionStatusLogout + || status == ConnectionStatus.ConnectionStatusKickedoff) { + SharedPreferences sp = getSharedPreferences(Config.SP_CONFIG_FILE_NAME, Context.MODE_PRIVATE); + sp.edit() + .clear() + .putBoolean("hasReadUserAgreement", true) + .apply(); + sp = getSharedPreferences("moment", Context.MODE_PRIVATE); + sp.edit().clear().apply(); + OKHttpHelper.clearCookies(); + if (status == ConnectionStatus.ConnectionStatusLogout) { + reLogin(false); + } else { + ChatManager.Instance().disconnect(true, false); + if (status == ConnectionStatus.ConnectionStatusKickedoff) { + reLogin(true); + } + } + } else if (status == ConnectionStatus.ConnectionStatusNotLicensed) { + Toast.makeText(MainActivity.this, "专业版IM服务没有授权或者授权过期!!!", Toast.LENGTH_LONG).show(); + } else if (status == ConnectionStatus.ConnectionStatusTimeInconsistent) { + Toast.makeText(MainActivity.this, "服务器和客户端时间相差太大!!!", Toast.LENGTH_LONG).show(); + } else if (status == ConnectionStatus.ConnectionStatusConnected) { + if (secretChatMenuItem != null) { + boolean isEnableSecretChat = ChatManager.Instance().isEnableSecretChat(); + secretChatMenuItem.setEnabled(isEnableSecretChat); + } + } + }); + MessageViewModel messageViewModel = new ViewModelProvider(this).get(MessageViewModel.class); + messageViewModel.messageLiveData().observe(this, uiMessages -> { + for (UiMessage uiMessage : uiMessages) { + if (uiMessage.message.messageId > 0 && (uiMessage.message.content.getMessageContentType() == MessageContentType.MESSAGE_CONTENT_TYPE_FEED + || uiMessage.message.content.getMessageContentType() == MessageContentType.MESSAGE_CONTENT_TYPE_FEED_COMMENT)) { + updateMomentBadgeView(); + } + } + }); + + Intent intent = getIntent(); + String action = intent.getAction(); + String type = intent.getType(); + if (Intent.ACTION_SEND.equals(action)) { + if ("text/plain".equals(type)) { + handleSendText(intent); + } else { + Uri fileUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + handleSendFile(fileUri); + } + } else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) { + handleSendMultiple(intent); + } + + requestMandatoryPermissions(); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + String action = intent.getAction(); + String type = intent.getType(); + if (Intent.ACTION_SEND.equals(action)) { + if ("text/plain".equals(type)) { + handleSendText(intent); + } else { + Uri fileUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + handleSendFile(fileUri); + } + } else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) { + handleSendMultiple(intent); // Handle multiple items being sent + } + } + + @Override + protected void afterMenus(Menu menu) { + super.afterMenus(menu); + boolean isEnableSecretChat = ChatManager.Instance().isEnableSecretChat(); + if (!isEnableSecretChat) { +// secretChatMenuItem = menu.findItem(R.id.secretChat); +// secretChatMenuItem.setEnabled(false); + } + } + + private void reLogin(boolean isKickedOff) { + if (isFinishing()) { + return; + } + Intent intent = new Intent(this, SplashActivity.class); + intent.putExtra("isKickedOff", isKickedOff); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finish(); + } + + private void init() { initView(); - ConversationListViewModel conversationListViewModel = ViewModelProviders - .of(this, new ConversationListViewModelFactory(Arrays.asList(Conversation.ConversationType.Single, Conversation.ConversationType.Group, Conversation.ConversationType.Channel), Arrays.asList(0))) - .get(ConversationListViewModel.class); + conversationListViewModel = new ViewModelProvider(this, new ConversationListViewModelFactory(Arrays.asList(Conversation.ConversationType.Single, Conversation.ConversationType.Group, Conversation.ConversationType.Channel, Conversation.ConversationType.SecretChat), Arrays.asList(0))) + .get(ConversationListViewModel.class); conversationListViewModel.unreadCountLiveData().observe(this, unreadCount -> { if (unreadCount != null && unreadCount.unread > 0) { - if (unreadMessageUnreadBadgeView == null) { - BottomNavigationMenuView bottomNavigationMenuView = ((BottomNavigationMenuView) bottomNavigationView.getChildAt(0)); - View view = bottomNavigationMenuView.getChildAt(0); - unreadMessageUnreadBadgeView = new QBadgeView(MainActivity.this); - unreadMessageUnreadBadgeView.bindTarget(view); - } - unreadMessageUnreadBadgeView.setBadgeNumber(unreadCount.unread); - } else if (unreadMessageUnreadBadgeView != null) { - unreadMessageUnreadBadgeView.hide(true); + showUnreadMessageBadgeView(unreadCount.unread); + } else { + hideUnreadMessageBadgeView(); } }); - ContactViewModel contactViewModel = ViewModelProviders.of(this).get(ContactViewModel.class); + contactViewModel = WfcUIKit.getAppScopeViewModel(ContactViewModel.class); contactViewModel.friendRequestUpdatedLiveData().observe(this, count -> { if (count == null || count == 0) { - if (unreadFriendRequestBadgeView != null) { - unreadFriendRequestBadgeView.hide(true); - } + hideUnreadFriendRequestBadgeView(); } else { - if (unreadFriendRequestBadgeView == null) { - BottomNavigationMenuView bottomNavigationMenuView = ((BottomNavigationMenuView) bottomNavigationView.getChildAt(0)); - View view = bottomNavigationMenuView.getChildAt(1); - unreadFriendRequestBadgeView = new QBadgeView(MainActivity.this); - unreadFriendRequestBadgeView.bindTarget(view); - } - unreadFriendRequestBadgeView.setBadgeNumber(count); + showUnreadFriendRequestBadgeView(count); } }); - if (checkDisplayName()) { - ignoreBatteryOption(); + + checkDisplayName(); + } + + private void showUnreadMessageBadgeView(int count) { + if (unreadMessageUnreadBadgeView == null) { + BottomNavigationMenuView bottomNavigationMenuView = ((BottomNavigationMenuView) bottomNavigationView.getChildAt(0)); + View view = bottomNavigationMenuView.getChildAt(0); + unreadMessageUnreadBadgeView = new QBadgeView(MainActivity.this); + unreadMessageUnreadBadgeView.bindTarget(view); + } + unreadMessageUnreadBadgeView.setBadgeNumber(count); + } + + private void hideUnreadMessageBadgeView() { + if (unreadMessageUnreadBadgeView != null) { + unreadMessageUnreadBadgeView.hide(true); + unreadMessageUnreadBadgeView = null; } } + private void updateMomentBadgeView() { + if (!WfcUIKit.getWfcUIKit().isSupportMoment()) { + return; + } + List messages = ChatManager.Instance().getMessagesEx2(Collections.singletonList(Conversation.ConversationType.Single), Collections.singletonList(1), Arrays.asList(MessageStatus.Unread), 0, true, 100, null); + int count = messages == null ? 0 : messages.size(); + if (count > 0) { + if (discoveryBadgeView == null) { + BottomNavigationMenuView bottomNavigationMenuView = ((BottomNavigationMenuView) bottomNavigationView.getChildAt(0)); + int index = TextUtils.isEmpty(Config.WORKSPACE_URL) ? 2 : 3; + View view = bottomNavigationMenuView.getChildAt(index); + discoveryBadgeView = new QBadgeView(MainActivity.this); + discoveryBadgeView.bindTarget(view); + } + discoveryBadgeView.setBadgeNumber(count); + } else { + if (discoveryBadgeView != null) { + discoveryBadgeView.hide(true); + discoveryBadgeView = null; + } + } + } + + private void showUnreadFriendRequestBadgeView(int count) { + if (unreadFriendRequestBadgeView == null) { + BottomNavigationMenuView bottomNavigationMenuView = ((BottomNavigationMenuView) bottomNavigationView.getChildAt(0)); + View view = bottomNavigationMenuView.getChildAt(1); + unreadFriendRequestBadgeView = new QBadgeView(MainActivity.this); + unreadFriendRequestBadgeView.bindTarget(view); + } + unreadFriendRequestBadgeView.setBadgeNumber(count); + } + public void hideUnreadFriendRequestBadgeView() { if (unreadFriendRequestBadgeView != null) { unreadFriendRequestBadgeView.hide(true); @@ -144,35 +355,78 @@ public class MainActivity extends WfcBaseActivity implements ViewPager.OnPageCha } private void initView() { - setTitle(UIUtils.getString(R.string.app_name)); + setTitle(getString(R.string.app_title_chat)); + + startingTextView.setVisibility(View.GONE); + contentLinearLayout.setVisibility(View.VISIBLE); //设置ViewPager的最大缓存页面 - mVpContent.setOffscreenPageLimit(3); + contentViewPager.setOffscreenPageLimit(4); conversationListFragment = new ConversationListFragment(); - contactFragment = new ContactFragment(); - discoveryFragment = new DiscoveryFragment(); - meFragment = new MeFragment(); + contactListFragment = new ContactListFragment(); + DiscoveryFragment discoveryFragment = new DiscoveryFragment(); + MeFragment meFragment = new MeFragment(); mFragmentList.add(conversationListFragment); - mFragmentList.add(contactFragment); + mFragmentList.add(contactListFragment); + boolean showWorkSpace = !TextUtils.isEmpty(Config.WORKSPACE_URL); + if (showWorkSpace) { + mFragmentList.add(WebViewFragment.loadUrl(Config.WORKSPACE_URL)); + } mFragmentList.add(discoveryFragment); mFragmentList.add(meFragment); - mVpContent.setAdapter(new HomeFragmentPagerAdapter(getSupportFragmentManager(), mFragmentList)); - mVpContent.setOnPageChangeListener(this); + contentViewPager.setAdapter(new HomeFragmentPagerAdapter(this, mFragmentList)); + contentViewPager.registerOnPageChangeCallback(this.onPageChangeCallback); - bottomNavigationView.setOnNavigationItemSelectedListener(item -> { + bottomNavigationView.setOnItemReselectedListener(item -> { switch (item.getItemId()) { case R.id.conversation_list: - mVpContent.setCurrentItem(0); + long now = System.currentTimeMillis(); + if (now - lastSelectConversatonListItemTimestamp < 200) { + conversationListFragment.scrollToNextUnreadConversation(); + } + lastSelectConversatonListItemTimestamp = now; + break; + default: + break; + } + }); + bottomNavigationView.setOnItemSelectedListener(item -> { + switch (item.getItemId()) { + case R.id.conversation_list: + setCurrentViewPagerItem(0, false); + setTitle(R.string.app_title_chat); + if (!isDarkTheme()) { + setTitleBackgroundResource(R.color.gray5, false); + } break; case R.id.contact: - mVpContent.setCurrentItem(1); + setCurrentViewPagerItem(1, false); + setTitle(R.string.app_title_contact); + if (!isDarkTheme()) { + setTitleBackgroundResource(R.color.gray5, false); + } + break; + case R.id.workspace: + setCurrentViewPagerItem(2, false); + setTitle(R.string.app_title_workspace); + if (!isDarkTheme()) { + setTitleBackgroundResource(R.color.gray5, false); + } break; case R.id.discovery: - mVpContent.setCurrentItem(2); + setCurrentViewPagerItem(showWorkSpace ? 3 : 2, false); + setTitle(R.string.app_title_discover); + if (!isDarkTheme()) { + setTitleBackgroundResource(R.color.gray5, false); + } break; case R.id.me: - mVpContent.setCurrentItem(3); + setCurrentViewPagerItem(showWorkSpace ? 4 : 3, false); + setTitle(R.string.app_title_me); + if (!isDarkTheme()) { + setTitleBackgroundResource(R.color.white, false); + } break; default: break; @@ -181,20 +435,58 @@ public class MainActivity extends WfcBaseActivity implements ViewPager.OnPageCha }); } + private void showMoreActionMenu() { + List> menuItems = new ArrayList<>(); + menuItems.add(new Pair<>(R.mipmap.ic_start_chat, getString(R.string.start_group_chat))); + menuItems.add(new Pair<>(R.mipmap.ic_start_chat, getString(R.string.start_secret_chat))); + menuItems.add(new Pair<>(R.mipmap.ic_add_friend, getString(R.string.add_friend))); + menuItems.add(new Pair<>(R.mipmap.ic_qr_code, getString(R.string.scan_qrcode))); + PopupMenu moreActionsMenu = new PopupMenu(this, menuItems, position -> { + switch (position) { + case 0: + createConversation(); + break; + case 1: + boolean isEnableSecretChat = ChatManager.Instance().isEnableSecretChat(); + if (isEnableSecretChat) { + pickContactToCreateSecretConversation(); + } else { + Toast.makeText(this, R.string.e2e_not_enable, Toast.LENGTH_SHORT).show(); + } + break; + case 2: + searchUser(); + break; + case 3: + String[] permissions = new String[]{Manifest.permission.CAMERA}; + PermissionKit.PermissionReqTuple[] tuples = PermissionKit.buildRequestPermissionTuples(this, permissions); + PermissionKit.checkThenRequestPermission(this, getSupportFragmentManager(), tuples, o -> { + startActivityForResult(new Intent(MainActivity.this, ScanQRCodeActivity.class), REQUEST_CODE_SCAN_QR_CODE); + }); + default: + break; + } + }); + View view = findViewById(R.id.more); + View toolbar = findViewById(R.id.toolbar); + int[] location = new int[2]; + toolbar.getLocationOnScreen(location); + int y = location[1] + toolbar.getHeight(); + view.getLocationOnScreen(location); + int yOffset = y - (location[1] + view.getHeight()); + + moreActionsMenu.showAsListMenu(view, 0, yOffset); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + case R.id.more: + showMoreActionMenu(); + return true; case R.id.search: showSearchPortal(); break; - case R.id.chat: - createConversation(); - break; - case R.id.add_contact: - searchUser(); - break; - case R.id.scan_qrcode: - startActivityForResult(new Intent(this, CaptureActivity.class), REQUEST_CODE_SCAN_QR_CODE); default: break; } @@ -211,58 +503,52 @@ public class MainActivity extends WfcBaseActivity implements ViewPager.OnPageCha startActivity(intent); } - private void searchUser() { - Intent intent = new Intent(this, SearchUserActivity.class); - startActivity(intent); + private void createSecretChat(String userId) { + ConversationViewModel conversationViewModel = new ViewModelProvider(this).get(ConversationViewModel.class); + conversationViewModel.createSecretChat(userId).observeForever(stringOperateResult -> { + if (stringOperateResult.isSuccess()) { + Conversation conversation = new Conversation(Conversation.ConversationType.SecretChat, stringOperateResult.getResult().first, stringOperateResult.getResult().second); + Intent intent = new Intent(this, ConversationActivity.class); + intent.putExtra("conversation", conversation); + startActivity(intent); + } else { + if (stringOperateResult.getErrorCode() == 86) { + //自己关闭了密聊功能 + } else if (stringOperateResult.getErrorCode() == 87) { + //对方关闭了密聊功能 + } else { + //提示网络错误 + } + } + }); } - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + private void pickContactToCreateSecretConversation() { + Intent intent = new Intent(this, ContactListActivity.class); + intent.putExtra("showChannel", false); + startActivityForResult(intent, REQUEST_CODE_PICK_CONTACT); } - @Override - public void onPageSelected(int position) { - switch (position) { - case 0: - bottomNavigationView.setSelectedItemId(R.id.conversation_list); - break; - case 1: - bottomNavigationView.setSelectedItemId(R.id.contact); - break; - case 2: - bottomNavigationView.setSelectedItemId(R.id.discovery); - break; - case 3: - bottomNavigationView.setSelectedItemId(R.id.me); - break; - default: - break; - } - contactFragment.showQuickIndexBar(position == 1); - } - - @Override - public void onPageScrollStateChanged(int state) { - if (state != ViewPager.SCROLL_STATE_IDLE) { - //滚动过程中隐藏快速导航条 - contactFragment.showQuickIndexBar(false); - } else { - contactFragment.showQuickIndexBar(true); - } + private void searchUser() { + Intent intent = new Intent(this, SearchUserActivity.class); + startActivity(intent); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (resultCode != RESULT_OK) { + super.onActivityResult(requestCode, resultCode, data); + return; + } switch (requestCode) { case REQUEST_CODE_SCAN_QR_CODE: - if (resultCode == RESULT_OK) { - String result = data.getStringExtra(Intents.Scan.RESULT); - onScanPcQrCode(result); - } + String result = data.getStringExtra(Intents.Scan.RESULT); + onScanPcQrCode(result); break; - case REQUEST_IGNORE_BATTERY_CODE: - if (resultCode == RESULT_CANCELED) { - Toast.makeText(this, "允许野火IM后台运行,更能保证消息的实时性", Toast.LENGTH_SHORT).show(); + case REQUEST_CODE_PICK_CONTACT: + UserInfo userInfo = data.getParcelableExtra("userInfo"); + if (userInfo != null) { + createSecretChat(userInfo.uid); } break; default: @@ -271,12 +557,25 @@ public class MainActivity extends WfcBaseActivity implements ViewPager.OnPageCha } } + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + Intent i = getBaseContext().getPackageManager().getLaunchIntentForPackage( + getBaseContext().getPackageName()); + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(i); + finish(); + } + private void onScanPcQrCode(String qrcode) { String prefix = qrcode.substring(0, qrcode.lastIndexOf('/') + 1); - String value = qrcode.substring(qrcode.lastIndexOf("/") + 1); -// Uri uri = Uri.parse(value); -// uri.getAuthority(); -// uri.getQuery() + String value = qrcode.substring(qrcode.lastIndexOf('/') + 1, qrcode.indexOf('?') > 0 ? qrcode.indexOf('?') : qrcode.length()); + Uri uri = Uri.parse(qrcode); + Set queryNames = uri.getQueryParameterNames(); + Map params = new HashMap<>(); + for (String query : queryNames) { + params.put(query, uri.getQueryParameter(query)); + } switch (prefix) { case WfcScheme.QR_CODE_PREFIX_PC_SESSION: pcLogin(value); @@ -285,11 +584,14 @@ public class MainActivity extends WfcBaseActivity implements ViewPager.OnPageCha showUser(value); break; case WfcScheme.QR_CODE_PREFIX_GROUP: - joinGroup(value); + joinGroup(value, params); break; case WfcScheme.QR_CODE_PREFIX_CHANNEL: subscribeChannel(value); break; + case WfcScheme.QR_CODE_PREFIX_CONFERENCE: + joinConference(value, params); + break; default: Toast.makeText(this, "qrcode: " + qrcode, Toast.LENGTH_SHORT).show(); break; @@ -314,9 +616,10 @@ public class MainActivity extends WfcBaseActivity implements ViewPager.OnPageCha startActivity(intent); } - private void joinGroup(String groupId) { + private void joinGroup(String groupId, Map params) { Intent intent = new Intent(this, GroupInfoActivity.class); intent.putExtra("groupId", groupId); + intent.putExtra("from", (String) params.get("from")); startActivity(intent); } @@ -326,9 +629,16 @@ public class MainActivity extends WfcBaseActivity implements ViewPager.OnPageCha startActivity(intent); } + private void joinConference(String conferenceId, Map params) { + Intent intent = new Intent(this, ConferenceInfoActivity.class); + intent.putExtra("conferenceId", conferenceId); + intent.putExtra("password", (String) params.get("pwd")); + startActivity(intent); + } + private boolean checkDisplayName() { UserViewModel userViewModel = WfcUIKit.getAppScopeViewModel(UserViewModel.class); - SharedPreferences sp = getSharedPreferences("config", Context.MODE_PRIVATE); + SharedPreferences sp = getSharedPreferences("wfc_config", Context.MODE_PRIVATE); UserInfo userInfo = userViewModel.getUserInfo(userViewModel.getUserId(), false); if (userInfo != null && TextUtils.equals(userInfo.displayName, userInfo.mobile)) { if (!sp.getBoolean("updatedDisplayName", false)) { @@ -342,34 +652,202 @@ public class MainActivity extends WfcBaseActivity implements ViewPager.OnPageCha private void updateDisplayName() { MaterialDialog dialog = new MaterialDialog.Builder(this) - .content("修改个人昵称?") - .positiveText("修改") - .negativeText("取消") - .onPositive(new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - Intent intent = new Intent(MainActivity.this, ChangeMyNameActivity.class); - startActivity(intent); - } - }).build(); + .content("修改个人昵称?") + .positiveText("修改") + .negativeText("取消") + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + Intent intent = new Intent(MainActivity.this, ChangeMyNameActivity.class); + startActivity(intent); + } + }).build(); dialog.show(); } - - private void ignoreBatteryOption() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - try { - Intent intent = new Intent(); - String packageName = getPackageName(); - PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - if (!pm.isIgnoringBatteryOptimizations(packageName)) { - intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.parse("package:" + packageName)); - startActivityForResult(intent, REQUEST_IGNORE_BATTERY_CODE); + // 分享 + private void handleSendText(Intent intent) { + String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); + if (!TextUtils.isEmpty(sharedText)) { + MessageContent content = new TextMessageContent(sharedText); + shareMessage(content); + } else { + ClipData clipData = intent.getClipData(); + if (clipData != null) { + int count = clipData.getItemCount(); + if (count == 1) { + ClipData.Item item = clipData.getItemAt(0); + sharedText = (String) item.getText(); + + if (isMiShare(sharedText)) { + LinkMessageContent content = parseMiShare(sharedText); + shareMessage(content); + } else { + MessageContent content = new TextMessageContent(sharedText); + shareMessage(content); + } } - } catch (Exception e) { - e.printStackTrace(); } } } -} + + private void handleSendMultiple(Intent intent) { + // TODO 暂不支持一次分享多个文件,分享页面不支持,没有相关 UI +// ArrayList imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); +// if (imageUris != null) { +// for (Uri uri : imageUris) { +// handleSendFile(uri); +// } +// } + } + + private void handleSendFile(Uri fileUri) { + if (fileUri == null) { + return; + } + String filePath = FileUtils.getPath(this, fileUri); + if (TextUtils.isEmpty(filePath)) { + Toast.makeText(this, "Error selecting file", Toast.LENGTH_SHORT).show(); + return; + } + String suffix = filePath.substring(filePath.lastIndexOf(".")); + MessageContent content; + switch (suffix) { + case ".png": + case ".jpg": + case ".jpeg": + case ".gif": + content = new ImageMessageContent(filePath); + break; + case ".3gp": + case ".mpg": + case ".mpeg": + case ".mpe": + case ".mp4": + case ".avi": + content = new VideoMessageContent(filePath); + break; + default: + content = new FileMessageContent(filePath); + break; + } + shareMessage(content); + } + + private void shareMessage(MessageContent content) { + ArrayList msgs = new ArrayList<>(); + Message message = new Message(); + message.content = content; + msgs.add(message); + Intent intent = new Intent(this, ForwardActivity.class); + intent.putExtra("messages", msgs); + startActivity(intent); + } + + // 小米浏览器 我分享了【xxxx】, 快来看吧!@小米浏览器 | https://xxx + private boolean isMiShare(String text) { + if (TextUtils.isEmpty(text)) { + return false; + } + + if (text.startsWith("我分享了【") + && text.indexOf("】, 快来看吧!@小米浏览器 | http") > 1) { + return true; + } + return false; + } + + private LinkMessageContent parseMiShare(String text) { + LinkMessageContent content = new LinkMessageContent(); + String title = text.substring(text.indexOf("【") + 1, text.indexOf("】")); + content.setTitle(title); + String desc = text.substring(0, text.indexOf("@小米浏览器")); + content.setContentDigest(desc); + String url = text.substring(text.indexOf("http")); + content.setUrl(url); + return content; + } + + private void requestMandatoryPermissions() { + boolean resumed = false; +// if (Build.VERSION.SDK_INT >= 33) { +// AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); +// if (!alarmManager.canScheduleExactAlarms()) { +// Toast.makeText(this, "需要精确闹钟权限,否则不能正常使用 IM 功能", Toast.LENGTH_LONG).show(); +// Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM, Uri.parse("package:" + getPackageName())); +// startActivity(intent); +// resumed = true; +// } +// } + + +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { +// if (!Settings.canDrawOverlays(this)) { +// Toast.makeText(this, "需要后台弹出界面和显示悬浮窗权限,否则后台运行时,无法弹出音视频界面", Toast.LENGTH_LONG).show(); +// if (!resumed) { +// Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); +// startActivity(intent); +// } +// } +// } + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + String[] permissions = new String[]{Manifest.permission.POST_NOTIFICATIONS}; + PermissionKit.PermissionReqTuple[] tuples = PermissionKit.buildRequestPermissionTuples(this, permissions); + PermissionKit.checkThenRequestPermission(this, getSupportFragmentManager(), tuples, o -> { + // do nothing + }); + } + } + + private void setCurrentViewPagerItem(int item, boolean smoothScroll) { + if (contentViewPager.getCurrentItem() != item) { + contentViewPager.setCurrentItem(item, smoothScroll); + } + } + + private ViewPager2.OnPageChangeCallback onPageChangeCallback = new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int position) { + if (TextUtils.isEmpty(Config.WORKSPACE_URL)) { + if (position > 1) { + position++; + } + } + switch (position) { + case 0: + bottomNavigationView.setSelectedItemId(R.id.conversation_list); + break; + case 1: + bottomNavigationView.setSelectedItemId(R.id.contact); + break; + case 2: + bottomNavigationView.setSelectedItemId(R.id.workspace); + break; + case 3: + bottomNavigationView.setSelectedItemId(R.id.discovery); + break; + case 4: + bottomNavigationView.setSelectedItemId(R.id.me); + break; + default: + break; + } + contactListFragment.showQuickIndexBar(position == 1); + } + + @Override + public void onPageScrollStateChanged(int state) { + if (state != ViewPager.SCROLL_STATE_IDLE) { + //滚动过程中隐藏快速导航条 + contactListFragment.showQuickIndexBar(false); + } else { + contactListFragment.showQuickIndexBar(true); + } + } + }; +} \ No newline at end of file diff --git a/chat/src/main/java/cn/wildfire/chat/app/main/MeFragment.java b/chat/src/main/java/cn/wildfire/chat/app/main/MeFragment.java index 59ecca444dc820086749e24f2ed94182bcf63dad..6e61205101bde438370d4825e2c693563ee870cc 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/main/MeFragment.java +++ b/chat/src/main/java/cn/wildfire/chat/app/main/MeFragment.java @@ -1,6 +1,12 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app.main; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -14,45 +20,54 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; +import com.afollestad.materialdialogs.MaterialDialog; import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.RequestOptions; -import com.lqr.optionitemview.OptionItemView; import java.util.List; -import butterknife.Bind; -import butterknife.ButterKnife; -import butterknife.OnClick; +import cn.wildfire.chat.app.setting.AccountActivity; +import cn.wildfire.chat.app.setting.SettingActivity; import cn.wildfire.chat.kit.WfcUIKit; -import cn.wildfire.chat.kit.setting.SettingActivity; +import cn.wildfire.chat.kit.utils.LocaleUtils; +import cn.wildfire.chat.kit.conversation.file.FileRecordListActivity; +import cn.wildfire.chat.kit.favorite.FavoriteListActivity; +import cn.wildfire.chat.kit.settings.MessageNotifySettingActivity; +import cn.wildfire.chat.kit.third.utils.UIUtils; import cn.wildfire.chat.kit.user.UserInfoActivity; import cn.wildfire.chat.kit.user.UserViewModel; +import cn.wildfire.chat.kit.widget.OptionItemView; import cn.wildfirechat.chat.R; import cn.wildfirechat.model.UserInfo; +import cn.wildfirechat.remote.ChatManager; public class MeFragment extends Fragment { - @Bind(R.id.meLinearLayout) LinearLayout meLinearLayout; - @Bind(R.id.portraitImageView) ImageView portraitImageView; - @Bind(R.id.nameTextView) TextView nameTextView; - @Bind(R.id.accountTextView) TextView accountTextView; - @Bind(R.id.notificationOptionItemView) OptionItemView notificationOptionItem; - @Bind(R.id.settintOptionItemView) OptionItemView settingOptionItem; + OptionItemView fileRecordOptionItem; + + OptionItemView conversationOptionItem; + private UserViewModel userViewModel; private UserInfo userInfo; + private boolean isVisibleToUser; private Observer> userInfoLiveDataObserver = new Observer>() { @Override public void onChanged(@Nullable List userInfos) { + if (userInfos == null) { + return; + } for (UserInfo info : userInfos) { if (info.uid.equals(userViewModel.getUserId())) { userInfo = info; @@ -67,27 +82,83 @@ public class MeFragment extends Fragment { @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.main_fragment_me, container, false); - ButterKnife.bind(this, view); + bindViews(view); + bindEvents(view); init(); return view; } - private void updateUserInfo(UserInfo userInfo) { - Glide.with(this).load(userInfo.portrait).apply(new RequestOptions().placeholder(R.mipmap.avatar_def).centerCrop()).into(portraitImageView); - nameTextView.setText(userInfo.displayName); - accountTextView.setText("账号: " + userInfo.name); + @Override + + public void setMenuVisibility(boolean isvisible) { + super.setMenuVisibility(isvisible); + this.isVisibleToUser = isvisible; } - private void init() { - userViewModel = WfcUIKit.getAppScopeViewModel(UserViewModel.class); - userViewModel.getUserInfoAsync(userViewModel.getUserId(), true) - .observe(this, info -> { + @Override + public void onResume() { + super.onResume(); + if (this.isVisibleToUser && userViewModel != null) { + userViewModel.getUserInfoAsync(userViewModel.getUserId(), true) + .observe(getViewLifecycleOwner(), info -> { userInfo = info; if (userInfo != null) { updateUserInfo(userInfo); } }); + } + } + + private void bindEvents(View view) { + view.findViewById(R.id.meLinearLayout).setOnClickListener(v -> showMyInfo()); + view.findViewById(R.id.favOptionItemView).setOnClickListener(v -> fav()); + view.findViewById(R.id.accountOptionItemView).setOnClickListener(v -> account()); + view.findViewById(R.id.fileRecordOptionItemView).setOnClickListener(v -> files()); + view.findViewById(R.id.themeOptionItemView).setOnClickListener(v -> theme()); + view.findViewById(R.id.languageOptionItemView).setOnClickListener(v -> selectLanguage()); + view.findViewById(R.id.settingOptionItemView).setOnClickListener(v -> setting()); + view.findViewById(R.id.notificationOptionItemView).setOnClickListener(v -> msgNotifySetting()); + view.findViewById(R.id.conversationOptionItemView).setOnClickListener(v -> conversationSetting()); + } + + private void bindViews(View view) { + meLinearLayout = view.findViewById(R.id.meLinearLayout); + portraitImageView = view.findViewById(R.id.portraitImageView); + nameTextView = view.findViewById(R.id.nameTextView); + accountTextView = view.findViewById(R.id.accountTextView); + notificationOptionItem = view.findViewById(R.id.notificationOptionItemView); + settingOptionItem = view.findViewById(R.id.settingOptionItemView); + conversationOptionItem = view.findViewById(R.id.conversationOptionItemView); + fileRecordOptionItem = view.findViewById(R.id.fileRecordOptionItemView); + } + + private void updateUserInfo(UserInfo userInfo) { + RequestOptions options = new RequestOptions() + .placeholder(R.mipmap.avatar_def) + .transforms(new CenterCrop(), new RoundedCorners(UIUtils.dip2Px(getContext(), 10))); + Glide.with(this) + .load(userInfo.portrait) + .apply(options) + .into(portraitImageView); + nameTextView.setText(userInfo.displayName); + accountTextView.setText(getString(R.string.account_label, userInfo.name)); + } + + private void init() { + userViewModel = WfcUIKit.getAppScopeViewModel(UserViewModel.class); + userViewModel.getUserInfoAsync(userViewModel.getUserId(), true) + .observe(getViewLifecycleOwner(), info -> { + userInfo = info; + if (userInfo != null) { + updateUserInfo(userInfo); + } + }); userViewModel.userInfoLiveData().observeForever(userInfoLiveDataObserver); + if (ChatManager.Instance().isCommercialServer()) { + fileRecordOptionItem.setVisibility(View.VISIBLE); + } else { + fileRecordOptionItem.setVisibility(View.GONE); + } } @Override @@ -96,16 +167,96 @@ public class MeFragment extends Fragment { userViewModel.userInfoLiveData().removeObserver(userInfoLiveDataObserver); } - @OnClick(R.id.meLinearLayout) void showMyInfo() { Intent intent = new Intent(getActivity(), UserInfoActivity.class); intent.putExtra("userInfo", userInfo); startActivity(intent); } - @OnClick(R.id.settintOptionItemView) + void fav() { + Intent intent = new Intent(getActivity(), FavoriteListActivity.class); + startActivity(intent); + } + + void account() { + Intent intent = new Intent(getActivity(), AccountActivity.class); + startActivity(intent); + } + + void files() { + Intent intent = new Intent(getActivity(), FileRecordListActivity.class); + startActivity(intent); + } + + + void theme() { + SharedPreferences sp = getActivity().getSharedPreferences("wfc_kit_config", Context.MODE_PRIVATE); + boolean darkTheme = sp.getBoolean("darkTheme", true); + new MaterialDialog.Builder(getContext()).items(R.array.themes).itemsCallback(new MaterialDialog.ListCallback() { + @Override + public void onSelection(MaterialDialog dialog, View v, int position, CharSequence text) { + if (position == 0 && darkTheme) { + sp.edit().putBoolean("darkTheme", false).apply(); + restart(); + return; + } + if (position == 1 && !darkTheme) { + sp.edit().putBoolean("darkTheme", true).apply(); + restart(); + } + } + }).show(); + } + + void selectLanguage() { + String currentLanguage = LocaleUtils.getLanguage(getContext()); + boolean isChinese = LocaleUtils.LANGUAGE_CHINESE.equals(currentLanguage); + + new MaterialDialog.Builder(getContext()).items(R.array.languages).itemsCallback(new MaterialDialog.ListCallback() { + @Override + public void onSelection(MaterialDialog dialog, View v, int position, CharSequence text) { + if (position == 0 && !isChinese) { + // 选择中文 + changeLanguage(LocaleUtils.LANGUAGE_CHINESE); + return; + } + if (position == 1 && isChinese) { + // 选择英文 + changeLanguage(LocaleUtils.LANGUAGE_ENGLISH); + } + } + }).show(); + } + + private void changeLanguage(String languageCode) { + LocaleUtils.setLocale(getContext(), languageCode); + // 重启应用以应用语言更改 + restart(); + } + void setting() { Intent intent = new Intent(getActivity(), SettingActivity.class); startActivity(intent); } + + void msgNotifySetting() { + Intent intent = new Intent(getActivity(), MessageNotifySettingActivity.class); + startActivity(intent); + } + + void conversationSetting() { + // TODO + // 设置背景等 + } + + private void restart() { + // 创建一个指向主活动的新意图,并清除任务栈 + Intent intent = new Intent(getActivity(), MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + // 结束当前活动 + if (getActivity() != null) { + getActivity().finish(); + } + } } diff --git a/chat/src/main/java/cn/wildfire/chat/app/main/PCLoginActivity.java b/chat/src/main/java/cn/wildfire/chat/app/main/PCLoginActivity.java index 19d759c9defe02e4ed8e32e2f31e7b088ea2a761..674ff059297af1cb96863667b4003e0d5fe3bb0f 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/main/PCLoginActivity.java +++ b/chat/src/main/java/cn/wildfire/chat/app/main/PCLoginActivity.java @@ -1,36 +1,53 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app.main; import android.text.TextUtils; import android.widget.Button; +import android.widget.TextView; import android.widget.Toast; -import com.afollestad.materialdialogs.MaterialDialog; -import java.util.HashMap; -import java.util.Map; -import androidx.lifecycle.ViewModelProviders; -import butterknife.Bind; -import butterknife.OnClick; -import cn.wildfire.chat.app.Config; +import com.afollestad.materialdialogs.MaterialDialog; + +import cn.wildfire.chat.app.AppService; import cn.wildfire.chat.app.login.model.PCSession; import cn.wildfire.chat.kit.WfcBaseActivity; import cn.wildfire.chat.kit.WfcUIKit; -import cn.wildfire.chat.kit.net.OKHttpHelper; -import cn.wildfire.chat.kit.net.SimpleCallback; import cn.wildfire.chat.kit.user.UserViewModel; import cn.wildfirechat.chat.R; +import cn.wildfirechat.client.Platform; public class PCLoginActivity extends WfcBaseActivity { private String token; - private PCSession pcSession; - @Bind(R.id.confirmButton) + private boolean isConfirmPcLogin = false; Button confirmButton; + TextView descTextView; + + private Platform platform; + + protected void bindEvents() { + super.bindEvents(); + findViewById(R.id.confirmButton).setOnClickListener(v -> confirmPCLogin()); + findViewById(R.id.cancelButton).setOnClickListener(v -> cancelPCLogin()); + } + + protected void bindViews() { + super.bindViews(); + confirmButton = findViewById(R.id.confirmButton); + descTextView = findViewById(R.id.descTextView); + } @Override protected void beforeViews() { token = getIntent().getStringExtra("token"); - if (TextUtils.isEmpty(token)) { + isConfirmPcLogin = getIntent().getBooleanExtra("isConfirmPcLogin", false); + int tmp = getIntent().getIntExtra("platform", 0); + platform = Platform.platform(tmp); + if (!isConfirmPcLogin && TextUtils.isEmpty(token)) { finish(); } } @@ -42,35 +59,57 @@ public class PCLoginActivity extends WfcBaseActivity { @Override protected void afterViews() { - scanPCLogin(token); + descTextView.setText(getString(R.string.pc_login_allow, platform.getPlatFormName())); + if (isConfirmPcLogin) { + confirmButton.setEnabled(true); + } else { + scanPCLogin(token); + } } - @OnClick(R.id.confirmButton) void confirmPCLogin() { UserViewModel userViewModel = WfcUIKit.getAppScopeViewModel(UserViewModel.class); confirmPCLogin(token, userViewModel.getUserId()); } + void cancelPCLogin() { + AppService.Instance().cancelPCLogin(token, new AppService.PCLoginCallback() { + @Override + public void onUiSuccess() { + if (isFinishing()) { + return; + } + finish(); + } + + @Override + public void onUiFailure(int code, String msg) { + if (isFinishing()) { + return; + } + finish(); + } + }); + } + private void scanPCLogin(String token) { MaterialDialog dialog = new MaterialDialog.Builder(this) - .content("处理中") - .progress(true, 100) - .build(); + .content(R.string.pc_login_processing) + .progress(true, 100) + .build(); dialog.show(); - String url = "http://" + Config.APP_SERVER_HOST + ":" + Config.APP_SERVER_PORT + "/scan_pc"; - url += "/" + token; - OKHttpHelper.post(url, null, new SimpleCallback() { + + AppService.Instance().scanPCLogin(token, new AppService.ScanPCCallback() { @Override public void onUiSuccess(PCSession pcSession) { if (isFinishing()) { return; } dialog.dismiss(); - PCLoginActivity.this.pcSession = pcSession; if (pcSession.getStatus() == 1) { confirmButton.setEnabled(true); } else { - Toast.makeText(PCLoginActivity.this, "status: " + pcSession.getStatus(), Toast.LENGTH_SHORT).show(); + Toast.makeText(PCLoginActivity.this, getString(R.string.pc_login_status, pcSession.getStatus()), Toast.LENGTH_SHORT).show(); finish(); } } @@ -87,18 +126,13 @@ public class PCLoginActivity extends WfcBaseActivity { } private void confirmPCLogin(String token, String userId) { - String url = "http://" + Config.APP_SERVER_HOST + ":" + Config.APP_SERVER_PORT + "/confirm_pc"; - - Map params = new HashMap<>(2); - params.put("user_id", userId); - params.put("token", token); - OKHttpHelper.post(url, params, new SimpleCallback() { + AppService.Instance().confirmPCLogin(token, userId, new AppService.PCLoginCallback() { @Override - public void onUiSuccess(PCSession pcSession) { + public void onUiSuccess() { if (isFinishing()) { return; } - Toast.makeText(PCLoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show(); + Toast.makeText(PCLoginActivity.this, R.string.pc_login_success, Toast.LENGTH_SHORT).show(); finish(); } diff --git a/chat/src/main/java/cn/wildfire/chat/app/main/SplashActivity.java b/chat/src/main/java/cn/wildfire/chat/app/main/SplashActivity.java index 4c6a32b4ccf1121312498f639c4b999bb76c46c3..e716f657724d41cd28e8058a6204ae7f5f0195a9 100644 --- a/chat/src/main/java/cn/wildfire/chat/app/main/SplashActivity.java +++ b/chat/src/main/java/cn/wildfire/chat/app/main/SplashActivity.java @@ -1,149 +1,114 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + package cn.wildfire.chat.app.main; -import android.Manifest; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.provider.Settings; import android.text.TextUtils; import android.view.View; import android.view.Window; -import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; -import butterknife.ButterKnife; -import cn.wildfire.chat.app.login.SMSLoginActivity; +import cn.wildfire.chat.app.login.LoginActivity; +import cn.wildfire.chat.app.misc.KeyStoreUtil; +import cn.wildfire.chat.kit.Config; +import cn.wildfire.chat.kit.utils.LocaleUtils; import cn.wildfirechat.chat.R; public class SplashActivity extends AppCompatActivity { - private static String[] permissions = {Manifest.permission.READ_PHONE_STATE, - // 位置 - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION, - - //相机、麦克风 - Manifest.permission.RECORD_AUDIO, - Manifest.permission.CAMERA, - //存储空间 - Manifest.permission.WRITE_EXTERNAL_STORAGE, - }; - private static final int REQUEST_CODE_DRAW_OVERLAY = 101; - - private SharedPreferences sharedPreferences; private String id; private String token; - private void hideStatusBar() { - View decorView = getWindow().getDecorView(); - // Hide the status bar. - int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN; - decorView.setSystemUiVisibility(uiOptions); - } - @TargetApi(Build.VERSION_CODES.M) @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); setContentView(R.layout.activity_splash); - ButterKnife.bind(this); - hideStatusBar(); + hideSystemUI(); + setStatusBarColor(R.color.gray5); - sharedPreferences = getSharedPreferences("config", Context.MODE_PRIVATE); - id = sharedPreferences.getString("id", null); - token = sharedPreferences.getString("token", null); - if (checkPermission()) { - if (checkOverlayPermission()) { - new Handler().postDelayed(this::showNextScreen, 1000); - } - } else { - requestPermissions(permissions, 100); + try { + id = KeyStoreUtil.getData(this, "wf_userId"); + token = KeyStoreUtil.getData(this, "wf_token"); + } catch (Exception e) { + e.printStackTrace(); } - } - private boolean checkPermission() { - boolean granted = true; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - for (String permission : permissions) { - granted = checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; - if (!granted) { - break; - } - } - } - return granted; + new Handler().postDelayed(this::showNextScreen, 1000); } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - for (int grantResult : grantResults) { - if (grantResult != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, "需要悬浮窗等权限才能正常使用", Toast.LENGTH_LONG).show(); - finish(); - return; - } - } - if (checkOverlayPermission()) { - showNextScreen(); - } - } - - private boolean checkOverlayPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (!Settings.canDrawOverlays(this)) { - Toast.makeText(this, "需要悬浮窗权限", Toast.LENGTH_LONG).show(); - startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), REQUEST_CODE_DRAW_OVERLAY); - return false; - } - } - return true; + protected void attachBaseContext(Context newBase) { + String language = LocaleUtils.getLanguage(newBase); + Context context = LocaleUtils.updateResources(newBase, language); + super.attachBaseContext(context); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUEST_CODE_DRAW_OVERLAY) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (!Settings.canDrawOverlays(this)) { - Toast.makeText(this, "授权失败", Toast.LENGTH_LONG).show(); - finish(); - } else { - showNextScreen(); - } - } - } - } private void showNextScreen() { if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(token)) { showMain(); } else { - showLogin(); + SharedPreferences sp = getSharedPreferences(Config.SP_CONFIG_FILE_NAME, Context.MODE_PRIVATE); + if (sp.getBoolean("hasReadUserAgreement", false)) { + showLogin(); + } else { + showAgreement(); + } } } private void showMain() { Intent intent = new Intent(this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); finish(); } private void showLogin() { Intent intent; - intent = new Intent(this, SMSLoginActivity.class); + intent = new Intent(this, LoginActivity.class); + intent.putExtra("isKickedOff", getIntent().getBooleanExtra("isKickedOff", false)); startActivity(intent); finish(); } + + private void showAgreement() { + Intent intent; + intent = new Intent(this, AgreementActivity.class); + startActivity(intent); + finish(); + } + + private void hideSystemUI() { + // Set the IMMERSIVE flag. + // Set the content to appear under the system bars so that the content + // doesn't resize when the system bars hide and show. + View mDecorView = getWindow().getDecorView(); + mDecorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar +// | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar + | View.SYSTEM_UI_FLAG_IMMERSIVE); + } + + protected void setStatusBarColor(int resId) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().setStatusBarColor(ContextCompat.getColor(this, resId)); + } + } } diff --git a/chat/src/main/java/cn/wildfire/chat/app/main/WorkspaceFragment.java b/chat/src/main/java/cn/wildfire/chat/app/main/WorkspaceFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..ee33498f7f5545890d6c822ae2b70571052839dd --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/main/WorkspaceFragment.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app.main; + +import android.content.Intent; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import cn.wildfire.chat.kit.workspace.JsApi; +import cn.wildfirechat.chat.R; + +public class WorkspaceFragment extends Fragment { + private String url; + private String htmlContent; + private JsApi jsApi; + + WebView webView; + + public static WorkspaceFragment loadUrl(String url) { + WorkspaceFragment fragment = new WorkspaceFragment(); + Bundle bundle = new Bundle(); + bundle.putString("url", url); + fragment.setArguments(bundle); + return fragment; + } + + public static WorkspaceFragment loadHtmlContent(String htmlContent) { + WorkspaceFragment fragment = new WorkspaceFragment(); + Bundle bundle = new Bundle(); + bundle.putString("htmlContent", htmlContent); + fragment.setArguments(bundle); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_webview, container, false); + bindViews(view); + init(); + return view; + } + + private void bindViews(View view) { + webView = view.findViewById(R.id.webview); + } + + private void init() { + Bundle bundle = getArguments(); + url = bundle.getString("url"); + htmlContent = bundle.getString("htmlContent"); + WebSettings settings = webView.getSettings(); + settings.setJavaScriptEnabled(true); + webView.setWebViewClient(new WebViewClient() { + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + jsApi.setCurrentUrl(url); + return super.shouldOverrideUrlLoading(view, url); + } + }); + + if (!TextUtils.isEmpty(htmlContent)) { + webView.loadDataWithBaseURL("", htmlContent, "text/html", "UTF-8", ""); + } else { + webView.loadUrl(url); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + } +} diff --git a/chat/src/main/java/cn/wildfire/chat/app/misc/DiagnoseActivity.java b/chat/src/main/java/cn/wildfire/chat/app/misc/DiagnoseActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..0501cbacfa6247f1118a732dc8723e768fb8a96b --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/misc/DiagnoseActivity.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app.misc; + +import android.text.TextUtils; +import android.widget.TextView; +import android.widget.Toast; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.Socket; +import java.util.Date; + +import cn.wildfire.chat.app.AppService; +import cn.wildfire.chat.app.MyApp; +import cn.wildfire.chat.kit.Config; +import cn.wildfire.chat.kit.WfcBaseActivity; +import cn.wildfire.chat.kit.net.OKHttpHelper; +import cn.wildfire.chat.kit.net.SimpleCallback; +import cn.wildfirechat.avenginekit.AVEngineKit; +import cn.wildfirechat.chat.R; +import cn.wildfirechat.remote.ChatManager; + +public class DiagnoseActivity extends WfcBaseActivity { + + private TextView configInfoTextView; + private TextView diagnoseResultTextView; + + private StringBuffer diagnoseResultSB; + + @Override + protected int contentLayout() { + return R.layout.activity_diagnose; + } + + protected void bindEvents() { + super.bindEvents(); + findViewById(R.id.startDiagnoseButton).setOnClickListener(v -> diagnose()); + } + + protected void bindViews() { + super.bindViews(); + configInfoTextView = findViewById(R.id.configInfoTextView); + diagnoseResultTextView = findViewById(R.id.resultTextView); + } + + @Override + protected void afterViews() { + super.afterViews(); + updateConfigInfo(); + } + + private void diagnose() { + Toast.makeText(this, R.string.diagnose_start, Toast.LENGTH_SHORT).show(); + diagnoseResultSB = new StringBuffer(); + checkAppServer(); + checkApiVersion(); + tcping(); + } + + private void updateConfigInfo() { + StringBuilder sb = new StringBuilder(); + sb.append(getString(R.string.diagnose_current_time, new Date().toString())).append("\n"); + sb.append(getString(R.string.diagnose_app_server, AppService.APP_SERVER_ADDRESS)).append("\n"); + sb.append(getString(R.string.diagnose_route_host, Config.IM_SERVER_HOST)).append("\n"); + sb.append(getString(R.string.diagnose_route_port, MyApp.routePort)).append("\n"); + sb.append(getString(R.string.diagnose_longlink_host, MyApp.longLinkHost)).append("\n"); + sb.append(getString(R.string.diagnose_longlink_port, ChatManager.Instance().getLongLinkPort())).append("\n"); + sb.append(getString(R.string.diagnose_av_sdk, + AVEngineKit.isSupportConference() ? + getString(R.string.diagnose_av_sdk_pro) : + getString(R.string.diagnose_av_sdk_basic))).append("\n"); + + String ices = ""; + for (String[] ice : Config.ICE_SERVERS) { + ices += ice[0] + " " + ice[1] + " " + ice[2] + "\n"; + } + sb.append(getString(R.string.diagnose_turnserver, ices)); + sb.append(getString(R.string.diagnose_proto_version, ChatManager.Instance().getProtoRevision())).append("\n"); + + configInfoTextView.setText(sb.toString()); + } + + private void updateDiagnoseResult() { + diagnoseResultTextView.setText(diagnoseResultSB.toString()); + } + + private void checkAppServer() { + OKHttpHelper.get(AppService.APP_SERVER_ADDRESS, null, new SimpleCallback() { + @Override + public void onUiSuccess(String s) { + if ("Ok".equals(s)) { + diagnoseResultSB.append(getString(R.string.diagnose_app_server_ok)).append("\n\n"); + updateDiagnoseResult(); + } else { + diagnoseResultSB.append(getString(R.string.diagnose_app_server_error, s)).append("\n\n"); + } + } + + @Override + public void onUiFailure(int code, String msg) { + diagnoseResultSB.append(getString(R.string.diagnose_app_server_error, code + " " + msg)).append("\n\n"); + updateDiagnoseResult(); + } + }); + } + + private void checkApiVersion() { + String url = "http://" + Config.IM_SERVER_HOST + ":" + MyApp.routePort + "/api/version"; + OKHttpHelper.get(url, null, new SimpleCallback() { + @Override + public void onUiSuccess(String s) { + try { + JSONObject json = new JSONObject(s); + diagnoseResultSB.append(getString(R.string.diagnose_im_server_ok)); + diagnoseResultSB.append(getString(R.string.diagnose_remote_origin, json.getString("remoteOriginUrl"))).append("\n"); + diagnoseResultSB.append(getString(R.string.diagnose_commit_message, json.getString("commitMessageShort"))).append("\n"); + diagnoseResultSB.append(getString(R.string.diagnose_commit_time, json.getString("commitTime"))).append("\n\n"); + updateDiagnoseResult(); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + @Override + public void onUiFailure(int code, String msg) { + diagnoseResultSB.append(getString(R.string.diagnose_im_server_error, code, msg)).append("\n"); + updateDiagnoseResult(); + } + }); + } + + private void tcping() { + if (TextUtils.isEmpty(MyApp.longLinkHost)) { + Toast.makeText(this, R.string.diagnose_longlink_empty, Toast.LENGTH_SHORT).show(); + return; + } + String host = MyApp.longLinkHost; + int port = ChatManager.Instance().getLongLinkPort(); + ChatManager.Instance().getWorkHandler().post(() -> { + try (Socket socket = new Socket(host, port)) { + diagnoseResultSB.append(getString(R.string.diagnose_tcp_ping_ok)).append("\n\n"); + ChatManager.Instance().getMainHandler().post(this::updateDiagnoseResult); + } catch (IOException e) { + diagnoseResultSB.append(getString(R.string.diagnose_tcp_ping_error, e.getMessage())).append("\n\n"); + ChatManager.Instance().getMainHandler().post(this::updateDiagnoseResult); + } + }); + } +} \ No newline at end of file diff --git a/chat/src/main/java/cn/wildfire/chat/app/misc/KeyStoreUtil.java b/chat/src/main/java/cn/wildfire/chat/app/misc/KeyStoreUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..0cc7630ec3bcc6925618d3b4a3a6cef7eaf194b1 --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/misc/KeyStoreUtil.java @@ -0,0 +1,133 @@ +package cn.wildfire.chat.app.misc; + +import android.content.Context; +import android.content.SharedPreferences; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; + +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Base64; + +import javax.crypto.Cipher; + +import cn.wildfire.chat.kit.Config; + +public class KeyStoreUtil { + // 密钥库类型 + private static final String PP_KEYSTORE_TYPE = "AndroidKeyStore"; + // 密钥库别名 + private static final String PP_KEYSTORE_ALIAS = "pp_keystore_alias"; + // 加密算法标准算法名称 + private static final String PP_TRANSFORMATION = "RSA/ECB/PKCS1Padding"; + + /** + * 触发生成密钥对. + *

+ * 生成RSA 密钥对,包括公钥和私钥 + * + * @return KeyPair 密钥对,包含公钥和私钥 + */ + private static KeyPair generateKey() throws Exception { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + // 创建密钥生成器 + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, PP_KEYSTORE_TYPE); + // 配置密钥生成器参数 + KeyGenParameterSpec builder = new KeyGenParameterSpec.Builder(PP_KEYSTORE_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) + .setDigests(KeyProperties.DIGEST_SHA256) + .build(); + + keyPairGenerator.initialize(builder); + // 生成密钥对 + return keyPairGenerator.generateKeyPair(); + } else { + return null; + } + } + + /** + * 获取公钥. + * + * @return 公钥 + */ + private static PublicKey getPublicKey() throws Exception { + KeyStore keyStore = KeyStore.getInstance(PP_KEYSTORE_TYPE); + keyStore.load(null); + // 判断密钥是否存在 + if (!keyStore.containsAlias(PP_KEYSTORE_ALIAS)) { + return generateKey().getPublic(); + } + KeyStore.Entry entry = keyStore.getEntry(PP_KEYSTORE_ALIAS, null); + if (!(entry instanceof KeyStore.PrivateKeyEntry)) { + return null; + } + return ((KeyStore.PrivateKeyEntry) entry).getCertificate().getPublicKey(); + } + + /** + * 获取私钥. + * + * @return 密钥 + */ + private static PrivateKey getPrivateKey() throws Exception { + KeyStore keyStore = KeyStore.getInstance(PP_KEYSTORE_TYPE); + keyStore.load(null); + // 判断密钥是否存在 + if (!keyStore.containsAlias(PP_KEYSTORE_ALIAS)) { + return generateKey().getPrivate(); + } + KeyStore.Entry entry = keyStore.getEntry(PP_KEYSTORE_ALIAS, null); + if (!(entry instanceof KeyStore.PrivateKeyEntry)) { + return null; + } + return ((KeyStore.PrivateKeyEntry) entry).getPrivateKey(); + } + + /** + * 加密保存数据 + * + * @param context 上下文 + * @param key 数据的Key + * @param data 数据 + */ + public static void saveData(Context context, String key, String data) throws Exception { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + byte[] bytes = data.getBytes(StandardCharsets.UTF_8); + PublicKey publicKey = getPublicKey(); + Cipher cipher = Cipher.getInstance(PP_TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + bytes = cipher.doFinal(bytes); + data = Base64.getEncoder().encodeToString(bytes); + } + SharedPreferences sharedPreferences = context.getSharedPreferences(Config.SP_CONFIG_FILE_NAME, Context.MODE_PRIVATE); + sharedPreferences.edit().putString(key, data).commit(); + } + + /** + * 获取保密数据 + * + * @param context 上下文 + * @param key 数据的Key + * @return 解密后的数据 + */ + public static String getData(Context context, String key) throws Exception { + SharedPreferences sharedPreferences = context.getSharedPreferences(Config.SP_CONFIG_FILE_NAME, Context.MODE_PRIVATE); + String data = sharedPreferences.getString(key, null); + if (data == null) { + return null; + } + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + PrivateKey privateKey = getPrivateKey(); + Cipher cipher = Cipher.getInstance(PP_TRANSFORMATION); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(data)); + data = new String(bytes, StandardCharsets.UTF_8); + } + return data; + } +} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/setting/AboutActivity.java b/chat/src/main/java/cn/wildfire/chat/app/setting/AboutActivity.java similarity index 36% rename from chat/src/main/java/cn/wildfire/chat/kit/setting/AboutActivity.java rename to chat/src/main/java/cn/wildfire/chat/app/setting/AboutActivity.java index a09e66d487769c72c60d0865fc7fa20321bee1f1..2099eec712892e2f55d715cfa505d19f885e9731 100644 --- a/chat/src/main/java/cn/wildfire/chat/kit/setting/AboutActivity.java +++ b/chat/src/main/java/cn/wildfire/chat/app/setting/AboutActivity.java @@ -1,21 +1,37 @@ -package cn.wildfire.chat.kit.setting; +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app.setting; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.widget.TextView; -import butterknife.Bind; -import butterknife.OnClick; -import cn.wildfire.chat.app.Config; +import cn.wildfire.chat.app.AppService; +import cn.wildfire.chat.kit.Config; import cn.wildfire.chat.kit.WfcBaseActivity; import cn.wildfire.chat.kit.WfcWebViewActivity; +import cn.wildfirechat.avenginekit.AVEngineKit; import cn.wildfirechat.chat.R; +import cn.wildfirechat.remote.ChatManager; public class AboutActivity extends WfcBaseActivity { - @Bind(R.id.infoTextView) TextView infoTextView; + protected void bindEvents() { + super.bindEvents(); + findViewById(R.id.introOptionItemView).setOnClickListener(v -> intro()); + findViewById(R.id.agreementOptionItemView).setOnClickListener(v -> agreement()); + findViewById(R.id.privacyOptionItemView).setOnClickListener(v -> privacy()); + } + + protected void bindViews() { + super.bindViews(); + infoTextView = findViewById(R.id.infoTextView); + } + @Override protected int contentLayout() { return R.layout.activity_about; @@ -27,10 +43,19 @@ public class AboutActivity extends WfcBaseActivity { try { PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), PackageManager.GET_CONFIGURATIONS); String info = packageInfo.packageName + "\n" - + packageInfo.versionCode + " " + packageInfo.versionName + "\n" - + Config.IM_SERVER_HOST + " " + Config.IM_SERVER_PORT + "\n" - + Config.APP_SERVER_HOST + " " + Config.APP_SERVER_PORT + "\n" - + Config.ICE_ADDRESS + " " + Config.ICE_USERNAME + " " + Config.ICE_PASSWORD + "\n"; + + packageInfo.versionCode + " " + packageInfo.versionName + "\n" + + ChatManager.Instance().getProtoRevision() + "\n" + + Config.IM_SERVER_HOST + "\n" + + AppService.APP_SERVER_ADDRESS + "\n"; + + if (AVEngineKit.isSupportConference()) { + info += "高级版音视频\n"; + } else { + info += "多人版版音视频\n"; + for (String[] ice : Config.ICE_SERVERS) { + info += ice[0] + " " + ice[1] + " " + ice[2] + "\n"; + } + } infoTextView.setText(info); @@ -39,18 +64,15 @@ public class AboutActivity extends WfcBaseActivity { } } - @OnClick(R.id.introOptionItemView) public void intro() { - WfcWebViewActivity.loadUrl(this, "野火IM功能介绍", "http://docs.wildfirechat.cn/"); + WfcWebViewActivity.loadUrl(this, getString(R.string.about_intro_title), getString(R.string.about_intro_url)); } - @OnClick(R.id.agreementOptionItemView) public void agreement() { - WfcWebViewActivity.loadUrl(this, "野火IM用户协议", "http://www.wildfirechat.cn/firechat_user_agreement.html"); + WfcWebViewActivity.loadUrl(this, getString(R.string.about_agreement_title), getString(R.string.about_agreement_url)); } - @OnClick(R.id.privacyOptionItemView) public void privacy() { - WfcWebViewActivity.loadUrl(this, "野火IM个人信息保护政策", "http://www.wildfirechat.cn/firechat_user_privacy.html"); + WfcWebViewActivity.loadUrl(this, getString(R.string.about_privacy_title), getString(R.string.about_privacy_url)); } } diff --git a/chat/src/main/java/cn/wildfire/chat/app/setting/AccountActivity.java b/chat/src/main/java/cn/wildfire/chat/app/setting/AccountActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..fe64d00a8ce6ed22a7f1c60e878f4e10ce5cd5b0 --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/setting/AccountActivity.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app.setting; + +import android.content.Intent; +import android.view.View; + +import com.afollestad.materialdialogs.MaterialDialog; + +import cn.wildfire.chat.kit.WfcBaseActivity; +import cn.wildfirechat.chat.R; + +public class AccountActivity extends WfcBaseActivity { + + @Override + protected int contentLayout() { + return R.layout.account_activity; + } + + protected void bindEvents() { + super.bindEvents(); + findViewById(R.id.changePasswordOptionItemView).setOnClickListener(v -> changePassword()); + } + + void changePassword() { + new MaterialDialog.Builder(this).items(R.array.change_password).itemsCallback(new MaterialDialog.ListCallback() { + @Override + public void onSelection(MaterialDialog dialog, View v, int position, CharSequence text) { + if (position == 0) { + Intent intent = new Intent(AccountActivity.this, ResetPasswordActivity.class); + startActivity(intent); + } else if (position == 1) { + Intent intent = new Intent(AccountActivity.this, ChangePasswordActivity.class); + startActivity(intent); + } + } + }).show(); + } +} diff --git a/chat/src/main/java/cn/wildfire/chat/app/setting/ChangePasswordActivity.java b/chat/src/main/java/cn/wildfire/chat/app/setting/ChangePasswordActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..5900abc30f1a36a63c399b3d8943a731862299a1 --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/setting/ChangePasswordActivity.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app.setting; + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.annotation.Nullable; + +import com.afollestad.materialdialogs.MaterialDialog; + +import cn.wildfire.chat.app.AppService; +import cn.wildfire.chat.kit.WfcBaseActivity; +import cn.wildfire.chat.kit.net.SimpleCallback; +import cn.wildfire.chat.kit.net.base.StatusResult; +import cn.wildfire.chat.kit.widget.SimpleTextWatcher; +import cn.wildfirechat.chat.R; + +public class ChangePasswordActivity extends WfcBaseActivity { + Button confirmButton; + EditText oldPasswordEditText; + EditText newPasswordEditText; + EditText confirmPasswordEditText; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + protected void bindEvents() { + super.bindEvents(); + findViewById(R.id.confirmButton).setOnClickListener(v -> resetPassword()); + oldPasswordEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + oldPassword(s); + } + }); + newPasswordEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + newPassword(s); + } + }); + confirmPasswordEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + confirmPassword(s); + } + }); + } + + protected void bindViews() { + super.bindViews(); + confirmButton = findViewById(R.id.confirmButton); + oldPasswordEditText = findViewById(R.id.oldPasswordEditText); + newPasswordEditText = findViewById(R.id.newPasswordEditText); + confirmPasswordEditText = findViewById(R.id.confirmPasswordEditText); + } + + @Override + protected int contentLayout() { + return R.layout.change_password_activity; + } + + @Override + protected void afterViews() { +// setStatusBarTheme(this, false); +// setStatusBarColor(R.color.gray14); + } + + void oldPassword(Editable editable) { + if (!TextUtils.isEmpty(newPasswordEditText.getText()) && !TextUtils.isEmpty(confirmPasswordEditText.getText()) && !TextUtils.isEmpty(editable)) { + confirmButton.setEnabled(true); + } else { + confirmButton.setEnabled(false); + } + } + + void newPassword(Editable editable) { + if (!TextUtils.isEmpty(oldPasswordEditText.getText()) && !TextUtils.isEmpty(confirmPasswordEditText.getText()) && !TextUtils.isEmpty(editable)) { + confirmButton.setEnabled(true); + } else { + confirmButton.setEnabled(false); + } + } + + void confirmPassword(Editable editable) { + if (!TextUtils.isEmpty(oldPasswordEditText.getText()) && !TextUtils.isEmpty(newPasswordEditText.getText()) && !TextUtils.isEmpty(editable)) { + confirmButton.setEnabled(true); + } else { + confirmButton.setEnabled(false); + } + } + + void resetPassword() { + String oldPassword = oldPasswordEditText.getText().toString().trim(); + String newPassword = newPasswordEditText.getText().toString().trim(); + String confirmPassword = confirmPasswordEditText.getText().toString().trim(); + if (!TextUtils.equals(newPassword, confirmPassword)) { + Toast.makeText(this, R.string.password_not_match, Toast.LENGTH_SHORT).show(); + return; + } + + MaterialDialog dialog = new MaterialDialog.Builder(this) + .content(R.string.password_changing) + .progress(true, 10) + .cancelable(false) + .build(); + dialog.show(); + + AppService.Instance().changePassword(oldPassword, newPassword, new SimpleCallback() { + @Override + public void onUiSuccess(StatusResult result) { + if (isFinishing()) { + return; + } + Toast.makeText(ChangePasswordActivity.this, R.string.password_change_success, Toast.LENGTH_SHORT).show(); + dialog.dismiss(); + finish(); + } + + @Override + public void onUiFailure(int code, String msg) { + if (isFinishing()) { + return; + } + dialog.dismiss(); + Toast.makeText(ChangePasswordActivity.this, getString(R.string.password_change_failed, code, msg), Toast.LENGTH_SHORT).show(); + } + }); + } +} diff --git a/chat/src/main/java/cn/wildfire/chat/app/setting/ResetPasswordActivity.java b/chat/src/main/java/cn/wildfire/chat/app/setting/ResetPasswordActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..3dc88e1b01d491a12ce274a978090bb369e3b83b --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/setting/ResetPasswordActivity.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app.setting; + +import android.os.Handler; +import android.text.Editable; +import android.text.TextUtils; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.afollestad.materialdialogs.MaterialDialog; + +import cn.wildfire.chat.app.AppService; +import cn.wildfire.chat.kit.WfcBaseActivity; +import cn.wildfire.chat.kit.net.SimpleCallback; +import cn.wildfire.chat.kit.net.base.StatusResult; +import cn.wildfire.chat.kit.widget.SimpleTextWatcher; +import cn.wildfirechat.chat.R; + +public class ResetPasswordActivity extends WfcBaseActivity { + Button confirmButton; + + EditText authCodeEditText; + EditText newPasswordEditText; + EditText confirmPasswordEditText; + + TextView requestAuthCodeButton; + + FrameLayout authCodeFrameLayout; + + private String resetCode; + + protected void bindEvents() { + super.bindEvents(); + requestAuthCodeButton.setOnClickListener(v -> requestAuthCode()); + confirmButton.setOnClickListener(v -> resetPassword()); + authCodeEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + authCode(s); + } + }); + newPasswordEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + newPassword(s); + } + }); + confirmPasswordEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + confirmPassword(s); + } + }); + } + + protected void bindViews() { + super.bindViews(); + confirmButton = findViewById(R.id.confirmButton); + authCodeEditText = findViewById(R.id.authCodeEditText); + newPasswordEditText = findViewById(R.id.newPasswordEditText); + confirmPasswordEditText = findViewById(R.id.confirmPasswordEditText); + requestAuthCodeButton = findViewById(R.id.requestAuthCodeButton); + authCodeFrameLayout = findViewById(R.id.authCodeFrameLayout); + } + + @Override + protected int contentLayout() { + return R.layout.reset_password_activity; + } + + @Override + protected void afterViews() { + resetCode = getIntent().getStringExtra("resetCode"); + if (!TextUtils.isEmpty(resetCode)) { + authCodeFrameLayout.setVisibility(View.GONE); + } + } + + void authCode(Editable editable) { + if (!TextUtils.isEmpty(newPasswordEditText.getText()) && !TextUtils.isEmpty(confirmPasswordEditText.getText()) && !TextUtils.isEmpty(editable)) { + confirmButton.setEnabled(true); + } else { + confirmButton.setEnabled(false); + } + } + + void newPassword(Editable editable) { + if ((!TextUtils.isEmpty(authCodeEditText.getText()) || !TextUtils.isEmpty(resetCode)) && !TextUtils.isEmpty(confirmPasswordEditText.getText()) && !TextUtils.isEmpty(editable)) { + confirmButton.setEnabled(true); + } else { + confirmButton.setEnabled(false); + } + } + + void confirmPassword(Editable editable) { + if ((!TextUtils.isEmpty(authCodeEditText.getText()) || !TextUtils.isEmpty(resetCode)) && !TextUtils.isEmpty(newPasswordEditText.getText()) && !TextUtils.isEmpty(editable)) { + confirmButton.setEnabled(true); + } else { + confirmButton.setEnabled(false); + } + } + + private Handler handler = new Handler(); + + void requestAuthCode() { + requestAuthCodeButton.setEnabled(false); + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (!isFinishing()) { + requestAuthCodeButton.setEnabled(true); + } + } + }, 60 * 1000); + + Toast.makeText(this, R.string.requesting_reset_code, Toast.LENGTH_SHORT).show(); + + AppService.Instance().requestResetAuthCode(null, new AppService.SendCodeCallback() { + @Override + public void onUiSuccess() { + Toast.makeText(ResetPasswordActivity.this, R.string.reset_code_send_success, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onUiFailure(int code, String msg) { + Toast.makeText(ResetPasswordActivity.this, getString(R.string.reset_code_send_failure, code, msg), Toast.LENGTH_SHORT).show(); + } + }); + } + + void resetPassword() { + String newPassword = newPasswordEditText.getText().toString().trim(); + String confirmPassword = confirmPasswordEditText.getText().toString().trim(); + if (!TextUtils.equals(newPassword, confirmPassword)) { + Toast.makeText(this, R.string.password_not_match, Toast.LENGTH_SHORT).show(); + return; + } + + MaterialDialog dialog = new MaterialDialog.Builder(this) + .content(R.string.reset_password_progress) + .progress(true, 10) + .cancelable(false) + .build(); + dialog.show(); + + String code = TextUtils.isEmpty(resetCode) ? authCodeEditText.getText().toString() : resetCode; + + AppService.Instance().resetPassword(null, code, newPassword, new SimpleCallback() { + @Override + public void onUiSuccess(StatusResult result) { + if (isFinishing()) { + return; + } + Toast.makeText(ResetPasswordActivity.this, R.string.reset_password_success, Toast.LENGTH_SHORT).show(); + dialog.dismiss(); + finish(); + } + + @Override + public void onUiFailure(int code, String msg) { + if (isFinishing()) { + return; + } + dialog.dismiss(); + Toast.makeText(ResetPasswordActivity.this, getString(R.string.reset_password_failure, code, msg), Toast.LENGTH_SHORT).show(); + } + }); + } +} diff --git a/chat/src/main/java/cn/wildfire/chat/app/setting/SettingActivity.java b/chat/src/main/java/cn/wildfire/chat/app/setting/SettingActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..42fa5e55733fd572779169499998bd118248d2cc --- /dev/null +++ b/chat/src/main/java/cn/wildfire/chat/app/setting/SettingActivity.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2020 WildFireChat. All rights reserved. + */ + +package cn.wildfire.chat.app.setting; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Build; +import android.os.PowerManager; +import android.provider.Settings; +import android.webkit.CookieManager; +import android.webkit.WebStorage; +import android.widget.Toast; + +import androidx.annotation.Nullable; + +import cn.wildfire.chat.app.AppService; +import cn.wildfire.chat.app.main.SplashActivity; +import cn.wildfire.chat.app.misc.DiagnoseActivity; +import cn.wildfire.chat.kit.ChatManagerHolder; +import cn.wildfire.chat.kit.Config; +import cn.wildfire.chat.kit.WfcBaseActivity; +import cn.wildfire.chat.kit.net.OKHttpHelper; +import cn.wildfire.chat.kit.net.SimpleCallback; +import cn.wildfire.chat.kit.settings.PrivacySettingActivity; +import cn.wildfire.chat.kit.widget.OptionItemView; +import cn.wildfirechat.chat.R; + +public class SettingActivity extends WfcBaseActivity { + private final int REQUEST_IGNORE_BATTERY_CODE = 100; + OptionItemView diagnoseOptionItemView; + + + protected void bindEvents() { + super.bindEvents(); + findViewById(R.id.exitOptionItemView).setOnClickListener(v -> exit()); + findViewById(R.id.privacySettingOptionItemView).setOnClickListener(v -> privacySetting()); + findViewById(R.id.diagnoseOptionItemView).setOnClickListener(v -> diagnose()); + findViewById(R.id.uploadLogOptionItemView).setOnClickListener(v -> uploadLog()); + findViewById(R.id.batteryOptionItemView).setOnClickListener(v -> batteryOptimize()); + findViewById(R.id.aboutOptionItemView).setOnClickListener(v -> about()); + } + + protected void bindViews() { + super.bindViews(); + diagnoseOptionItemView = findViewById(R.id.diagnoseOptionItemView); + } + + @Override + protected int contentLayout() { + return R.layout.setting_activity; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + switch (requestCode) { + case REQUEST_IGNORE_BATTERY_CODE: + if (resultCode == RESULT_CANCELED) { + Toast.makeText(this, R.string.battery_optimize_tip, Toast.LENGTH_SHORT).show(); + } + break; + default: + super.onActivityResult(requestCode, resultCode, data); + break; + } + } + + void exit() { + //不要清除session,这样再次登录时能够保留历史记录。如果需要清除掉本地历史记录和服务器信息这里使用true + ChatManagerHolder.gChatManager.disconnect(true, false); + SharedPreferences sp = getSharedPreferences(Config.SP_CONFIG_FILE_NAME, Context.MODE_PRIVATE); + sp.edit() + .clear() + .putBoolean("hasReadUserAgreement", true) + .apply(); + + sp = getSharedPreferences("moment", Context.MODE_PRIVATE); + sp.edit().clear().apply(); + + OKHttpHelper.clearCookies(); + + WebStorage.getInstance().deleteAllData(); + CookieManager.getInstance().removeAllCookies(null); + CookieManager.getInstance().flush(); + + Intent intent = new Intent(this, SplashActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finish(); + } + + void privacySetting() { + Intent intent = new Intent(this, PrivacySettingActivity.class); + startActivity(intent); + } + + void diagnose() { + Intent intent = new Intent(this, DiagnoseActivity.class); + startActivity(intent); + } + + void uploadLog() { + AppService.Instance().uploadLog(new SimpleCallback() { + @Override + public void onUiSuccess(String path) { + if (!isFinishing()) { + Toast.makeText(SettingActivity.this, getString(R.string.upload_log_success, path), Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onUiFailure(int code, String msg) { + if (!isFinishing()) { + Toast.makeText(SettingActivity.this, getString(R.string.upload_log_failed, code, msg), Toast.LENGTH_SHORT).show(); + } + } + }); + } + + @SuppressLint("BatteryLife") + void batteryOptimize() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + Intent intent = new Intent(); + String packageName = getPackageName(); + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + if (!pm.isIgnoringBatteryOptimizations(packageName)) { + intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.parse("package:" + packageName)); + startActivityForResult(intent, REQUEST_IGNORE_BATTERY_CODE); + } else { + Toast.makeText(this, R.string.battery_optimize_allowed, Toast.LENGTH_SHORT).show(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } else { + Toast.makeText(this, R.string.system_version_not_support, Toast.LENGTH_SHORT).show(); + } + } + + void about() { + Intent intent = new Intent(this, AboutActivity.class); + startActivity(intent); + } +} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/ConfigEventViewModel.java b/chat/src/main/java/cn/wildfire/chat/kit/ConfigEventViewModel.java deleted file mode 100644 index 7883c9830bb4139385244cd274d66a99b30c18a1..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/ConfigEventViewModel.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.wildfire.chat.kit; - -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; - -import cn.wildfire.chat.kit.common.AppScopeViewModel; - -public class ConfigEventViewModel extends ViewModel implements AppScopeViewModel { - private MutableLiveData> showGroupAliasLiveData; - - public MutableLiveData> showGroupAliasLiveData() { - if (showGroupAliasLiveData == null) { - showGroupAliasLiveData = new MutableLiveData<>(); - } - return showGroupAliasLiveData; - } - - public void postGroupAliasEvent(String groupId, boolean show) { - if (showGroupAliasLiveData != null) { - showGroupAliasLiveData.setValue(new Event<>(show)); - } - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/WfcGlideModule.java b/chat/src/main/java/cn/wildfire/chat/kit/WfcGlideModule.java deleted file mode 100644 index 4b800134b9f6627a6326bdf9e2f1e8d57ce8ba14..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/WfcGlideModule.java +++ /dev/null @@ -1,9 +0,0 @@ -package cn.wildfire.chat.kit; - -import com.bumptech.glide.annotation.GlideModule; -import com.bumptech.glide.module.AppGlideModule; - -@GlideModule -public final class WfcGlideModule extends AppGlideModule { -} - diff --git a/chat/src/main/java/cn/wildfire/chat/kit/WfcIntent.java b/chat/src/main/java/cn/wildfire/chat/kit/WfcIntent.java deleted file mode 100644 index d1b9044680e2715f5d7eb713ceb92f91da5d504a..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/WfcIntent.java +++ /dev/null @@ -1,10 +0,0 @@ -package cn.wildfire.chat.kit; - -public interface WfcIntent { - String ACTION_MAIN = "cn.wildfirechat.chat.main"; - String ACTION_CONVERSATION = "cn.wildfirechat.chat.conversation"; - String ACTION_CONTACT = "cn.wildfirechat.chat.contact"; - String ACTION_USER_INFO = "cn.wildfirechat.chat.user.info"; - String ACTION_GROUP_INFO = "cn.wildfirechat.chat.group.info"; - String ACTION_VOIP_SINGLE = "cn.wildfirechat.kit.chat.voip.single"; -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/WfcUIKit.java b/chat/src/main/java/cn/wildfire/chat/kit/WfcUIKit.java deleted file mode 100644 index 704b185e29bb05d4067040f54d92b1a8617e8d9b..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/WfcUIKit.java +++ /dev/null @@ -1,192 +0,0 @@ -package cn.wildfire.chat.kit; - -import android.app.Activity; -import android.app.Application; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.media.AudioManager; -import android.net.Uri; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleObserver; -import androidx.lifecycle.OnLifecycleEvent; -import androidx.lifecycle.ProcessLifecycleOwner; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; -import androidx.lifecycle.ViewModelStore; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.request.RequestOptions; -import com.lqr.emoji.LQREmotionKit; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import cn.wildfire.chat.app.Config; -import cn.wildfire.chat.kit.common.AppScopeViewModel; -import cn.wildfire.chat.kit.voip.AsyncPlayer; -import cn.wildfire.chat.kit.voip.SingleVoipCallActivity; -import cn.wildfirechat.avenginekit.AVEngineKit; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.client.NotInitializedExecption; -import cn.wildfirechat.message.Message; -import cn.wildfirechat.message.core.PersistFlag; -import cn.wildfirechat.push.PushService; -import cn.wildfirechat.remote.ChatManager; -import cn.wildfirechat.remote.OnRecallMessageListener; -import cn.wildfirechat.remote.OnReceiveMessageListener; - - -public class WfcUIKit implements AVEngineKit.AVEngineCallback, OnReceiveMessageListener, OnRecallMessageListener { - - private boolean isBackground = true; - private static Application application; - private static ViewModelProvider viewModelProvider; - private ViewModelStore viewModelStore; - - public void init(Application application) { - WfcUIKit.application = application; - initWFClient(application); - //初始化表情控件 - LQREmotionKit.init(application, (context, path, imageView) -> Glide.with(context).load(path).apply(new RequestOptions().centerCrop().diskCacheStrategy(DiskCacheStrategy.RESOURCE).dontAnimate()).into(imageView)); - - ProcessLifecycleOwner.get().getLifecycle().addObserver(new LifecycleObserver() { - @OnLifecycleEvent(Lifecycle.Event.ON_START) - public void onForeground() { - PushService.clearNotification(application); - WfcNotificationManager.getInstance().clearAllNotification(application); - isBackground = false; - } - - @OnLifecycleEvent(Lifecycle.Event.ON_STOP) - public void onBackground() { - isBackground = true; - viewModelStore.clear(); - } - }); - - viewModelStore = new ViewModelStore(); - ViewModelProvider.Factory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application); - viewModelProvider = new ViewModelProvider(viewModelStore, factory); - } - - private void initWFClient(Application application) { - ChatManager.init(application, Config.IM_SERVER_HOST, Config.IM_SERVER_PORT); - try { - ChatManagerHolder.gChatManager = ChatManager.Instance(); - ChatManagerHolder.gChatManager.startLog(); - ChatManagerHolder.gChatManager.addOnReceiveMessageListener(this); - ChatManagerHolder.gChatManager.addRecallMessageListener(this); - PushService.init(application); - - ringPlayer = new AsyncPlayer(null); - AVEngineKit.init(application, this); - ChatManagerHolder.gAVEngine = AVEngineKit.Instance(); - ChatManagerHolder.gAVEngine.addIceServer(Config.ICE_ADDRESS, Config.ICE_USERNAME, Config.ICE_PASSWORD); - - SharedPreferences sp = application.getSharedPreferences("config", Context.MODE_PRIVATE); - String id = sp.getString("id", null); - String token = sp.getString("token", null); - if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(token)) { - ChatManagerHolder.gChatManager.connect(id, token); - } - } catch (NotInitializedExecption notInitializedExecption) { - notInitializedExecption.printStackTrace(); - } - } - - /** - * 当{@link androidx.lifecycle.ViewModel} 需要跨{@link android.app.Activity} 共享数据时使用 - */ - public static T getAppScopeViewModel(@NonNull Class modelClass) { - if (!AppScopeViewModel.class.isAssignableFrom(modelClass)) { - throw new IllegalArgumentException("the model class should be subclass of AppScopeViewModel"); - } - return viewModelProvider.get(modelClass); - } - - @Override - public void onReceiveCall(AVEngineKit.CallSession session) { - onCall(application, session.getClientId(), false, session.isAudioOnly()); - } - - private AsyncPlayer ringPlayer; - - @Override - public void shouldStartRing(boolean isIncomming) { - if (isIncomming) { - Uri uri = Uri.parse("android.resource://" + application.getPackageName() + "/" + R.raw.incoming_call_ring); - ringPlayer.play(application, uri, true, AudioManager.STREAM_RING); - } else { - Uri uri = Uri.parse("android.resource://" + application.getPackageName() + "/" + R.raw.outgoing_call_ring); - ringPlayer.play(application, uri, true, AudioManager.STREAM_RING); - } - } - - @Override - public void shouldSopRing() { - ringPlayer.stop(); - } - - // pls refer to https://stackoverflow.com/questions/11124119/android-starting-new-activity-from-application-class - public static void onCall(Context context, String targetId, boolean isMo, boolean isAudioOnly) { - Intent voip = new Intent(WfcIntent.ACTION_VOIP_SINGLE); - voip.putExtra(SingleVoipCallActivity.EXTRA_MO, isMo); - voip.putExtra(SingleVoipCallActivity.EXTRA_TARGET, targetId); - voip.putExtra(SingleVoipCallActivity.EXTRA_AUDIO_ONLY, isAudioOnly); - - if (context instanceof Activity) { - context.startActivity(voip); - } else { - Intent main = new Intent(WfcIntent.ACTION_MAIN); - voip.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PendingIntent pendingIntent = PendingIntent.getActivities(context, 100, new Intent[]{main, voip}, 0); - try { - pendingIntent.send(); - } catch (PendingIntent.CanceledException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - - @Override - public void onReceiveMessage(List messages, boolean hasMore) { - if (isBackground) { - // FIXME: 2018/5/28 只是临时方案,No_Persist消息,我觉得不应当到这儿,注册监听时, - // 就表明自己关系哪些类型的消息, 设置哪些种类的消息 - - if (messages == null) { - return; - } - - List msgs = new ArrayList<>(messages); - long now = System.currentTimeMillis(); - long delta = ChatManager.Instance().getServerDeltaTime(); - Iterator iterator = msgs.iterator(); - while (iterator.hasNext()) { - Message message = iterator.next(); - if (message.content.getPersistFlag() == PersistFlag.No_Persist - || now - (message.serverTime - delta) > 10 * 1000) { - iterator.remove(); - } - } - WfcNotificationManager.getInstance().handleReceiveMessage(application, msgs); - } else { - // do nothing - } - } - - @Override - public void onRecallMessage(Message message) { - if (isBackground) { - WfcNotificationManager.getInstance().handleRecallMessage(application, message); - } - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/WfcWebViewActivity.java b/chat/src/main/java/cn/wildfire/chat/kit/WfcWebViewActivity.java deleted file mode 100644 index fa3cd10f958ecef4afde335c37ed2e318df8ef81..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/WfcWebViewActivity.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.wildfire.chat.kit; - -import android.content.Context; -import android.content.Intent; -import android.text.TextUtils; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -import butterknife.Bind; -import cn.wildfirechat.chat.R; - -public class WfcWebViewActivity extends WfcBaseActivity { - private String url; - - @Bind(R.id.webview) - WebView webView; - - public static void loadUrl(Context context, String title, String url) { - Intent intent = new Intent(context, WfcWebViewActivity.class); - intent.putExtra("url", url); - intent.putExtra("title", title); - context.startActivity(intent); - } - - @Override - protected int contentLayout() { - return R.layout.activity_webview; - } - - @Override - protected void afterViews() { - url = getIntent().getStringExtra("url"); - webView.loadUrl(url); - String title = getIntent().getStringExtra("title"); - if (TextUtils.isEmpty(title)) { - webView.setWebViewClient(new WebViewClient() { - @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - String title = view.getTitle(); - if (!TextUtils.isEmpty(title)) { - setTitle(title); - } - } - }); - } else { - setTitle(title); - } - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/contact/ContactFragment.java b/chat/src/main/java/cn/wildfire/chat/kit/contact/ContactFragment.java deleted file mode 100644 index b544e836ece68b47f34abcefe96a51fc7f1827ea..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/contact/ContactFragment.java +++ /dev/null @@ -1,154 +0,0 @@ -package cn.wildfire.chat.kit.contact; - -import android.content.Intent; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.Observer; - -import java.util.List; - -import cn.wildfire.chat.app.main.MainActivity; -import cn.wildfire.chat.kit.IMServiceStatusViewModel; -import cn.wildfire.chat.kit.WfcUIKit; -import cn.wildfire.chat.kit.channel.ChannelListActivity; -import cn.wildfire.chat.kit.contact.model.ContactCountFooterValue; -import cn.wildfire.chat.kit.contact.model.FriendRequestValue; -import cn.wildfire.chat.kit.contact.model.GroupValue; -import cn.wildfire.chat.kit.contact.model.HeaderValue; -import cn.wildfire.chat.kit.contact.model.UIUserInfo; -import cn.wildfire.chat.kit.contact.newfriend.FriendRequestListActivity; -import cn.wildfire.chat.kit.contact.viewholder.footer.ContactCountViewHolder; -import cn.wildfire.chat.kit.contact.viewholder.header.ChannelViewHolder; -import cn.wildfire.chat.kit.contact.viewholder.header.FriendRequestViewHolder; -import cn.wildfire.chat.kit.contact.viewholder.header.GroupViewHolder; -import cn.wildfire.chat.kit.group.GroupListActivity; -import cn.wildfire.chat.kit.user.UserInfoActivity; -import cn.wildfire.chat.kit.user.UserViewModel; -import cn.wildfire.chat.kit.widget.QuickIndexBar; -import cn.wildfirechat.model.UserInfo; - -public class ContactFragment extends BaseContactFragment implements QuickIndexBar.OnLetterUpdateListener { - private UserViewModel userViewModel; - private IMServiceStatusViewModel imServiceStatusViewModel; - - private Observer friendRequestUpdateLiveDataObserver = count -> { - FriendRequestValue requestValue = new FriendRequestValue(count == null ? 0 : count); - contactAdapter.updateHeader(0, requestValue); - }; - - private Observer contactListUpdateLiveDataObserver = o -> { - loadContacts(); - }; - - private Observer imStatusLiveDataObserver = status -> { - if (status && (contactAdapter != null && (contactAdapter.contacts == null || contactAdapter.contacts.size() == 0))) { - loadContacts(); - } - }; - - private void loadContacts() { - contactViewModel.getContactsAsync(false) - .observe(this, userInfos -> { - if (userInfos == null || userInfos.isEmpty()) { - return; - } - contactAdapter.setContacts(userInfoToUIUserInfo(userInfos)); - contactAdapter.notifyDataSetChanged(); - - for (UserInfo info : userInfos) { - if (info.name == null || info.displayName == null) { - userViewModel.getUserInfo(info.uid, true); - } - } - }); - } - - private Observer> userInfoLiveDataObserver = userInfos -> { - contactAdapter.updateContacts(userInfoToUIUserInfo(userInfos)); - }; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = super.onCreateView(inflater, container, savedInstanceState); - - contactViewModel.contactListUpdatedLiveData().observeForever(contactListUpdateLiveDataObserver); - contactViewModel.friendRequestUpdatedLiveData().observeForever(friendRequestUpdateLiveDataObserver); - - userViewModel = WfcUIKit.getAppScopeViewModel(UserViewModel.class); - userViewModel.userInfoLiveData().observeForever(userInfoLiveDataObserver); - imServiceStatusViewModel = WfcUIKit.getAppScopeViewModel(IMServiceStatusViewModel.class); - imServiceStatusViewModel.imServiceStatusLiveData().observeForever(imStatusLiveDataObserver); - return view; - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - contactViewModel.contactListUpdatedLiveData().removeObserver(contactListUpdateLiveDataObserver); - contactViewModel.friendRequestUpdatedLiveData().removeObserver(friendRequestUpdateLiveDataObserver); - userViewModel.userInfoLiveData().removeObserver(userInfoLiveDataObserver); - imServiceStatusViewModel.imServiceStatusLiveData().removeObserver(imStatusLiveDataObserver); - } - - @Override - public void initHeaderViewHolders() { - addHeaderViewHolder(FriendRequestViewHolder.class, new FriendRequestValue(contactViewModel.getUnreadFriendRequestCount())); - addHeaderViewHolder(GroupViewHolder.class, new GroupValue()); - addHeaderViewHolder(ChannelViewHolder.class, new HeaderValue()); - } - - @Override - public void initFooterViewHolders() { - addFooterViewHolder(ContactCountViewHolder.class, new ContactCountFooterValue()); - } - - @Override - public void onContactClick(UIUserInfo userInfo) { - Intent intent = new Intent(getActivity(), UserInfoActivity.class); - intent.putExtra("userInfo", userInfo.getUserInfo()); - startActivity(intent); - } - - @Override - public void onHeaderClick(int index) { - switch (index) { - case 0: - ((MainActivity) getActivity()).hideUnreadFriendRequestBadgeView(); - showFriendRequest(); - break; - case 1: - showGroupList(); - break; - case 2: - showChannelList(); - break; - default: - break; - } - } - - private void showFriendRequest() { - FriendRequestValue value = new FriendRequestValue(0); - contactAdapter.updateHeader(0, value); - - contactViewModel.clearUnreadFriendRequestStatus(); - Intent intent = new Intent(getActivity(), FriendRequestListActivity.class); - startActivity(intent); - } - - private void showGroupList() { - Intent intent = new Intent(getActivity(), GroupListActivity.class); - startActivity(intent); - } - - private void showChannelList() { - Intent intent = new Intent(getActivity(), ChannelListActivity.class); - startActivity(intent); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/contact/ContactViewModel.java b/chat/src/main/java/cn/wildfire/chat/kit/contact/ContactViewModel.java deleted file mode 100644 index 3a9893d0aed28e770b3820715faad53a851e741f..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/contact/ContactViewModel.java +++ /dev/null @@ -1,154 +0,0 @@ -package cn.wildfire.chat.kit.contact; - -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; - -import java.util.List; - -import cn.wildfirechat.model.FriendRequest; -import cn.wildfirechat.model.UserInfo; -import cn.wildfirechat.remote.ChatManager; -import cn.wildfirechat.remote.GeneralCallback; -import cn.wildfirechat.remote.OnFriendUpdateListener; -import cn.wildfirechat.remote.SearchUserCallback; - -public class ContactViewModel extends ViewModel implements OnFriendUpdateListener { - private MutableLiveData contactListUpdatedLiveData; - private MutableLiveData friendRequestUpdatedLiveData; - - public ContactViewModel() { - super(); - ChatManager.Instance().addFriendUpdateListener(this); - } - - @Override - protected void onCleared() { - super.onCleared(); - ChatManager.Instance().removeFriendUpdateListener(this); - } - - public MutableLiveData contactListUpdatedLiveData() { - if (contactListUpdatedLiveData == null) { - contactListUpdatedLiveData = new MutableLiveData<>(); - } - return contactListUpdatedLiveData; - } - - public MutableLiveData friendRequestUpdatedLiveData() { - if (friendRequestUpdatedLiveData == null) { - friendRequestUpdatedLiveData = new MutableLiveData<>(); - } - return friendRequestUpdatedLiveData; - } - - public List getFriends(boolean refresh) { - return ChatManager.Instance().getMyFriendList(refresh); - } - - public LiveData> getContactsAsync(boolean refresh) { - MutableLiveData> data = new MutableLiveData<>(); - ChatManager.Instance().getWorkHandler().post(() -> { - List userInfos = ChatManager.Instance().getMyFriendListInfo(refresh); - data.postValue(userInfos); - }); - return data; - } - - public List getContacts(boolean refresh) { - return ChatManager.Instance().getMyFriendListInfo(refresh); - } - - public List getContacts(List ids) { - return ChatManager.Instance().getUserInfos(ids); - } - - public int getUnreadFriendRequestCount() { - return ChatManager.Instance().getUnreadFriendRequestStatus(); - } - - public void clearUnreadFriendRequestStatus() { - ChatManager.Instance().clearUnreadFriendRequestStatus(); - } - - @Override - public void onFriendListUpdate() { - if (contactListUpdatedLiveData != null) { - contactListUpdatedLiveData.setValue(new Object()); - } - } - - @Override - public void onFriendRequestUpdate() { - if (friendRequestUpdatedLiveData != null) { - friendRequestUpdatedLiveData.setValue(getUnreadFriendRequestCount()); - } - } - - public List getFriendRequest() { - return ChatManager.Instance().getFriendRequest(true); - } - - public MutableLiveData acceptFriendRequest(String friendId) { - - MutableLiveData result = new MutableLiveData<>(); - ChatManager.Instance().handleFriendRequest(friendId, true, new GeneralCallback() { - @Override - public void onSuccess() { - ChatManager.Instance().loadFriendRequestFromRemote(); - List inComingFriendRequests = ChatManager.Instance().getFriendRequest(true); - for (FriendRequest request : inComingFriendRequests) { - if (request.target.equals(friendId)) { - result.setValue(true); - return; - } - } - result.setValue(false); - } - - @Override - public void onFail(int errorCode) { - result.setValue(false); - } - }); - return result; - } - - public MutableLiveData> searchUser(String keyword) { - MutableLiveData> result = new MutableLiveData<>(); - ChatManager.Instance().searchUser(keyword, new SearchUserCallback() { - @Override - public void onSuccess(List userInfos) { - result.setValue(userInfos); - } - - @Override - public void onFail(int errorCode) { - result.setValue(null); - } - }); - - return result; - } - - public boolean isFriend(String targetUid) { - return ChatManager.Instance().isMyFriend(targetUid); - } - - public MutableLiveData invite(String targetUid, String message) { - MutableLiveData result = new MutableLiveData<>(); - ChatManager.Instance().sendFriendRequest(targetUid, message, new GeneralCallback() { - @Override - public void onSuccess() { - result.setValue(true); - } - - @Override - public void onFail(int errorCode) { - result.setValue(false); - } - }); - return result; - } - -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/contact/model/FooterValue.java b/chat/src/main/java/cn/wildfire/chat/kit/contact/model/FooterValue.java deleted file mode 100644 index 60ac6192ede54da430e75d763682346bb689ebf7..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/contact/model/FooterValue.java +++ /dev/null @@ -1,4 +0,0 @@ -package cn.wildfire.chat.kit.contact.model; - -public class FooterValue { -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/contact/model/HeaderValue.java b/chat/src/main/java/cn/wildfire/chat/kit/contact/model/HeaderValue.java deleted file mode 100644 index aaa80e48c0c222243d3bca29e079dcc18cc18678..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/contact/model/HeaderValue.java +++ /dev/null @@ -1,4 +0,0 @@ -package cn.wildfire.chat.kit.contact.model; - -public class HeaderValue { -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/contact/newfriend/SearchUserActivity.java b/chat/src/main/java/cn/wildfire/chat/kit/contact/newfriend/SearchUserActivity.java deleted file mode 100644 index 708c17311933154d825d481b4b8b9fefc68c4c70..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/contact/newfriend/SearchUserActivity.java +++ /dev/null @@ -1,70 +0,0 @@ -package cn.wildfire.chat.kit.contact.newfriend; - -import android.text.TextUtils; -import android.view.Menu; -import android.view.MenuItem; - -import androidx.appcompat.widget.SearchView; -import cn.wildfire.chat.kit.WfcBaseActivity; -import cn.wildfirechat.chat.R; - -public class SearchUserActivity extends WfcBaseActivity { - - private SearchView searchView; - private SearchUserFragment searchUserFragment; - - @Override - protected int contentLayout() { - return R.layout.fragment_container_activity; - } - - @Override - protected int menu() { - return R.menu.search_user; - } - - @Override - protected void afterMenus(Menu menu) { - super.afterMenus(menu); - MenuItem searchItem = menu.findItem(R.id.search); - //通过MenuItem得到SearchView - searchView = (SearchView) searchItem.getActionView(); - initSearchView(); - } - - private void initSearchView() { - searchView.onActionViewExpanded(); - searchView.setQueryHint("Search"); - - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - return false; - } - - @Override - public boolean onQueryTextChange(String s) { - search(s); - return true; - } - }); - - } - - @Override - protected void afterViews() { - searchUserFragment = new SearchUserFragment(); - getSupportFragmentManager().beginTransaction() - .replace(R.id.containerFrameLayout, searchUserFragment) - .commit(); - } - - private void search(String keyword) { - if (!TextUtils.isEmpty(keyword)) { - searchUserFragment.showSearchPromptView(keyword); - } else { - searchUserFragment.hideSearchPromptView(); - } - } - -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/contact/newfriend/SearchUserFragment.java b/chat/src/main/java/cn/wildfire/chat/kit/contact/newfriend/SearchUserFragment.java deleted file mode 100644 index 82a6093b8c96b9db10aab79abce91e44a4c73a88..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/contact/newfriend/SearchUserFragment.java +++ /dev/null @@ -1,81 +0,0 @@ -package cn.wildfire.chat.kit.contact.newfriend; - -import android.content.Intent; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProviders; -import butterknife.Bind; -import butterknife.ButterKnife; -import butterknife.OnClick; -import cn.wildfire.chat.kit.contact.ContactViewModel; -import cn.wildfire.chat.kit.user.UserInfoActivity; -import cn.wildfirechat.chat.R; - -public class SearchUserFragment extends Fragment { - private String keyword; - private ContactViewModel contactViewModel; - - @Bind(R.id.noUserRelativeLayout) - RelativeLayout noUserRelativeLayout; - @Bind(R.id.searchLinearLayout) - LinearLayout searchLinearLayout; - @Bind(R.id.keywordTextView) - TextView keywordTextView; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = LayoutInflater.from(container.getContext()).inflate(R.layout.contact_search_user_fragment, container, false); - ButterKnife.bind(this, view); - init(); - return view; - } - - public void showSearchPromptView(String keyword) { - if (TextUtils.isEmpty(keyword)) { - return; - } - this.keyword = keyword; - searchLinearLayout.setVisibility(View.VISIBLE); - noUserRelativeLayout.setVisibility(View.GONE); - keywordTextView.setText(keyword); - } - - public void hideSearchPromptView() { - this.keyword = null; - searchLinearLayout.setVisibility(View.GONE); - noUserRelativeLayout.setVisibility(View.GONE); - } - - @OnClick(R.id.searchLinearLayout) - void search() { - if (!TextUtils.isEmpty(keyword)) { - contactViewModel.searchUser(keyword).observe(this, userInfos -> { - if (userInfos != null && !userInfos.isEmpty()) { - // show user info activity - Intent intent = new Intent(getActivity(), UserInfoActivity.class); - intent.putExtra("userInfo", userInfos.get(0)); - startActivity(intent); - getActivity().finish(); - } else { - searchLinearLayout.setVisibility(View.GONE); - noUserRelativeLayout.setVisibility(View.VISIBLE); - } - }); - } - } - - private void init() { - contactViewModel = ViewModelProviders.of(this).get(ContactViewModel.class); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/ChatRoomConversationInfoFragment.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/ChatRoomConversationInfoFragment.java deleted file mode 100644 index b4aae34c9579e167a5b6152fd9ccb39f6500625f..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/ChatRoomConversationInfoFragment.java +++ /dev/null @@ -1,7 +0,0 @@ -package cn.wildfire.chat.kit.conversation; - -import androidx.fragment.app.Fragment; - -// TODO -public class ChatRoomConversationInfoFragment extends Fragment { -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/ConversationActivity.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/ConversationActivity.java deleted file mode 100644 index 020a5c38123fc76b197c86431b3e13b3bc82c77e..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/ConversationActivity.java +++ /dev/null @@ -1,630 +0,0 @@ -package cn.wildfire.chat.kit.conversation; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.Handler; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextUtils; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.widget.Toast; - -import androidx.annotation.Nullable; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProviders; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.SimpleItemAnimator; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import java.util.List; -import java.util.Map; - -import butterknife.Bind; -import butterknife.OnTouch; -import cn.wildfire.chat.kit.ChatManagerHolder; -import cn.wildfire.chat.kit.ConfigEventViewModel; -import cn.wildfire.chat.kit.WfcBaseActivity; -import cn.wildfire.chat.kit.WfcUIKit; -import cn.wildfire.chat.kit.channel.ChannelViewModel; -import cn.wildfire.chat.kit.chatroom.ChatRoomViewModel; -import cn.wildfire.chat.kit.common.OperateResult; -import cn.wildfire.chat.kit.conversation.ext.core.ConversationExtension; -import cn.wildfire.chat.kit.conversation.mention.MentionSpan; -import cn.wildfire.chat.kit.conversation.message.model.UiMessage; -import cn.wildfire.chat.kit.third.utils.UIUtils; -import cn.wildfire.chat.kit.user.UserInfoActivity; -import cn.wildfire.chat.kit.user.UserViewModel; -import cn.wildfire.chat.kit.widget.InputAwareLayout; -import cn.wildfire.chat.kit.widget.KeyboardAwareLinearLayout; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.message.MessageContent; -import cn.wildfirechat.message.TypingMessageContent; -import cn.wildfirechat.message.core.MessageDirection; -import cn.wildfirechat.message.core.PersistFlag; -import cn.wildfirechat.message.notification.TipNotificationContent; -import cn.wildfirechat.model.ChannelInfo; -import cn.wildfirechat.model.ChatRoomInfo; -import cn.wildfirechat.model.Conversation; -import cn.wildfirechat.model.GroupInfo; -import cn.wildfirechat.model.UserInfo; -import cn.wildfirechat.remote.ChatManager; - -public class ConversationActivity extends WfcBaseActivity implements - KeyboardAwareLinearLayout.OnKeyboardShownListener, - KeyboardAwareLinearLayout.OnKeyboardHiddenListener, - ConversationMessageAdapter.OnPortraitClickListener, - ConversationMessageAdapter.OnPortraitLongClickListener, ConversationInputPanel.OnConversationInputPanelStateChangeListener { - - public static final int REQUEST_PICK_MENTION_CONTACT = 100; - - private Conversation conversation; - private boolean loadingNewMessage; - private boolean shouldContinueLoadNewMessage = false; - - private static final int MESSAGE_LOAD_COUNT_PER_TIME = 20; - private static final int MESSAGE_LOAD_AROUND = 10; - - @Bind(R.id.rootLinearLayout) - InputAwareLayout rootLinearLayout; - @Bind(R.id.swipeRefreshLayout) - SwipeRefreshLayout swipeRefreshLayout; - @Bind(R.id.msgRecyclerView) - RecyclerView recyclerView; - - @Bind(R.id.inputPanelFrameLayout) - ConversationInputPanel inputPanel; - - private ConversationMessageAdapter adapter; - private boolean moveToBottom = true; - private ConversationViewModel conversationViewModel; - private UserViewModel userViewModel; - private ChatRoomViewModel chatRoomViewModel; - - private Handler handler; - private long initialFocusedMessageId; - // 用户channel主发起,针对某个用户的会话 - private String channelPrivateChatUser; - private String conversationTitle = ""; - private SharedPreferences sharedPreferences; - private boolean showGroupMemberAlias = false; - - private Observer messageLiveDataObserver = new Observer() { - @Override - public void onChanged(@Nullable UiMessage uiMessage) { - MessageContent content = uiMessage.message.content; - if (isDisplayableMessage(uiMessage)) { - // 消息定位时,如果收到新消息、或者发送消息,需要重新加载消息列表 - if (shouldContinueLoadNewMessage) { - shouldContinueLoadNewMessage = false; - reloadMessage(); - return; - } - adapter.addNewMessage(uiMessage); - if (moveToBottom || uiMessage.message.sender.equals(ChatManager.Instance().getUserId())) { - UIUtils.postTaskDelay(() -> recyclerView.smoothScrollToPosition(adapter.getItemCount() - 1), 100); - } - } - if (content instanceof TypingMessageContent && uiMessage.message.direction == MessageDirection.Receive) { - updateTypingStatusTitle((TypingMessageContent) content); - } else { - resetConversationTitle(); - } - - if (uiMessage.message.direction == MessageDirection.Receive) { - conversationViewModel.clearUnreadStatus(conversation); - } - } - }; - private Observer messageUpdateLiveDatObserver = new Observer() { - @Override - public void onChanged(@Nullable UiMessage uiMessage) { - if (isDisplayableMessage(uiMessage)) { - adapter.updateMessage(uiMessage); - } - } - }; - - private Observer messageRemovedLiveDataObserver = new Observer() { - @Override - public void onChanged(@Nullable UiMessage uiMessage) { - if (isDisplayableMessage(uiMessage)) { - adapter.removeMessage(uiMessage); - } - } - }; - - private boolean isDisplayableMessage(UiMessage uiMessage) { - MessageContent content = uiMessage.message.content; - if (content.getPersistFlag() == PersistFlag.Persist - || content.getPersistFlag() == PersistFlag.Persist_And_Count) { - return true; - } - return false; - } - - private Observer> mediaUploadedLiveDataObserver = new Observer>() { - @Override - public void onChanged(@Nullable Map stringStringMap) { - for (Map.Entry entry : stringStringMap.entrySet()) { - sharedPreferences.edit() - .putString(entry.getKey(), entry.getValue()) - .apply(); - } - - } - }; - - private Observer> userInfoUpdateLiveDataObserver = new Observer>() { - @Override - public void onChanged(@Nullable List userInfos) { - adapter.updateUserInfos(userInfos); - } - }; - - private Observer clearMessageLiveDataObserver = (obj) -> { - adapter.setMessages(null); - adapter.notifyDataSetChanged(); - }; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // you can setup background here -// getWindow().setBackgroundDrawableResource(R.mipmap.splash); - } - - @Override - protected void afterViews() { - initView(); - sharedPreferences = getSharedPreferences("sticker", Context.MODE_PRIVATE); - Intent intent = getIntent(); - conversation = intent.getParcelableExtra("conversation"); - conversationTitle = intent.getStringExtra("conversationTitle"); - initialFocusedMessageId = intent.getLongExtra("toFocusMessageId", -1); - if (conversation == null) { - finish(); - } - setupConversation(conversation); - conversationViewModel.clearUnreadStatus(conversation); - } - - @Override - protected int contentLayout() { - return R.layout.conversation_activity; - } - - public static Intent buildConversationIntent(Context context, Conversation.ConversationType type, String target, int line) { - return buildConversationIntent(context, type, target, line, -1); - } - - public static Intent buildConversationIntent(Context context, Conversation.ConversationType type, String target, int line, long toFocusMessageId) { - Conversation conversation = new Conversation(type, target, line); - return buildConversationIntent(context, conversation, null, toFocusMessageId); - } - - public static Intent buildConversationIntent(Context context, Conversation.ConversationType type, String target, int line, String channelPrivateChatUser) { - Conversation conversation = new Conversation(type, target, line); - return buildConversationIntent(context, conversation, null, -1); - } - - public static Intent buildConversationIntent(Context context, Conversation conversation, String channelPrivateChatUser, long toFocusMessageId) { - Intent intent = new Intent(context, ConversationActivity.class); - intent.putExtra("conversation", conversation); - intent.putExtra("toFocusMessageId", toFocusMessageId); - intent.putExtra("channelPrivateChatUser", channelPrivateChatUser); - return intent; - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - conversation = intent.getParcelableExtra("conversation"); - initialFocusedMessageId = intent.getLongExtra("toFocusMessageId", -1); - channelPrivateChatUser = intent.getStringExtra("channelPrivateChatUser"); - setupConversation(conversation); - } - - private void initView() { - handler = new Handler(); - rootLinearLayout.addOnKeyboardShownListener(this); - - swipeRefreshLayout.setOnRefreshListener(this::loadMoreOldMessages); - - // message list - adapter = new ConversationMessageAdapter(this); - adapter.setOnPortraitClickListener(this); - adapter.setOnPortraitLongClickListener(this); - LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); - recyclerView.setLayoutManager(linearLayoutManager); - recyclerView.setAdapter(adapter); - ((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); - recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - super.onScrollStateChanged(recyclerView, newState); - // 向上滑动,不在底部,收到消息时,不滑动到底部, 发送消息时,可以强制置为true - if (newState != RecyclerView.SCROLL_STATE_IDLE) { - return; - } - if (!recyclerView.canScrollVertically(1)) { - moveToBottom = true; - if (initialFocusedMessageId != -1 && !loadingNewMessage && shouldContinueLoadNewMessage) { - int lastVisibleItem = linearLayoutManager.findLastCompletelyVisibleItemPosition(); - if (lastVisibleItem > adapter.getItemCount() - 3) { - loadMoreNewMessages(); - } - } - } else { - moveToBottom = false; - } - } - }); - - inputPanel.init(this, rootLinearLayout); - inputPanel.setOnConversationInputPanelStateChangeListener(this); - } - - private void setupConversation(Conversation conversation) { - if (conversationViewModel == null) { - conversationViewModel = ViewModelProviders.of(this, new ConversationViewModelFactory(conversation, channelPrivateChatUser)).get(ConversationViewModel.class); - - conversationViewModel.messageLiveData().observeForever(messageLiveDataObserver); - conversationViewModel.messageUpdateLiveData().observeForever(messageUpdateLiveDatObserver); - conversationViewModel.messageRemovedLiveData().observeForever(messageRemovedLiveDataObserver); - conversationViewModel.mediaUpdateLiveData().observeForever(mediaUploadedLiveDataObserver); - conversationViewModel.clearMessageLiveData().observeForever(clearMessageLiveDataObserver); - - userViewModel = WfcUIKit.getAppScopeViewModel(UserViewModel.class); - userViewModel.userInfoLiveData().observeForever(userInfoUpdateLiveDataObserver); - } else { - conversationViewModel.setConversation(conversation, channelPrivateChatUser); - } - - ConfigEventViewModel configEventViewModel = WfcUIKit.getAppScopeViewModel(ConfigEventViewModel.class); - configEventViewModel.showGroupAliasLiveData().observe(this, (event) -> { - adapter.notifyDataSetChanged(); - }); - - inputPanel.setupConversation(conversationViewModel, conversation); - - MutableLiveData> messages; - if (initialFocusedMessageId != -1) { - shouldContinueLoadNewMessage = true; - messages = conversationViewModel.loadAroundMessages(initialFocusedMessageId, MESSAGE_LOAD_AROUND); - } else { - messages = conversationViewModel.getMessages(); - } - - // load message - swipeRefreshLayout.setRefreshing(true); - messages.observe(this, uiMessages -> { - swipeRefreshLayout.setRefreshing(false); - adapter.setMessages(uiMessages); - adapter.notifyDataSetChanged(); - - if (adapter.getItemCount() > 1) { - int initialMessagePosition; - if (initialFocusedMessageId != -1) { - initialMessagePosition = adapter.getMessagePosition(initialFocusedMessageId); - if (initialMessagePosition != -1) { - recyclerView.scrollToPosition(initialMessagePosition); - adapter.highlightFocusMessage(initialMessagePosition); - } - } else { - moveToBottom = true; - recyclerView.scrollToPosition(adapter.getItemCount() - 1); - } - } - }); - if (conversation.type == Conversation.ConversationType.ChatRoom) { - joinChatRoom(); - } - - setTitle(); - } - - private void joinChatRoom() { - chatRoomViewModel = ViewModelProviders.of(this).get(ChatRoomViewModel.class); - chatRoomViewModel.joinChatRoom(conversation.target) - .observe(this, new Observer>() { - @Override - public void onChanged(@Nullable OperateResult booleanOperateResult) { - if (booleanOperateResult.isSuccess()) { - String welcome = "欢迎 %s 加入聊天室"; - TipNotificationContent content = new TipNotificationContent(); - String userId = userViewModel.getUserId(); - UserInfo userInfo = userViewModel.getUserInfo(userId, false); - if (userInfo != null) { - content.tip = String.format(welcome, userInfo.displayName); - } else { - content.tip = String.format(welcome, "<" + userId + ">"); - } - conversationViewModel.sendMessage(content); - loadMoreOldMessages(); - setChatRoomConversationTitle(); - - } else { - Toast.makeText(ConversationActivity.this, "加入聊天室失败", Toast.LENGTH_SHORT).show(); - finish(); - } - } - }); - } - - private void quitChatRoom() { - String welcome = "%s 离开了聊天室"; - TipNotificationContent content = new TipNotificationContent(); - String userId = userViewModel.getUserId(); - UserInfo userInfo = userViewModel.getUserInfo(userId, false); - if (userInfo != null) { - content.tip = String.format(welcome, userInfo.displayName); - } else { - content.tip = String.format(welcome, "<" + userId + ">"); - } - conversationViewModel.sendMessage(content); - chatRoomViewModel.quitChatRoom(conversation.target); - } - - private void setChatRoomConversationTitle() { - chatRoomViewModel.getChatRoomInfo(conversation.target, System.currentTimeMillis()) - .observe(this, chatRoomInfoOperateResult -> { - if (chatRoomInfoOperateResult.isSuccess()) { - ChatRoomInfo chatRoomInfo = chatRoomInfoOperateResult.getResult(); - conversationTitle = chatRoomInfo.title; - setTitle(conversationTitle); - } - }); - } - - private void setTitle() { - if (!TextUtils.isEmpty(conversationTitle)) { - setTitle(conversationTitle); - } - - if (conversation.type == Conversation.ConversationType.Single) { - UserInfo userInfo = ChatManagerHolder.gChatManager.getUserInfo(conversation.target, false); - conversationTitle = userViewModel.getUserDisplayName(userInfo); - } else if (conversation.type == Conversation.ConversationType.Group) { - GroupInfo groupInfo = ChatManagerHolder.gChatManager.getGroupInfo(conversation.target, false); - if (groupInfo != null) { - conversationTitle = groupInfo.name; - } - } else if (conversation.type == Conversation.ConversationType.Channel) { - ChannelViewModel channelViewModel = ViewModelProviders.of(this).get(ChannelViewModel.class); - ChannelInfo channelInfo = channelViewModel.getChannelInfo(conversation.target, false); - if (channelInfo != null) { - conversationTitle = channelInfo.name; - } - - if (!TextUtils.isEmpty(channelPrivateChatUser)) { - UserInfo channelPrivateChatUserInfo = userViewModel.getUserInfo(channelPrivateChatUser, false); - if (channelPrivateChatUserInfo != null) { - conversationTitle += "@" + userViewModel.getUserDisplayName(channelPrivateChatUserInfo); - } else { - conversationTitle += "@<" + channelPrivateChatUser + ">"; - } - } - } - setTitle(conversationTitle); - } - - @Override - protected int menu() { - return R.menu.conversation; - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.menu_conversation_info) { - showConversationInfo(); - return true; - } - return super.onOptionsItemSelected(item); - } - - - private void showConversationInfo() { - Intent intent = new Intent(ConversationActivity.this, ConversationInfoActivity.class); - intent.putExtra("conversationInfo", ChatManager.Instance().getConversation(conversation)); - startActivity(intent); - } - - @OnTouch({R.id.contentLayout, R.id.msgRecyclerView}) - boolean onTouch(View view, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN && inputPanel.extension.canHideOnScroll()) { - inputPanel.collapse(); - } - return false; - } - - @Override - public void onPortraitClick(UserInfo userInfo) { - Intent intent = new Intent(this, UserInfoActivity.class); - intent.putExtra("userInfo", userInfo); - startActivity(intent); - } - - @Override - public void onPortraitLongClick(UserInfo userInfo) { - // TODO panel insert - int position = inputPanel.editText.getSelectionEnd(); - position = position >= 0 ? position : 0; - if (conversation.type == Conversation.ConversationType.Group) { - SpannableString spannableString = mentionSpannable(userInfo); - inputPanel.editText.getEditableText().insert(position, spannableString); - } else { - inputPanel.editText.getEditableText().insert(position, userViewModel.getUserDisplayName(userInfo)); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode >= ConversationExtension.REQUEST_CODE_MIN) { - inputPanel.extension.onActivityResult(requestCode, resultCode, data); - return; - } else if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_PICK_MENTION_CONTACT) { - boolean isMentionAll = data.getBooleanExtra("mentionAll", false); - SpannableString spannableString; - if (isMentionAll) { - spannableString = mentionAllSpannable(); - } else { - String userId = data.getStringExtra("userId"); - UserInfo userInfo = userViewModel.getUserInfo(userId, false); - spannableString = mentionSpannable(userInfo); - } - int position = inputPanel.editText.getSelectionEnd(); - position = position > 0 ? position - 1 : 0; - inputPanel.editText.getEditableText().replace(position, position + 1, spannableString); - return; - } - super.onActivityResult(requestCode, resultCode, data); - } - - private SpannableString mentionAllSpannable() { - String text = "@所有人 "; - SpannableString spannableString = new SpannableString(text); - spannableString.setSpan(new MentionSpan(true), 0, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - return spannableString; - } - - private SpannableString mentionSpannable(UserInfo userInfo) { - String text = "@" + userInfo.displayName + " "; - SpannableString spannableString = new SpannableString(text); - spannableString.setSpan(new MentionSpan(userInfo.uid), 0, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - return spannableString; - } - - @Override - protected void onPause() { - super.onPause(); - inputPanel.onActivityPause(); - } - - @Override - protected void onDestroy() { - if (conversation.type == Conversation.ConversationType.ChatRoom) { - quitChatRoom(); - } - - super.onDestroy(); - conversationViewModel.messageLiveData().removeObserver(messageLiveDataObserver); - conversationViewModel.messageUpdateLiveData().removeObserver(messageUpdateLiveDatObserver); - conversationViewModel.messageRemovedLiveData().removeObserver(messageRemovedLiveDataObserver); - conversationViewModel.mediaUpdateLiveData().removeObserver(mediaUploadedLiveDataObserver); - conversationViewModel.clearMessageLiveData().removeObserver(clearMessageLiveDataObserver); - userViewModel.userInfoLiveData().removeObserver(userInfoUpdateLiveDataObserver); - } - - @Override - public void onBackPressed() { - if (rootLinearLayout.getCurrentInput() != null) { - rootLinearLayout.hideAttachedInput(true); - inputPanel.collapse(); - } else { - super.onBackPressed(); - } - } - - @Override - public void onKeyboardShown() { - inputPanel.onKeyboardShown(); - recyclerView.scrollToPosition(adapter.getItemCount() - 1); - } - - @Override - public void onKeyboardHidden() { - inputPanel.onKeyboardHidden(); - } - - private void reloadMessage() { - conversationViewModel.getMessages().observe(this, uiMessages -> { - adapter.setMessages(uiMessages); - adapter.notifyDataSetChanged(); - }); - } - - private void loadMoreOldMessages() { - long fromMessageId = 0; - long fromMessageUid = 0; - if (adapter.getMessages() != null && !adapter.getMessages().isEmpty()) { - fromMessageId = adapter.getItem(0).message.messageId; - fromMessageUid = adapter.getItem(0).message.messageUid; - } - conversationViewModel.loadOldMessages(fromMessageId, fromMessageUid, MESSAGE_LOAD_COUNT_PER_TIME) - .observe(this, uiMessages -> { - adapter.addMessagesAtHead(uiMessages); - - swipeRefreshLayout.setRefreshing(false); - }); - } - - private void loadMoreNewMessages() { - loadingNewMessage = true; - adapter.showLoadingNewMessageProgressBar(); - conversationViewModel.loadNewMessages(adapter.getItem(adapter.getItemCount() - 2).message.messageId, MESSAGE_LOAD_COUNT_PER_TIME) - .observe(this, messages -> { - loadingNewMessage = false; - adapter.dismissLoadingNewMessageProgressBar(); - - if (messages == null || messages.isEmpty()) { - shouldContinueLoadNewMessage = false; - } - if (messages != null && !messages.isEmpty()) { - adapter.addMessagesAtTail(messages); - } - }); - } - - private void updateTypingStatusTitle(TypingMessageContent typingMessageContent) { - String typingDesc = ""; - switch (typingMessageContent.getType()) { - case TypingMessageContent.TYPING_TEXT: - typingDesc = "对方正在输入"; - break; - case TypingMessageContent.TYPING_VOICE: - typingDesc = "对方正在录音"; - break; - case TypingMessageContent.TYPING_CAMERA: - typingDesc = "对方正在拍照"; - break; - case TypingMessageContent.TYPING_FILE: - typingDesc = "对方正在发送文件"; - break; - case TypingMessageContent.TYPING_LOCATION: - typingDesc = "对方正在发送位置"; - break; - default: - typingDesc = "unknown"; - break; - } - setTitle(typingDesc); - handler.postDelayed(resetConversationTitleRunnable, 5000); - } - - private Runnable resetConversationTitleRunnable = this::resetConversationTitle; - - private void resetConversationTitle() { - if (!TextUtils.equals(conversationTitle, getTitle())) { - setTitle(conversationTitle); - handler.removeCallbacks(resetConversationTitleRunnable); - } - } - - @Override - public void onInputPanelExpanded() { - recyclerView.scrollToPosition(adapter.getItemCount() - 1); - } - - @Override - public void onInputPanelCollapsed() { - // do nothing - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/ConversationViewModelFactory.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/ConversationViewModelFactory.java deleted file mode 100644 index 971e6cee1b644ef6ab7d69ef42e811f3c45555fe..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/ConversationViewModelFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.wildfire.chat.kit.conversation; - -import androidx.annotation.NonNull; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; -import cn.wildfirechat.model.Conversation; - -public class ConversationViewModelFactory extends ViewModelProvider.NewInstanceFactory { - private Conversation conversation; - private String channelPrivateChatUser; - - public ConversationViewModelFactory(Conversation conversation) { - this.conversation = conversation; - this.channelPrivateChatUser = null; - } - - public ConversationViewModelFactory(Conversation conversation, String channelPrivateChatUser) { - super(); - this.conversation = conversation; - this.channelPrivateChatUser = channelPrivateChatUser; - } - - @NonNull - @Override - public T create(@NonNull Class modelClass) { - return (T) new ConversationViewModel(conversation, channelPrivateChatUser); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/GroupConversationInfoFragment.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/GroupConversationInfoFragment.java deleted file mode 100644 index e5ce36e94e026f63de9cd0dba5c691a03dd83cc1..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/GroupConversationInfoFragment.java +++ /dev/null @@ -1,376 +0,0 @@ -package cn.wildfire.chat.kit.conversation; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.CompoundButton; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProviders; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.kyleduo.switchbutton.SwitchButton; -import com.lqr.optionitemview.OptionItemView; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import butterknife.Bind; -import butterknife.ButterKnife; -import butterknife.OnClick; -import cn.wildfire.chat.app.Config; -import cn.wildfire.chat.kit.ConfigEventViewModel; -import cn.wildfire.chat.kit.WfcScheme; -import cn.wildfire.chat.kit.WfcUIKit; -import cn.wildfire.chat.kit.common.OperateResult; -import cn.wildfire.chat.kit.contact.ContactViewModel; -import cn.wildfire.chat.kit.conversationlist.ConversationListViewModel; -import cn.wildfire.chat.kit.conversationlist.ConversationListViewModelFactory; -import cn.wildfire.chat.kit.group.AddGroupMemberActivity; -import cn.wildfire.chat.kit.group.GroupViewModel; -import cn.wildfire.chat.kit.group.RemoveGroupMemberActivity; -import cn.wildfire.chat.kit.group.SetGroupNameActivity; -import cn.wildfire.chat.kit.qrcode.QRCodeActivity; -import cn.wildfire.chat.kit.user.UserInfoActivity; -import cn.wildfire.chat.kit.user.UserViewModel; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.model.Conversation; -import cn.wildfirechat.model.ConversationInfo; -import cn.wildfirechat.model.GroupInfo; -import cn.wildfirechat.model.GroupMember; -import cn.wildfirechat.model.UserInfo; - -public class GroupConversationInfoFragment extends Fragment implements ConversationMemberAdapter.OnMemberClickListener, CompoundButton.OnCheckedChangeListener { - - // group - @Bind(R.id.groupLinearLayout_0) - LinearLayout groupLinearLayout_0; - @Bind(R.id.groupNameOptionItemView) - OptionItemView groupNameOptionItemView; - @Bind(R.id.groupQRCodeOptionItemView) - OptionItemView groupQRCodeOptionItemView; - @Bind(R.id.groupNoticeLinearLayout) - LinearLayout noticeLinearLayout; - @Bind(R.id.groupNoticeTextView) - TextView noticeTextView; - @Bind(R.id.groupManageOptionItemView) - OptionItemView groupManageOptionItemView; - @Bind(R.id.groupManageDividerLine) - View groupManageDividerLine; - - @Bind(R.id.groupLinearLayout_1) - LinearLayout groupLinearLayout_1; - @Bind(R.id.myGroupNickNameOptionItemView) - OptionItemView myGroupNickNameOptionItemView; - @Bind(R.id.showGroupMemberAliasSwitchButton) - SwitchButton showGroupMemberNickNameSwitchButton; - - @Bind(R.id.quitButton) - Button quitGroupButton; - - @Bind(R.id.markGroupLinearLayout) - LinearLayout markGroupLinearLayout; - @Bind(R.id.markGroupSwitchButton) - SwitchButton markGroupSwitchButton; - - // common - @Bind(R.id.memberRecyclerView) - RecyclerView memberReclerView; - @Bind(R.id.stickTopSwitchButton) - SwitchButton stickTopSwitchButton; - @Bind(R.id.silentSwitchButton) - SwitchButton silentSwitchButton; - - private ConversationInfo conversationInfo; - private ConversationMemberAdapter conversationMemberAdapter; - private ConversationViewModel conversationViewModel; - private UserViewModel userViewModel; - - private GroupViewModel groupViewModel; - private GroupInfo groupInfo; - // me in group - private GroupMember groupMember; - - - private static final int REQUEST_ADD_MEMBER = 100; - private static final int REQUEST_REMOVE_MEMBER = 200; - private static final int REQUEST_CODE_SET_GROUP_NAME = 300; - - public static GroupConversationInfoFragment newInstance(ConversationInfo conversationInfo) { - GroupConversationInfoFragment fragment = new GroupConversationInfoFragment(); - Bundle args = new Bundle(); - args.putParcelable("conversationInfo", conversationInfo); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle args = getArguments(); - assert args != null; - conversationInfo = args.getParcelable("conversationInfo"); - assert conversationInfo != null; - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.conversation_info_group_fragment, container, false); - ButterKnife.bind(this, view); - init(); - return view; - } - - private void init() { - conversationViewModel = ViewModelProviders.of(this, new ConversationViewModelFactory(conversationInfo.conversation)).get(ConversationViewModel.class); - userViewModel = WfcUIKit.getAppScopeViewModel(UserViewModel.class); - ContactViewModel contactViewModel = ViewModelProviders.of(this).get(ContactViewModel.class); - String userId = userViewModel.getUserId(); - groupLinearLayout_0.setVisibility(View.VISIBLE); - groupLinearLayout_1.setVisibility(View.VISIBLE); - markGroupLinearLayout.setVisibility(View.VISIBLE); - markGroupSwitchButton.setOnCheckedChangeListener(this); - quitGroupButton.setVisibility(View.VISIBLE); - - groupViewModel = ViewModelProviders.of(this).get(GroupViewModel.class); - List groupMembers = groupViewModel.getGroupMembers(conversationInfo.conversation.target, true); - List memberIds = new ArrayList<>(); - for (GroupMember member : groupMembers) { - if (member.memberId.equals(userId)) { - groupMember = member; - } - memberIds.add(member.memberId); - } - groupInfo = groupViewModel.getGroupInfo(conversationInfo.conversation.target, false); - - if (groupMember == null || groupInfo == null) { - Toast.makeText(getActivity(), "你不在群组或发生错误, 请稍后再试", Toast.LENGTH_SHORT).show(); - getActivity().finish(); - return; - } - - SharedPreferences sp = getActivity().getSharedPreferences(Config.SP_NAME, Context.MODE_PRIVATE); - String showAliasKey = String.format(Config.SP_KEY_SHOW_GROUP_MEMBER_ALIAS, groupInfo.target); - showGroupMemberNickNameSwitchButton.setChecked(sp.getBoolean(showAliasKey, false)); - showGroupMemberNickNameSwitchButton.setOnCheckedChangeListener((buttonView, isChecked) -> { - sp.edit() - .putBoolean(showAliasKey, isChecked) - .apply(); - ConfigEventViewModel configEventViewModel = WfcUIKit.getAppScopeViewModel(ConfigEventViewModel.class); - configEventViewModel.postGroupAliasEvent(groupInfo.target, isChecked); - }); - - boolean enableRemoveMember = false; - if (groupMember.type != GroupMember.GroupMemberType.Normal || userId.equals(groupInfo.owner)) { - enableRemoveMember = true; - } - conversationMemberAdapter = new ConversationMemberAdapter(true, enableRemoveMember); - List members = contactViewModel.getContacts(memberIds); - - for (GroupMember member : groupMembers) { - for (UserInfo userInfo : members) { - if (!TextUtils.isEmpty(member.alias) && member.memberId.equals(userInfo.uid)) { - userInfo.displayName = member.alias; - break; - } - } - } - myGroupNickNameOptionItemView.setRightText(groupMember.alias); - groupNameOptionItemView.setRightText(groupInfo.name); - - conversationMemberAdapter.setMembers(members); - conversationMemberAdapter.setOnMemberClickListener(this); - - memberReclerView.setAdapter(conversationMemberAdapter); - memberReclerView.setLayoutManager(new GridLayoutManager(getActivity(), 5)); - stickTopSwitchButton.setChecked(conversationInfo.isTop); - silentSwitchButton.setChecked(conversationInfo.isSilent); - stickTopSwitchButton.setOnCheckedChangeListener(this); - silentSwitchButton.setOnCheckedChangeListener(this); - } - - @OnClick(R.id.groupNameOptionItemView) - void updateGroupName() { - Intent intent = new Intent(getActivity(), SetGroupNameActivity.class); - intent.putExtra("groupInfo", groupInfo); - startActivityForResult(intent, REQUEST_CODE_SET_GROUP_NAME); - } - - @OnClick(R.id.groupNoticeLinearLayout) - void updateGroupNotice() { - // TODO - } - - @OnClick(R.id.groupManageOptionItemView) - void manageGroup() { - // TODO - } - - @OnClick(R.id.myGroupNickNameOptionItemView) - void updateMyGroupAlias() { - MaterialDialog dialog = new MaterialDialog.Builder(getActivity()) - .input("请输入你的群昵称", groupMember.alias, false, (dialog1, input) -> { - groupViewModel.modifyMyGroupAlias(groupInfo.target, input.toString().trim()) - .observe(GroupConversationInfoFragment.this, new Observer() { - @Override - public void onChanged(@Nullable OperateResult operateResult) { - ConfigEventViewModel configEventViewModel = WfcUIKit.getAppScopeViewModel(ConfigEventViewModel.class); - configEventViewModel.postGroupAliasEvent(groupInfo.target, true); - if (operateResult.isSuccess()) { - myGroupNickNameOptionItemView.setRightText(input.toString().trim()); - } else { - Toast.makeText(getActivity(), "修改群昵称失败:" + operateResult.getErrorCode(), Toast.LENGTH_SHORT).show(); - - } - } - }); - }) - .negativeText("取消") - .positiveText("确定") - .onPositive((dialog12, which) -> { - dialog12.dismiss(); - }) - .build(); - dialog.show(); - } - - @OnClick(R.id.quitButton) - void quitGroup() { - groupViewModel.quitGroup(conversationInfo.conversation.target, Collections.singletonList(0)).observe(this, aBoolean -> { - if (aBoolean != null && aBoolean) { - getActivity().finish(); - } else { - Toast.makeText(getActivity(), "退出群组失败", Toast.LENGTH_SHORT).show(); - } - }); - } - - @OnClick(R.id.clearMessagesOptionItemView) - void clearMessage() { - conversationViewModel.clearConversationMessage(conversationInfo.conversation); - } - - @OnClick(R.id.groupQRCodeOptionItemView) - void showGroupQRCode() { - String qrCodeValue = WfcScheme.QR_CODE_PREFIX_GROUP + groupInfo.target; - Intent intent = QRCodeActivity.buildQRCodeIntent(getActivity(), "群二维码", groupInfo.portrait, qrCodeValue); - startActivity(intent); - } - - @Override - public void onUserMemberClick(UserInfo userInfo) { - Intent intent = new Intent(getActivity(), UserInfoActivity.class); - intent.putExtra("userInfo", userInfo); - startActivity(intent); - } - - @Override - public void onAddMemberClick() { - Intent intent = new Intent(getActivity(), AddGroupMemberActivity.class); - intent.putExtra("groupInfo", groupInfo); - startActivityForResult(intent, REQUEST_ADD_MEMBER); - } - - @Override - public void onRemoveMemberClick() { - if (groupInfo != null) { - Intent intent = new Intent(getActivity(), RemoveGroupMemberActivity.class); - intent.putExtra("groupInfo", groupInfo); - startActivityForResult(intent, REQUEST_REMOVE_MEMBER); - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_ADD_MEMBER: - if (resultCode == AddGroupMemberActivity.RESULT_ADD_SUCCESS) { - List memberIds = data.getStringArrayListExtra("memberIds"); - addGroupMember(memberIds); - } - break; - case REQUEST_REMOVE_MEMBER: - if (resultCode == RemoveGroupMemberActivity.RESULT_REMOVE_SUCCESS) { - List memberIds = data.getStringArrayListExtra("memberIds"); - removeGroupMember(memberIds); - } - break; - case REQUEST_CODE_SET_GROUP_NAME: - if (resultCode == SetGroupNameActivity.RESULT_SET_GROUP_NAME_SUCCESS) { - groupNameOptionItemView.setRightText(data.getStringExtra("groupName")); - } - break; - default: - super.onActivityResult(requestCode, resultCode, data); - break; - } - } - - private void addGroupMember(List memberIds) { - if (memberIds == null || memberIds.isEmpty()) { - return; - } - List userInfos = userViewModel.getUserInfos(memberIds); - if (userInfos == null) { - return; - } - conversationMemberAdapter.addMembers(userInfos); - } - - private void removeGroupMember(List memberIds) { - if (memberIds == null || memberIds.isEmpty()) { - return; - } - conversationMemberAdapter.removeMembers(memberIds); - } - - private void stickTop(boolean top) { - ConversationListViewModel conversationListViewModel = ViewModelProviders - .of(this, new ConversationListViewModelFactory(Arrays.asList(Conversation.ConversationType.Single, Conversation.ConversationType.Group, Conversation.ConversationType.Channel), Arrays.asList(0))) - .get(ConversationListViewModel.class); - conversationListViewModel.setConversationTop(conversationInfo, top); - } - - private void markGroup(boolean mark) { - groupViewModel.setFavGroup(groupInfo.target, mark); - } - - private void silent(boolean silent) { - conversationViewModel.setConversationSilent(conversationInfo.conversation, silent); - } - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - switch (buttonView.getId()) { - case R.id.markGroupSwitchButton: - markGroup(isChecked); - break; - case R.id.stickTopSwitchButton: - stickTop(isChecked); - break; - case R.id.silentSwitchButton: - silent(isChecked); - break; - default: - break; - } - - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/ext/VoipExt.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/ext/VoipExt.java deleted file mode 100644 index 16f22b3f85a12752ec6db297de9a79bcfd8d5104..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/ext/VoipExt.java +++ /dev/null @@ -1,126 +0,0 @@ -package cn.wildfire.chat.kit.conversation.ext; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.view.View; - -import java.util.ArrayList; - -import androidx.lifecycle.ViewModelProviders; -import cn.wildfire.chat.kit.WfcUIKit; -import cn.wildfire.chat.kit.annotation.ExtContextMenuItem; -import cn.wildfire.chat.kit.conversation.ext.core.ConversationExt; -import cn.wildfire.chat.kit.group.GroupViewModel; -import cn.wildfire.chat.kit.group.PickGroupMemberActivity; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.model.Conversation; -import cn.wildfirechat.model.GroupInfo; - -public class VoipExt extends ConversationExt { - private static final int REQUEST_CODE_GROUP_VIDEO_CHAT = 0; - public static final int REQUEST_CODE_GROUP_AUDIO_CHAT = 1; - - @ExtContextMenuItem(title = "视频通话") - public void voip(View containerView, Conversation conversation) { - switch (conversation.type) { - case Single: - videoChat(conversation.target); - break; - case Group: - pickGroupMemberToVideoChat(); - break; - default: - break; - } - } - - @ExtContextMenuItem(title = "语音通话") - public void audio(View containerView, Conversation conversation) { - switch (conversation.type) { - case Single: - audioChat(conversation.target); - break; - case Group: - pickGroupMemberToAudioChat(); - break; - default: - break; - } - } - - private void pickGroupMemberToAudioChat() { - Intent intent = new Intent(context, PickGroupMemberActivity.class); - GroupViewModel groupViewModel = ViewModelProviders.of(context).get(GroupViewModel.class); - GroupInfo groupInfo = groupViewModel.getGroupInfo(conversation.target, false); - intent.putExtra("groupInfo", groupInfo); - intent.putExtra("maxCount", 1); - startActivityForResult(intent, REQUEST_CODE_GROUP_AUDIO_CHAT); - } - - private void pickGroupMemberToVideoChat() { - Intent intent = new Intent(context, PickGroupMemberActivity.class); - GroupViewModel groupViewModel = ViewModelProviders.of(context).get(GroupViewModel.class); - GroupInfo groupInfo = groupViewModel.getGroupInfo(conversation.target, false); - intent.putExtra("groupInfo", groupInfo); - intent.putExtra("maxCount", 1); - startActivityForResult(intent, REQUEST_CODE_GROUP_VIDEO_CHAT); - } - - private void audioChat(String targetId) { - WfcUIKit.onCall(context, targetId, true, true); - } - - private void videoChat(String targetId) { - WfcUIKit.onCall(context, targetId, true, false); - } - - @Override - public int priority() { - return 99; - } - - @Override - public int iconResId() { - return R.mipmap.ic_func_video; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK) { - return; - } - ArrayList memberIds; - switch (requestCode) { - case REQUEST_CODE_GROUP_AUDIO_CHAT: - memberIds = data.getStringArrayListExtra(PickGroupMemberActivity.EXTRA_RESULT); - if (memberIds != null && memberIds.size() > 0) { - audioChat(memberIds.get(0)); - } - break; - case REQUEST_CODE_GROUP_VIDEO_CHAT: - memberIds = data.getStringArrayListExtra(PickGroupMemberActivity.EXTRA_RESULT); - if (memberIds != null && memberIds.size() > 0) { - videoChat(memberIds.get(0)); - } - break; - default: - break; - } - } - - @Override - public boolean filter(Conversation conversation) { - if (conversation.type == Conversation.ConversationType.Single - || conversation.type == Conversation.ConversationType.Group) { - return false; - } - return true; - } - - - @Override - public String title(Context context) { - return "视频通话"; - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/forward/PickConversationTargetToForwardActivity.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/forward/PickConversationTargetToForwardActivity.java deleted file mode 100644 index dc25eb54bcc4f624462c7103c128d2c96950814d..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/forward/PickConversationTargetToForwardActivity.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.wildfire.chat.kit.conversation.forward; - -import android.content.Intent; -import android.widget.Toast; - -import com.afollestad.materialdialogs.MaterialDialog; - -import java.util.List; - -import androidx.annotation.Nullable; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProviders; -import cn.wildfire.chat.kit.common.OperateResult; -import cn.wildfire.chat.kit.contact.model.UIUserInfo; -import cn.wildfire.chat.kit.contact.pick.PickConversationTargetActivity; -import cn.wildfire.chat.kit.group.GroupViewModel; -import cn.wildfirechat.model.GroupInfo; - -public class PickConversationTargetToForwardActivity extends PickConversationTargetActivity { - // TODO 多选,单选 - // 先支持单选 - private boolean singleMode = true; - - @Override - protected void onContactPicked(List userInfos) { - if (singleMode && userInfos.size() > 1) { - // 先创建群组 - MaterialDialog dialog = new MaterialDialog.Builder(this) - .content("创建中...") - .progress(true, 100) - .build(); - dialog.show(); - GroupViewModel groupViewModel = ViewModelProviders.of(this).get(GroupViewModel.class); - groupViewModel.createGroup(this, userInfos) - .observe(this, new Observer>() { - @Override - public void onChanged(@Nullable OperateResult result) { - dialog.dismiss(); - if (result.isSuccess()) { - GroupInfo groupInfo = groupViewModel.getGroupInfo(result.getResult(), false); - Intent intent = new Intent(); - intent.putExtra("groupInfo", groupInfo); - setResult(RESULT_OK, intent); - finish(); - } else { - Toast.makeText(PickConversationTargetToForwardActivity.this, "create group error", Toast.LENGTH_SHORT).show(); - } - } - }); - } else { - Intent intent = new Intent(); - intent.putExtra("userInfo", userInfos.get(0).getUserInfo()); - setResult(RESULT_OK, intent); - finish(); - } - } - - // TODO 多选 - @Override - public void onGroupPicked(List groupInfos) { - Intent intent = new Intent(); - intent.putExtra("groupInfo", groupInfos.get(0)); - setResult(RESULT_OK, intent); - finish(); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/mention/MentionGroupMemberFragment.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/mention/MentionGroupMemberFragment.java deleted file mode 100644 index bc2b8c67a9389708e524f4acd8675bb005833bea..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/mention/MentionGroupMemberFragment.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.wildfire.chat.kit.conversation.mention; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; - -import cn.wildfire.chat.kit.contact.model.HeaderValue; -import cn.wildfire.chat.kit.group.GroupMemberListFragment; -import cn.wildfirechat.model.GroupInfo; - -public class MentionGroupMemberFragment extends GroupMemberListFragment { - - public static MentionGroupMemberFragment newInstance(GroupInfo groupInfo) { - Bundle args = new Bundle(); - args.putParcelable("groupInfo", groupInfo); - MentionGroupMemberFragment fragment = new MentionGroupMemberFragment(); - fragment.setArguments(args); - return fragment; - } - - @Override - public void initHeaderViewHolders() { - addHeaderViewHolder(MentionAllHeaderViewHolder.class, new HeaderValue()); - } - - @Override - public void onHeaderClick(int index) { - Intent intent = new Intent(); - intent.putExtra("mentionAll", true); - getActivity().setResult(Activity.RESULT_OK, intent); - getActivity().finish(); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/AudioMessageContentViewHolder.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/AudioMessageContentViewHolder.java deleted file mode 100644 index 118bfeca84aeaa88b2808d9588e44d1ba723c21d..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/AudioMessageContentViewHolder.java +++ /dev/null @@ -1,95 +0,0 @@ -package cn.wildfire.chat.kit.conversation.message.viewholder; - -import android.graphics.drawable.AnimationDrawable; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import java.io.File; - -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.Bind; -import butterknife.OnClick; -import cn.wildfire.chat.app.Config; -import cn.wildfire.chat.kit.annotation.EnableContextMenu; -import cn.wildfire.chat.kit.annotation.MessageContentType; -import cn.wildfire.chat.kit.annotation.ReceiveLayoutRes; -import cn.wildfire.chat.kit.annotation.SendLayoutRes; -import cn.wildfire.chat.kit.conversation.message.model.UiMessage; -import cn.wildfire.chat.kit.third.utils.UIUtils; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.message.SoundMessageContent; -import cn.wildfirechat.message.core.MessageDirection; - -@MessageContentType(SoundMessageContent.class) -@SendLayoutRes(resId = R.layout.conversation_item_audio_send) -@ReceiveLayoutRes(resId = R.layout.conversation_item_audio_receive) -@EnableContextMenu -public class AudioMessageContentViewHolder extends MediaMessageContentViewHolder { - @Bind(R.id.audioImageView) - ImageView ivAudio; - @Bind(R.id.durationTextView) - TextView durationTextView; - @Bind(R.id.audioContentLayout) - RelativeLayout contentLayout; - - public AudioMessageContentViewHolder(FragmentActivity context, RecyclerView.Adapter adapter, View itemView) { - super(context, adapter, itemView); - } - - @Override - public void onBind(UiMessage message) { - super.onBind(message); - SoundMessageContent voiceMessage = (SoundMessageContent) message.message.content; - int increment = UIUtils.getDisplayWidth() / 2 / Config.DEFAULT_MAX_AUDIO_RECORD_TIME_SECOND * voiceMessage.getDuration(); - - durationTextView.setText(voiceMessage.getDuration() + "''"); - ViewGroup.LayoutParams params = contentLayout.getLayoutParams(); - params.width = UIUtils.dip2Px(65) + UIUtils.dip2Px(increment); - contentLayout.setLayoutParams(params); - - AnimationDrawable animation; - if (message.isPlaying) { - animation = (AnimationDrawable) ivAudio.getBackground(); - if (!animation.isRunning()) { - animation.start(); - } - } else { - // TODO 不知道怎么回事,动画开始了,就停不下来, 所以采用这种方式 - ivAudio.setBackground(null); - if (message.message.direction == MessageDirection.Send) { - ivAudio.setBackgroundResource(R.drawable.audio_animation_right_list); - } else { - ivAudio.setBackgroundResource(R.drawable.audio_animation_left_list); - } - } - - // 下载完成,开始播放 - if (message.progress == 100) { - message.progress = 0; - itemView.post(() -> { - conversationViewModel.playAudioMessage(message); - }); - } - } - - @OnClick(R.id.audioContentLayout) - public void onClick(View view) { - File file = conversationViewModel.mediaMessageContentFile(message); - if (file == null) { - return; - } - if (file.exists()) { - conversationViewModel.playAudioMessage(message); - } else { - if (message.isDownloading) { - return; - } - conversationViewModel.downloadMedia(message, file); - } - } - -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/FileMessageContentViewHolder.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/FileMessageContentViewHolder.java deleted file mode 100644 index 60a66c8deb25e6d6323a03b73872cfe8fd811d2e..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/FileMessageContentViewHolder.java +++ /dev/null @@ -1,80 +0,0 @@ -package cn.wildfire.chat.kit.conversation.message.viewholder; - -import android.content.ComponentName; -import android.content.Intent; -import android.view.View; -import android.widget.Toast; - -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; - -import java.io.File; - -import butterknife.Bind; -import butterknife.OnClick; -import cn.wildfire.chat.kit.annotation.EnableContextMenu; -import cn.wildfire.chat.kit.annotation.LayoutRes; -import cn.wildfire.chat.kit.annotation.MessageContentType; -import cn.wildfire.chat.kit.conversation.message.model.UiMessage; -import cn.wildfire.chat.kit.third.utils.UIUtils; -import cn.wildfire.chat.kit.utils.FileUtils; -import cn.wildfire.chat.kit.widget.BubbleImageView; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.message.FileMessageContent; -import cn.wildfirechat.message.core.MessageStatus; - -@MessageContentType(FileMessageContent.class) -@LayoutRes(resId = R.layout.conversation_item_file_send) -@EnableContextMenu -public class FileMessageContentViewHolder extends MediaMessageContentViewHolder { - - @Bind(R.id.imageView) - BubbleImageView imageView; - private FileMessageContent fileMessageContent; - - public FileMessageContentViewHolder(FragmentActivity context, RecyclerView.Adapter adapter, View itemView) { - super(context, adapter, itemView); - } - - @Override - public void onBind(UiMessage message) { - super.onBind(message); - fileMessageContent = (FileMessageContent) message.message.content; - if (message.message.status == MessageStatus.Sending || message.isDownloading) { - imageView.setPercent(message.progress); - imageView.setProgressVisible(true); - imageView.showShadow(false); - } else { - imageView.setProgressVisible(false); - imageView.showShadow(false); - } - Glide.with(context).load(R.mipmap.ic_file) - .apply(new RequestOptions().override(UIUtils.dip2Px(150), UIUtils.dip2Px(150)).centerCrop()).into(imageView); - } - - @OnClick(R.id.imageView) - public void onClick(View view) { - if (message.isDownloading) { - return; - } - File file = conversationViewModel.mediaMessageContentFile(message); - if (file == null) { - return; - } - - if (file.exists()) { - Intent intent = FileUtils.getViewIntent(context, file); - ComponentName cn = intent.resolveActivity(context.getPackageManager()); - if (cn == null) { - Toast.makeText(context, "找不到能打开此文件的应用", Toast.LENGTH_SHORT).show(); - return; - } - context.startActivity(intent); - } else { - conversationViewModel.downloadMedia(message, file); - } - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/MediaMessageContentViewHolder.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/MediaMessageContentViewHolder.java deleted file mode 100644 index 5e4d4439c5e90b411e3408e6f8b8307c573b786b..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/MediaMessageContentViewHolder.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.wildfire.chat.kit.conversation.message.viewholder; - -import android.view.View; -import android.widget.ProgressBar; - -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.Bind; -import cn.wildfire.chat.kit.conversation.message.model.UiMessage; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.message.MediaMessageContent; -import cn.wildfirechat.message.Message; -import cn.wildfirechat.message.MessageContent; -import cn.wildfirechat.message.core.MessageDirection; -import cn.wildfirechat.message.core.MessageStatus; - -public class MediaMessageContentViewHolder extends NormalMessageContentViewHolder { - @Bind(R.id.progressBar) - ProgressBar progressBar; - - public MediaMessageContentViewHolder(FragmentActivity activity, RecyclerView.Adapter adapter, View itemView) { - super(activity, adapter, itemView); - } - - @Override - protected void onBind(UiMessage message) { - if (message.message.direction == MessageDirection.Receive) { - if (message.isDownloading) { - progressBar.setVisibility(View.VISIBLE); - } else { - progressBar.setVisibility(View.GONE); - } - } else { - // todo - } - } - - @Override - protected void setSendStatus(Message item) { - super.setSendStatus(item); - MessageContent msgContent = item.content; - if (msgContent instanceof MediaMessageContent) { - //只需要设置自己发送的状态 - MessageStatus sentStatus = item.status; - if (sentStatus == MessageStatus.Sending) { - progressBar.setVisibility(View.VISIBLE); - } else if (sentStatus == MessageStatus.Send_Failure) { - progressBar.setVisibility(View.GONE); - } else if (sentStatus == MessageStatus.Sent) { - progressBar.setVisibility(View.GONE); - } - } - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/NormalMessageContentViewHolder.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/NormalMessageContentViewHolder.java deleted file mode 100644 index f7cff332b5e05d654b36b10a854be481ed3e88cf..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/NormalMessageContentViewHolder.java +++ /dev/null @@ -1,232 +0,0 @@ -package cn.wildfire.chat.kit.conversation.message.viewholder; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.text.TextUtils; -import android.view.View; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentActivity; -import androidx.lifecycle.ViewModelProviders; -import androidx.recyclerview.widget.RecyclerView; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.bumptech.glide.load.resource.bitmap.CenterCrop; -import com.bumptech.glide.load.resource.bitmap.RoundedCorners; - -import butterknife.Bind; -import butterknife.OnClick; -import cn.wildfire.chat.app.Config; -import cn.wildfire.chat.kit.ChatManagerHolder; -import cn.wildfire.chat.kit.GlideApp; -import cn.wildfire.chat.kit.annotation.MessageContextMenuItem; -import cn.wildfire.chat.kit.conversation.ConversationActivity; -import cn.wildfire.chat.kit.conversation.forward.ForwardActivity; -import cn.wildfire.chat.kit.conversation.message.model.UiMessage; -import cn.wildfire.chat.kit.group.GroupViewModel; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.message.Message; -import cn.wildfirechat.message.MessageContent; -import cn.wildfirechat.message.core.MessageDirection; -import cn.wildfirechat.message.core.MessageStatus; -import cn.wildfirechat.message.notification.NotificationMessageContent; -import cn.wildfirechat.model.Conversation; -import cn.wildfirechat.model.UserInfo; -import cn.wildfirechat.remote.ChatManager; - -/** - * 普通消息 - */ -public abstract class NormalMessageContentViewHolder extends MessageContentViewHolder { - @Bind(R.id.portraitImageView) - ImageView portraitImageView; - @Bind(R.id.errorLinearLayout) - LinearLayout errorLinearLayout; - @Bind(R.id.nameTextView) - TextView nameTextView; - - public NormalMessageContentViewHolder(FragmentActivity activity, RecyclerView.Adapter adapter, View itemView) { - super(activity, adapter, itemView); - } - - @Override - public void onBind(UiMessage message, int position) { - super.onBind(message, position); - this.message = message; - this.position = position; - - setSenderAvatar(message.message); - setSenderName(message.message); - setSendStatus(message.message); - try { - onBind(message); - } catch (Exception e) { - e.printStackTrace(); - } - - if (message.isFocus) { - highlightItem(itemView, message); - } - } - - protected abstract void onBind(UiMessage message); - - /** - * when animation finish, do not forget to set {@link UiMessage#isFocus} to {@code true} - * - * @param itemView the item view - * @param message the message to highlight - */ - protected void highlightItem(View itemView, UiMessage message) { - Animation animation = new AlphaAnimation((float) 0.4, (float) 0.2); - itemView.setBackgroundColor(itemView.getResources().getColor(R.color.colorPrimary)); - animation.setRepeatCount(2); - animation.setDuration(500); - animation.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - - } - - @Override - public void onAnimationEnd(Animation animation) { - itemView.setBackground(null); - message.isFocus = false; - } - - @Override - public void onAnimationRepeat(Animation animation) { - - } - }); - itemView.startAnimation(animation); - } - - // TODO 也用注解来做? - public boolean checkable(UiMessage message) { - return true; - } - - @Nullable - @OnClick(R.id.errorLinearLayout) - public void onRetryClick(View itemView) { - new MaterialDialog.Builder(context) - .content("重新发送?") - .negativeText("取消") - .positiveText("重发") - .onPositive((dialog, which) -> conversationViewModel.resendMessage(message.message)) - .build() - .show(); - } - - - @MessageContextMenuItem(tag = MessageContextMenuItemTags.TAG_RECALL, title = "撤回", priority = 10) - public void recall(View itemView, UiMessage message) { - conversationViewModel.recallMessage(message.message); - } - - @MessageContextMenuItem(tag = MessageContextMenuItemTags.TAG_DELETE, title = "删除", confirm = true, confirmPrompt = "确认删除此消息", priority = 11) - public void removeMessage(View itemView, UiMessage message) { - conversationViewModel.deleteMessage(message.message); - } - - @MessageContextMenuItem(tag = MessageContextMenuItemTags.TAG_FORWARD, title = "转发", priority = 11) - public void forwardMessage(View itemView, UiMessage message) { - Intent intent = new Intent(context, ForwardActivity.class); - intent.putExtra("message", message.message); - context.startActivity(intent); - } - - @MessageContextMenuItem(tag = MessageContextMenuItemTags.TAG_CHANEL_PRIVATE_CHAT, title = "私聊", priority = 12) - public void startChanelPrivateChat(View itemView, UiMessage message) { - Intent intent = ConversationActivity.buildConversationIntent(context, Conversation.ConversationType.Channel, message.message.conversation.target, message.message.conversation.line, message.message.sender); - context.startActivity(intent); - } - - @Override - public boolean contextMenuItemFilter(UiMessage uiMessage, String tag) { - Message message = uiMessage.message; - if (MessageContextMenuItemTags.TAG_RECALL.equals(tag)) { - long delta = ChatManager.Instance().getServerDeltaTime(); - long now = System.currentTimeMillis(); - if (message.direction == MessageDirection.Send - && TextUtils.equals(message.sender, ChatManager.Instance().getUserId()) - && now - (message.serverTime - delta) < 60 * 1000) { - return false; - } else { - return true; - } - } - - if (uiMessage.message.content instanceof NotificationMessageContent && MessageContextMenuItemTags.TAG_FORWARD.equals(tag)) { - return true; - } - - // 只有channel 主可以发起 - if (MessageContextMenuItemTags.TAG_CHANEL_PRIVATE_CHAT.equals(tag)) { - if (uiMessage.message.conversation.type == Conversation.ConversationType.Channel - && uiMessage.message.direction == MessageDirection.Receive - && conversationViewModel.getChannelPrivateChatUser() == null) { - return false; - } - return true; - } - return false; - } - - private void setSenderAvatar(Message item) { - // TODO get user info from viewModel - UserInfo userInfo = ChatManagerHolder.gChatManager.getUserInfo(item.sender, false); - if (userInfo != null && !TextUtils.isEmpty(userInfo.portrait) && portraitImageView != null) { - GlideApp - .with(context) - .load(userInfo.portrait) - .transforms(new CenterCrop(), new RoundedCorners(10)) - .into(portraitImageView); - } - } - - private void setSenderName(Message item) { - if (item.conversation.type == Conversation.ConversationType.Single) { - nameTextView.setVisibility(View.GONE); - } else if (item.conversation.type == Conversation.ConversationType.Group) { - showGroupMemberAlias(message.message.conversation, message.message.sender); - } else { - // todo - } - } - - private void showGroupMemberAlias(Conversation conversation, String sender) { - SharedPreferences sp = context.getSharedPreferences(Config.SP_NAME, Context.MODE_PRIVATE); - if (!sp.getBoolean(String.format(Config.SP_KEY_SHOW_GROUP_MEMBER_ALIAS, conversation.target), false)) { - nameTextView.setVisibility(View.GONE); - return; - } - nameTextView.setVisibility(View.VISIBLE); - // TODO optimize 缓存userInfo吧 -// if (Conversation.equals(nameTextView.getTag(), sender)) { -// return; -// } - GroupViewModel groupViewModel = ViewModelProviders.of(context).get(GroupViewModel.class); - nameTextView.setText(groupViewModel.getGroupMemberDisplayName(conversation.target, sender)); - nameTextView.setTag(sender); - } - - protected void setSendStatus(Message item) { - MessageContent msgContent = item.content; - MessageStatus sentStatus = item.status; - if (sentStatus == MessageStatus.Sending) { - errorLinearLayout.setVisibility(View.GONE); - } else if (sentStatus == MessageStatus.Send_Failure) { - errorLinearLayout.setVisibility(View.VISIBLE); - } else if (sentStatus == MessageStatus.Sent) { - errorLinearLayout.setVisibility(View.GONE); - } - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/NotificationMessageContentViewHolder.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/NotificationMessageContentViewHolder.java deleted file mode 100644 index 347210f5a599450cc3c5910b9f3c494f1ed5f0de..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/NotificationMessageContentViewHolder.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.wildfire.chat.kit.conversation.message.viewholder; - -import android.view.View; - -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.RecyclerView; - -public abstract class NotificationMessageContentViewHolder extends MessageContentViewHolder { - public NotificationMessageContentViewHolder(FragmentActivity activity, RecyclerView.Adapter adapter, View itemView) { - super(activity, adapter, itemView); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/RecallMessageContentViewHolderSimple.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/RecallMessageContentViewHolderSimple.java deleted file mode 100644 index d733550bdb38ff30d84e43eb85547d989f6dff1d..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/RecallMessageContentViewHolderSimple.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.wildfire.chat.kit.conversation.message.viewholder; - -import android.view.View; -import android.widget.TextView; - -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.RecyclerView; - -import butterknife.Bind; -import cn.wildfire.chat.kit.annotation.LayoutRes; -import cn.wildfire.chat.kit.annotation.MessageContentType; -import cn.wildfire.chat.kit.conversation.message.model.UiMessage; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.message.notification.RecallMessageContent; - -@MessageContentType(RecallMessageContent.class) -@LayoutRes(resId = R.layout.conversation_item_notification) -public class RecallMessageContentViewHolderSimple extends SimpleNotificationMessageContentViewHolder { - @Bind(R.id.notificationTextView) - TextView notificationTextView; - - public RecallMessageContentViewHolderSimple(FragmentActivity activity, RecyclerView.Adapter adapter, View itemView) { - super(activity, adapter, itemView); - } - - @Override - protected void onBind(UiMessage message) { - RecallMessageContent content = (RecallMessageContent) message.message.content; - notificationTextView.setText(content.digest(message.message)); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/TextMessageContentViewHolder.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/TextMessageContentViewHolder.java deleted file mode 100644 index a867f35b6bf2dad7b467c09c373a0d6fa204ef48..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/TextMessageContentViewHolder.java +++ /dev/null @@ -1,60 +0,0 @@ -package cn.wildfire.chat.kit.conversation.message.viewholder; - -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.text.style.ImageSpan; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.RecyclerView; - -import com.lqr.emoji.MoonUtils; - -import butterknife.Bind; -import butterknife.OnClick; -import cn.wildfire.chat.kit.annotation.EnableContextMenu; -import cn.wildfire.chat.kit.annotation.MessageContentType; -import cn.wildfire.chat.kit.annotation.MessageContextMenuItem; -import cn.wildfire.chat.kit.annotation.ReceiveLayoutRes; -import cn.wildfire.chat.kit.annotation.SendLayoutRes; -import cn.wildfire.chat.kit.conversation.message.model.UiMessage; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.message.TextMessageContent; - -@MessageContentType(TextMessageContent.class) -@SendLayoutRes(resId = R.layout.conversation_item_text_send) -@ReceiveLayoutRes(resId = R.layout.conversation_item_text_receive) -@EnableContextMenu -public class TextMessageContentViewHolder extends NormalMessageContentViewHolder { - @Bind(R.id.contentTextView) - TextView contentTextView; - - public TextMessageContentViewHolder(FragmentActivity activity, RecyclerView.Adapter adapter, View itemView) { - super(activity, adapter, itemView); - } - - @Override - public void onBind(UiMessage message) { - MoonUtils.identifyFaceExpression(context, contentTextView, ((TextMessageContent) message.message.content).getContent(), ImageSpan.ALIGN_BOTTOM); - } - - @OnClick(R.id.contentTextView) - public void onClickTest(View view) { - Toast.makeText(context, "onTextMessage click: " + ((TextMessageContent) message.message.content).getContent(), Toast.LENGTH_SHORT).show(); - } - - - @MessageContextMenuItem(tag = MessageContextMenuItemTags.TAG_CLIP, title = "复制", confirm = false, priority = 12) - public void clip(View itemView, UiMessage message) { - ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - if (clipboardManager == null) { - return; - } - TextMessageContent content = (TextMessageContent) message.message.content; - ClipData clipData = ClipData.newPlainText("messageContent", content.getContent()); - clipboardManager.setPrimaryClip(clipData); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/VideoMessageContentViewHolder.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/VideoMessageContentViewHolder.java deleted file mode 100644 index d3798ac16b4767ffc6fa265e61cb9a29ab1f74c5..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/VideoMessageContentViewHolder.java +++ /dev/null @@ -1,80 +0,0 @@ -package cn.wildfire.chat.kit.conversation.message.viewholder; - -import android.view.View; -import android.widget.ImageView; - -import java.util.ArrayList; -import java.util.List; - -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.Bind; -import butterknife.OnClick; -import cn.wildfire.chat.kit.annotation.EnableContextMenu; -import cn.wildfire.chat.kit.annotation.LayoutRes; -import cn.wildfire.chat.kit.annotation.MessageContentType; -import cn.wildfire.chat.kit.conversation.ConversationMessageAdapter; -import cn.wildfire.chat.kit.conversation.message.model.UiMessage; -import cn.wildfire.chat.kit.preview.MMPreviewActivity; -import cn.wildfire.chat.kit.widget.BubbleImageView; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.message.VideoMessageContent; - -@MessageContentType(VideoMessageContent.class) -@LayoutRes(resId = R.layout.conversation_item_video_send) -@EnableContextMenu -public class VideoMessageContentViewHolder extends MediaMessageContentViewHolder { - @Bind(R.id.imageView) - BubbleImageView imageView; - @Bind(R.id.playImageView) - ImageView playImageView; - - public VideoMessageContentViewHolder(FragmentActivity context, RecyclerView.Adapter adapter, View itemView) { - super(context, adapter, itemView); - } - - @Override - public void onBind(UiMessage message) { - VideoMessageContent fileMessage = (VideoMessageContent) message.message.content; - if (fileMessage.getThumbnail() != null && fileMessage.getThumbnail().getWidth() > 0) { - imageView.setImageBitmap(fileMessage.getThumbnail()); - playImageView.setVisibility(View.VISIBLE); - } else { - imageView.setImageResource(R.mipmap.img_video_default); - playImageView.setVisibility(View.GONE); - } - } - - @OnClick(R.id.videoContentLayout) - void play() { - List messages = ((ConversationMessageAdapter) adapter).getMessages(); - List mmMessages = new ArrayList<>(); - for (UiMessage msg : messages) { - if (msg.message.content.getType() == cn.wildfirechat.message.core.MessageContentType.ContentType_Image - || msg.message.content.getType() == cn.wildfirechat.message.core.MessageContentType.ContentType_Video) { - mmMessages.add(msg); - } - } - if (mmMessages.isEmpty()) { - return; - } - - int current = 0; - for (int i = 0; i < mmMessages.size(); i++) { - if (message.message.messageId == mmMessages.get(i).message.messageId) { - current = i; - break; - } - } - MMPreviewActivity.startActivity(context, mmMessages, current); - } - - @Override - public boolean contextMenuItemFilter(UiMessage uiMessage, String tag) { - if (MessageContextMenuItemTags.TAG_FORWARD.equals(tag)) { - return true; - } else { - return super.contextMenuItemFilter(uiMessage, tag); - } - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/VoipMessageViewHolder.java b/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/VoipMessageViewHolder.java deleted file mode 100644 index d287a77df89d1c49cfee14246a1d146ed19cec24..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/message/viewholder/VoipMessageViewHolder.java +++ /dev/null @@ -1,63 +0,0 @@ -package cn.wildfire.chat.kit.conversation.message.viewholder; - -import android.view.View; -import android.widget.TextView; - -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.Bind; -import butterknife.ButterKnife; -import butterknife.OnClick; -import cn.wildfire.chat.kit.WfcUIKit; -import cn.wildfire.chat.kit.annotation.EnableContextMenu; -import cn.wildfire.chat.kit.annotation.MessageContentType; -import cn.wildfire.chat.kit.annotation.ReceiveLayoutRes; -import cn.wildfire.chat.kit.annotation.SendLayoutRes; -import cn.wildfire.chat.kit.conversation.message.model.UiMessage; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.message.CallStartMessageContent; - -@MessageContentType(CallStartMessageContent.class) -@ReceiveLayoutRes(resId = R.layout.conversation_item_voip_receive) -@SendLayoutRes(resId = R.layout.conversation_item_voip_send) -@EnableContextMenu -public class VoipMessageViewHolder extends NormalMessageContentViewHolder { - @Bind(R.id.contentTextView) - TextView textView; - - public VoipMessageViewHolder(FragmentActivity activity, RecyclerView.Adapter adapter, View itemView) { - super(activity, adapter, itemView); - ButterKnife.bind(this, itemView); - } - - @Override - public void onBind(UiMessage message) { - CallStartMessageContent content = (CallStartMessageContent) message.message.content; - if (content.getStatus() == 0) { - textView.setText("对方未接听"); - } else if (content.getStatus() == 1) { - textView.setText("通话中"); - } else { - String text; - if (content.getConnectTime() > 0) { - long duration = (content.getEndTime() - content.getConnectTime()) / 1000; - if (duration > 3600) { - text = String.format("通话时长 %d:%02d:%02d", duration / 3600, (duration % 3600) / 60, (duration % 60)); - } else { - text = String.format("通话时长 %02d:%02d", duration / 60, (duration % 60)); - } - } else { - text = "对方未接听"; - } - textView.setText(text); - } - } - - @OnClick(R.id.contentTextView) - public void call(View view) { - if (((CallStartMessageContent) message.message.content).getStatus() == 1) { - return; - } - WfcUIKit.onCall(context, message.message.conversation.target, true, false); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversation/todo.md b/chat/src/main/java/cn/wildfire/chat/kit/conversation/todo.md deleted file mode 100644 index 3d3a017580f9bb996f292844efdad4587f3c014c..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversation/todo.md +++ /dev/null @@ -1,2 +0,0 @@ -1. inputPanel -2. menu group ```android:animateLayoutChanges="true" ``` diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/ConversationListFragment.java b/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/ConversationListFragment.java deleted file mode 100644 index 9525f5b7855238c15f155e68a388d576ef9d4e70..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/ConversationListFragment.java +++ /dev/null @@ -1,163 +0,0 @@ -package cn.wildfire.chat.kit.conversationlist; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProviders; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.SimpleItemAnimator; - -import java.util.Arrays; -import java.util.List; - -import cn.wildfire.chat.kit.IMServiceStatusViewModel; -import cn.wildfire.chat.kit.WfcUIKit; -import cn.wildfire.chat.kit.conversationlist.notification.ConnectionNotification; -import cn.wildfire.chat.kit.conversationlist.notification.StatusNotification; -import cn.wildfire.chat.kit.user.UserViewModel; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.client.ConnectionStatus; -import cn.wildfirechat.model.Conversation; -import cn.wildfirechat.model.ConversationInfo; -import cn.wildfirechat.model.UserInfo; - -public class ConversationListFragment extends Fragment { - private RecyclerView recyclerView; - private ConversationListAdapter adapter; - private static final List types = Arrays.asList(Conversation.ConversationType.Single, - Conversation.ConversationType.Group, - Conversation.ConversationType.Channel); - private static final List lines = Arrays.asList(0); - - private ConversationListViewModel conversationListViewModel; - private IMServiceStatusViewModel imServiceStatusViewModel; - private UserViewModel userViewModel; - private Observer conversationInfoObserver = new Observer() { - @Override - public void onChanged(@Nullable ConversationInfo conversationInfo) { - // just handle what we care about - if (types.contains(conversationInfo.conversation.type) && lines.contains(conversationInfo.conversation.line)) { - adapter.submitConversationInfo(conversationInfo); - // scroll or not? - // recyclerView.scrollToPosition(0); - } - } - }; - - private Observer conversationRemovedObserver = new Observer() { - @Override - public void onChanged(@Nullable Conversation conversation) { - if (conversation == null) { - return; - } - if (types.contains(conversation.type) && lines.contains(conversation.line)) { - adapter.removeConversation(conversation); - } - } - }; - - // 会话同步 - private Observer settingUpdateObserver = o -> reloadConversations(); - - private Observer> userInfoLiveDataObserver = (userInfos) -> { - adapter.updateUserInfos(userInfos); - }; - - private Observer imStatusLiveDataObserver = (connected) -> { - if (connected) { - if (adapter != null && (adapter.getConversationInfos() == null || adapter.getConversationInfos().size() == 0)) { - reloadConversations(); - } - } - }; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.conversationlist_frament, container, false); - recyclerView = view.findViewById(R.id.recyclerView); - init(); - return view; - } - - private void init() { - adapter = new ConversationListAdapter(this); - conversationListViewModel = ViewModelProviders - .of(this, new ConversationListViewModelFactory(types, lines)) - .get(ConversationListViewModel.class); - conversationListViewModel.getConversationListAsync(types, lines) - .observe(this, conversationInfos -> { - adapter.setConversationInfos(conversationInfos); - adapter.notifyDataSetChanged(); - }); - recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - - DividerItemDecoration itemDecor = new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL); - itemDecor.setDrawable(ContextCompat.getDrawable(getContext(), R.drawable.recyclerview_horizontal_divider)); - recyclerView.addItemDecoration(itemDecor); - recyclerView.setAdapter(adapter); - ((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); - - conversationListViewModel.conversationInfoLiveData().observeForever(conversationInfoObserver); - conversationListViewModel.conversationRemovedLiveData().observeForever(conversationRemovedObserver); - conversationListViewModel.settingUpdateLiveData().observeForever(settingUpdateObserver); - - imServiceStatusViewModel = WfcUIKit.getAppScopeViewModel(IMServiceStatusViewModel.class); - imServiceStatusViewModel.imServiceStatusLiveData().observeForever(imStatusLiveDataObserver); - - userViewModel = WfcUIKit.getAppScopeViewModel(UserViewModel.class); - userViewModel.userInfoLiveData().observeForever(userInfoLiveDataObserver); - - conversationListViewModel.connectionStatusLiveData().observe(this, status -> { - switch (status) { - case ConnectionStatus.ConnectionStatusConnecting: - showNotification(ConnectionNotification.class, "正在连接..."); - break; - case ConnectionStatus.ConnectionStatusConnected: - clearNotification(ConnectionNotification.class); - break; - case ConnectionStatus.ConnectionStatusUnconnected: - showNotification(ConnectionNotification.class, "连接失败"); - break; - default: - break; - } - }); - } - - private void showNotification(Class clazz, Object value) { - adapter.showStatusNotification(clazz, value); - } - - private void clearNotification(Class clazz) { - adapter.clearStatusNotification(clazz); - } - - private void reloadConversations() { - conversationListViewModel.getConversationListAsync(types, lines) - .observe(this, conversationInfos -> { - adapter.setConversationInfos(conversationInfos); - adapter.notifyDataSetChanged(); - }); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - conversationListViewModel.conversationInfoLiveData().removeObserver(conversationInfoObserver); - conversationListViewModel.conversationRemovedLiveData().removeObserver(conversationRemovedObserver); - conversationListViewModel.settingUpdateLiveData().removeObserver(settingUpdateObserver); - imServiceStatusViewModel.imServiceStatusLiveData().removeObserver(imStatusLiveDataObserver); - userViewModel.userInfoLiveData().removeObserver(userInfoLiveDataObserver); - } - -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/notification/ConnectionNotification.java b/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/notification/ConnectionNotification.java deleted file mode 100644 index e2b605af97908554e5ade1d3a9eb18b91d8834f9..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/notification/ConnectionNotification.java +++ /dev/null @@ -1,41 +0,0 @@ -package cn.wildfire.chat.kit.conversationlist.notification; - -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.fragment.app.Fragment; -import butterknife.Bind; -import butterknife.OnClick; -import cn.wildfirechat.chat.R; - -public class ConnectionNotification extends StatusNotification { - public ConnectionNotification(Fragment fragment) { - super(fragment); - } - - @Bind(R.id.statusTextView) - TextView statusTextView; - - @Override - public int priority() { - return 0; - } - - @Override - public int layoutRes() { - return R.layout.conversationlist_item_notification_connection_status; - } - - @Override - public void onBind(View view, Object value) { - String status = (String) value; - statusTextView.setText(status); - } - - @OnClick(R.id.statusTextView) - public void onClick() { - Toast.makeText(fragment.getContext(), "status on Click", Toast.LENGTH_SHORT).show(); - - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/notification/PCOlineNotification.java b/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/notification/PCOlineNotification.java deleted file mode 100644 index c117b7db0a9837353d3e5ed3439b7386ae3c4267..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/notification/PCOlineNotification.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.wildfire.chat.kit.conversationlist.notification; - -import android.view.View; - -import androidx.fragment.app.Fragment; -import cn.wildfirechat.chat.R; - -public class PCOlineNotification extends StatusNotification { - public PCOlineNotification(Fragment fragment) { - super(fragment); - } - - @Override - public int priority() { - return 0; - } - - @Override - public int layoutRes() { - return R.layout.conversationlist_item_notification_pc_online; - } - - @Override - public void onBind(View view, Object value) { - - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/notification/StatusNotification.java b/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/notification/StatusNotification.java deleted file mode 100644 index 6ad98f216b53f88f65341f91efa58bb2edd641ec..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/conversationlist/notification/StatusNotification.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.wildfire.chat.kit.conversationlist.notification; - -import android.view.View; - -import androidx.fragment.app.Fragment; - -public abstract class StatusNotification { - protected Fragment fragment; - - public StatusNotification(Fragment fragment) { - this.fragment = fragment; - } - - public abstract int priority(); - - public abstract int layoutRes(); - - /** - * @param view the view inflate from {@link #layoutRes()} - */ - public abstract void onBind(View view, Object value); - -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/group/GroupMemberListFragment.java b/chat/src/main/java/cn/wildfire/chat/kit/group/GroupMemberListFragment.java deleted file mode 100644 index 7620bca32c332e0c2c37565c7df1069a91e6fd7e..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/group/GroupMemberListFragment.java +++ /dev/null @@ -1,72 +0,0 @@ -package cn.wildfire.chat.kit.group; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.text.TextUtils; - -import androidx.annotation.Nullable; -import androidx.lifecycle.ViewModelProviders; - -import java.util.ArrayList; -import java.util.List; - -import cn.wildfire.chat.kit.contact.BaseContactFragment; -import cn.wildfire.chat.kit.contact.ContactAdapter; -import cn.wildfire.chat.kit.contact.ContactViewModel; -import cn.wildfire.chat.kit.contact.model.UIUserInfo; -import cn.wildfirechat.model.GroupInfo; -import cn.wildfirechat.model.GroupMember; -import cn.wildfirechat.model.UserInfo; - -public class GroupMemberListFragment extends BaseContactFragment { - private GroupInfo groupInfo; - - public static GroupMemberListFragment newInstance(GroupInfo groupInfo) { - Bundle args = new Bundle(); - args.putParcelable("groupInfo", groupInfo); - GroupMemberListFragment fragment = new GroupMemberListFragment(); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - groupInfo = getArguments().getParcelable("groupInfo"); - } - - @Override - public ContactAdapter onCreateContactAdapter() { - ContactAdapter contactAdapter = new ContactAdapter(this); - - GroupViewModel groupViewModel = ViewModelProviders.of(getActivity()).get(GroupViewModel.class); - List members = groupViewModel.getGroupMembers(groupInfo.target, false); - List memberIds = new ArrayList<>(members.size()); - for (GroupMember member : members) { - memberIds.add(member.memberId); - } - ContactViewModel contactViewModel = ViewModelProviders.of(getActivity()).get(ContactViewModel.class); - List userInfos = contactViewModel.getContacts(memberIds); - for (GroupMember member : members) { - for (UserInfo userInfo : userInfos) { - if (!TextUtils.isEmpty(member.alias) && member.memberId.equals(userInfo.uid)) { - userInfo.displayName = member.alias; - break; - } - } - } - List contacts = userInfoToUIUserInfo(userInfos); - contactAdapter.setContacts(contacts); - - return contactAdapter; - } - - @Override - public void onContactClick(UIUserInfo userInfo) { - Intent intent = new Intent(); - intent.putExtra("userId", userInfo.getUserInfo().uid); - getActivity().setResult(Activity.RESULT_OK, intent); - getActivity().finish(); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/group/GroupViewModel.java b/chat/src/main/java/cn/wildfire/chat/kit/group/GroupViewModel.java deleted file mode 100644 index 2ee5a5cb29a79d4732a1bc925fc45b4f869e3608..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/group/GroupViewModel.java +++ /dev/null @@ -1,368 +0,0 @@ -package cn.wildfire.chat.kit.group; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; - -import androidx.annotation.Nullable; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; - -import com.bumptech.glide.Glide; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import cn.wildfire.chat.kit.ChatManagerHolder; -import cn.wildfire.chat.kit.common.AppScopeViewModel; -import cn.wildfire.chat.kit.common.OperateResult; -import cn.wildfire.chat.kit.contact.model.UIUserInfo; -import cn.wildfire.chat.kit.third.utils.FileUtils; -import cn.wildfire.chat.kit.utils.portrait.CombineBitmapTools; -import cn.wildfirechat.message.MessageContentMediaType; -import cn.wildfirechat.message.notification.AddGroupMemberNotificationContent; -import cn.wildfirechat.message.notification.CreateGroupNotificationContent; -import cn.wildfirechat.message.notification.KickoffGroupMemberNotificationContent; -import cn.wildfirechat.message.notification.ModifyGroupAliasNotificationContent; -import cn.wildfirechat.message.notification.NotificationMessageContent; -import cn.wildfirechat.message.notification.QuitGroupNotificationContent; -import cn.wildfirechat.model.GroupInfo; -import cn.wildfirechat.model.GroupMember; -import cn.wildfirechat.model.ModifyGroupInfoType; -import cn.wildfirechat.model.UserInfo; -import cn.wildfirechat.remote.ChatManager; -import cn.wildfirechat.remote.GeneralCallback; -import cn.wildfirechat.remote.GeneralCallback2; -import cn.wildfirechat.remote.GetGroupsCallback; -import cn.wildfirechat.remote.OnGroupInfoUpdateListener; -import cn.wildfirechat.remote.OnGroupMembersUpdateListener; -import cn.wildfirechat.remote.UserSettingScope; - -public class GroupViewModel extends ViewModel implements AppScopeViewModel, OnGroupInfoUpdateListener, OnGroupMembersUpdateListener { - private MutableLiveData> groupInfoUpdateLiveData; - private MutableLiveData> groupMembersUpdateLiveData; - - public GroupViewModel() { - super(); - ChatManager.Instance().addGroupInfoUpdateListener(this); - ChatManager.Instance().addGroupMembersUpdateListener(this); - } - - @Override - protected void onCleared() { - ChatManager.Instance().removeGroupInfoUpdateListener(this); - ChatManager.Instance().removeGroupMembersUpdateListener(this); - } - - public MutableLiveData> groupInfoUpdateLiveData() { - if (groupInfoUpdateLiveData == null) { - groupInfoUpdateLiveData = new MutableLiveData<>(); - } - return groupInfoUpdateLiveData; - } - - public MutableLiveData> groupMembersUpdateLiveData() { - if (groupMembersUpdateLiveData == null) { - groupMembersUpdateLiveData = new MutableLiveData<>(); - } - return groupMembersUpdateLiveData; - } - - @Override - public void onGroupInfoUpdate(List groupInfos) { - if (groupInfoUpdateLiveData != null) { - groupInfoUpdateLiveData.setValue(groupInfos); - } - } - - public MutableLiveData> createGroup(Context context, List checkedUsers) { - List selectedIds = new ArrayList<>(checkedUsers.size()); - for (UIUserInfo userInfo : checkedUsers) { - selectedIds.add(userInfo.getUserInfo().uid); - } - selectedIds.add(ChatManager.Instance().getUserId()); - String mGroupName = ""; - if (checkedUsers.size() > 3) { - for (int i = 0; i < 3; i++) { - UserInfo friend = checkedUsers.get(i).getUserInfo(); - mGroupName += friend.displayName + "、"; - } - } else { - for (UIUserInfo friend : checkedUsers) { - mGroupName += friend.getUserInfo().displayName + "、"; - } - } - mGroupName = mGroupName.substring(0, mGroupName.length() - 1); - - - CreateGroupNotificationContent notifyCnt = new CreateGroupNotificationContent(); - notifyCnt.creator = ChatManager.Instance().getUserId(); - notifyCnt.groupName = mGroupName; - notifyCnt.fromSelf = true; - - MutableLiveData> groupLiveData = new MutableLiveData<>(); - ChatManager.Instance().getWorkHandler().post(() -> { - String groupPortrait = null; - try { - groupPortrait = generateGroupPortrait(context, checkedUsers); - } catch (Exception e) { - e.printStackTrace(); - } - if (groupPortrait != null) { - byte[] content = FileUtils.readFile(groupPortrait); - ChatManager.Instance().uploadMedia(content, MessageContentMediaType.PORTRAIT.getValue(), new GeneralCallback2() { - @Override - public void onSuccess(String result) { - ChatManager.Instance().createGroup(null, notifyCnt.groupName, result, selectedIds, Arrays.asList(0), notifyCnt, new GeneralCallback2() { - @Override - public void onSuccess(String groupId) { - groupLiveData.setValue(new OperateResult<>(groupId, 0)); - } - - @Override - public void onFail(int errorCode) { - groupLiveData.setValue(new OperateResult<>(errorCode)); - } - }); - } - - @Override - public void onFail(int errorCode) { - groupLiveData.setValue(new OperateResult<>("上传群头像失败", errorCode)); - } - }); - } else { - ChatManager.Instance().createGroup(null, notifyCnt.groupName, null, selectedIds, Arrays.asList(0), notifyCnt, new GeneralCallback2() { - @Override - public void onSuccess(String groupId) { - groupLiveData.setValue(new OperateResult<>(groupId, 0)); - } - - @Override - public void onFail(int errorCode) { - groupLiveData.setValue(new OperateResult<>(errorCode)); - } - }); - } - }); - return groupLiveData; - } - - public MutableLiveData addGroupMember(GroupInfo groupInfo, List memberIds) { - AddGroupMemberNotificationContent notificationContent = new AddGroupMemberNotificationContent(); - notificationContent.fromSelf = true; - notificationContent.invitor = ChatManager.Instance().getUserId(); - notificationContent.invitees = memberIds; - MutableLiveData result = new MutableLiveData<>(); - // TODO need update group portrait or not? - ChatManager.Instance().addGroupMembers(groupInfo.target, memberIds, Arrays.asList(0), notificationContent, new GeneralCallback() { - @Override - public void onSuccess() { - if (groupInfoUpdateLiveData != null) { - groupInfo.memberCount -= groupInfo.memberCount + memberIds.size(); - groupInfoUpdateLiveData.setValue(Collections.singletonList(groupInfo)); - } - result.setValue(true); - } - - @Override - public void onFail(int errorCode) { - result.setValue(false); - } - }); - - return result; - } - - public MutableLiveData removeGroupMember(GroupInfo groupInfo, List memberIds) { - KickoffGroupMemberNotificationContent notifyCnt = new KickoffGroupMemberNotificationContent(); - notifyCnt.operator = ChatManager.Instance().getUserId(); - notifyCnt.kickedMembers = memberIds; - notifyCnt.fromSelf = true; - - MutableLiveData result = new MutableLiveData<>(); - ChatManagerHolder.gChatManager.removeGroupMembers(groupInfo.target, memberIds, Arrays.asList(0), notifyCnt, new GeneralCallback() { - @Override - public void onSuccess() { - if (groupInfoUpdateLiveData != null) { - groupInfo.memberCount -= groupInfo.memberCount + memberIds.size(); - groupInfoUpdateLiveData.setValue(Collections.singletonList(groupInfo)); - } - result.setValue(true); - } - - @Override - public void onFail(int errorCode) { - result.setValue(false); - } - }); - - return result; - } - - public @Nullable - GroupInfo getGroupInfo(String groupId, boolean refresh) { - return ChatManager.Instance().getGroupInfo(groupId, refresh); - } - - public List getGroupMembers(String groupId, boolean forceRefresh) { - return ChatManager.Instance().getGroupMembers(groupId, forceRefresh); - } - - public GroupMember getGroupMember(String groupId, String memberId) { - return ChatManager.Instance().getGroupMember(groupId, memberId); - } - - // 优先级如下: - // 1. 群备注 2. 好友备注 3. 用户displayName 4. - public String getGroupMemberDisplayName(String groupId, String memberId) { - GroupMember groupMember = ChatManager.Instance().getGroupMember(groupId, memberId); - if (groupMember != null && !TextUtils.isEmpty(groupMember.alias)) { - return groupMember.alias; - } - - String alias = ChatManager.Instance().getFriendAlias(memberId); - if (!TextUtils.isEmpty(alias)) { - return alias; - } - UserInfo userInfo = ChatManager.Instance().getUserInfo(memberId, false); - if (userInfo != null && !TextUtils.isEmpty(userInfo.displayName)) { - return userInfo.displayName; - } - return "<" + memberId + ">"; - } - - public MutableLiveData>> getMyGroups() { - MutableLiveData>> result = new MutableLiveData<>(); - ChatManager.Instance().getMyGroups(new GetGroupsCallback() { - @Override - public void onSuccess(List groupInfos) { - result.setValue(new OperateResult<>(groupInfos, 0)); - } - - @Override - public void onFail(int errorCode) { - result.setValue(new OperateResult<>(null, 0)); - } - }); - return result; - } - - public MutableLiveData> modifyGroupInfo(String groupId, ModifyGroupInfoType modifyType, String newValue, NotificationMessageContent notifyMsg) { - MutableLiveData> result = new MutableLiveData<>(); - ChatManager.Instance().modifyGroupInfo(groupId, modifyType, newValue, Collections.singletonList(0), null, new GeneralCallback() { - @Override - public void onSuccess() { - result.setValue(new OperateResult<>(true, 0)); - } - - @Override - public void onFail(int errorCode) { - result.setValue(new OperateResult<>(false, errorCode)); - } - }); - return result; - } - - public MutableLiveData modifyMyGroupAlias(String groupId, String alias) { - MutableLiveData result = new MutableLiveData<>(); - ModifyGroupAliasNotificationContent content = new ModifyGroupAliasNotificationContent(); - content.fromSelf = true; - content.alias = alias; - ChatManager.Instance().modifyGroupAlias(groupId, alias, Collections.singletonList(0), content, new GeneralCallback() { - @Override - public void onSuccess() { - result.setValue(new OperateResult<>(0)); - } - - @Override - public void onFail(int errorCode) { - result.setValue(new OperateResult<>(errorCode)); - } - }); - return result; - } - - public MutableLiveData> setFavGroup(String groupId, boolean fav) { - MutableLiveData> result = new MutableLiveData<>(); - ChatManager.Instance().setUserSetting(UserSettingScope.FavoriteGroup, groupId, fav ? "1" : "0", new GeneralCallback() { - @Override - public void onSuccess() { - result.setValue(new OperateResult<>(0)); - } - - @Override - public void onFail(int errorCode) { - result.setValue(new OperateResult<>(errorCode)); - } - }); - return result; - } - - public MutableLiveData quitGroup(String groupId, List lines) { - QuitGroupNotificationContent notifyCnt = new QuitGroupNotificationContent(); - notifyCnt.operator = ChatManagerHolder.gChatManager.getUserId(); - MutableLiveData result = new MutableLiveData<>(); - ChatManager.Instance().quitGroup(groupId, lines, notifyCnt, new GeneralCallback() { - @Override - public void onSuccess() { - result.setValue(true); - } - - @Override - public void onFail(int errorCode) { - result.setValue(false); - } - }); - return result; - } - - private @Nullable - String generateGroupPortrait(Context context, List userInfos) throws Exception { - List bitmaps = new ArrayList<>(); - for (UIUserInfo userInfo : userInfos) { - try { - Drawable drawable = Glide.with(context).load(userInfo.getUserInfo().portrait).submit(60, 60).get(); - if (drawable instanceof BitmapDrawable) { - bitmaps.add(((BitmapDrawable) drawable).getBitmap()); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - Bitmap bitmap = CombineBitmapTools.combimeBitmap(context, 60, 60, bitmaps); - if (bitmap == null) { - return null; - } - //create a file to write bitmap data - File f = new File(context.getCacheDir(), System.currentTimeMillis() + ".png"); - f.createNewFile(); - - //Convert bitmap to byte array - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos); - byte[] bitmapData = bos.toByteArray(); - - //write the bytes in file - FileOutputStream fos = new FileOutputStream(f); - fos.write(bitmapData); - fos.flush(); - fos.close(); - - return f.getAbsolutePath(); - } - - @Override - public void onGroupMembersUpdate(String groupId, List groupMembers) { - if (groupMembersUpdateLiveData != null) { - groupMembersUpdateLiveData.setValue(groupMembers); - } - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/search/viewHolder/GroupViewHolder.java b/chat/src/main/java/cn/wildfire/chat/kit/search/viewHolder/GroupViewHolder.java deleted file mode 100644 index 84858c20231e94819f039f498d8db0a8e83c9d81..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/search/viewHolder/GroupViewHolder.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.wildfire.chat.kit.search.viewHolder; - -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.bumptech.glide.Glide; - -import androidx.fragment.app.Fragment; -import butterknife.Bind; -import butterknife.ButterKnife; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.model.GroupSearchResult; - -public class GroupViewHolder extends ResultItemViewHolder { - @Bind(R.id.portraitImageView) - ImageView portraitImageView; - @Bind(R.id.nameTextView) - TextView nameTextView; - @Bind(R.id.descTextView) - TextView descTextView; - - public GroupViewHolder(Fragment fragment, View itemView) { - super(fragment, itemView); - ButterKnife.bind(this, itemView); - } - - - @Override - public void onBind(String keyword, GroupSearchResult groupSearchResult) { - nameTextView.setText(groupSearchResult.groupInfo.name); - Glide.with(fragment).load(groupSearchResult.groupInfo.portrait).into(portraitImageView); - - String desc = ""; - switch (groupSearchResult.marchedType) { - case 0: - desc = "群名称包含: " + keyword; - break; - case 1: - desc = "群成员包含: " + keyword; - break; - case 2: - desc = "群名称和群成员都包含: " + keyword; - break; - default: - break; - } - descTextView.setText(desc); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/setting/SettingActivity.java b/chat/src/main/java/cn/wildfire/chat/kit/setting/SettingActivity.java deleted file mode 100644 index 5d5bb0666560875b7b864fcb380ae0a5666da4bf..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/setting/SettingActivity.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.wildfire.chat.kit.setting; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; - -import butterknife.OnClick; -import cn.wildfire.chat.kit.ChatManagerHolder; -import cn.wildfire.chat.kit.WfcBaseActivity; -import cn.wildfire.chat.app.main.SplashActivity; -import cn.wildfirechat.chat.R; - -public class SettingActivity extends WfcBaseActivity { - - @Override - protected int contentLayout() { - return R.layout.setting_activity; - } - - @OnClick(R.id.exitOptionItemView) - void exit() { - ChatManagerHolder.gChatManager.disconnect(true); - SharedPreferences sp = getSharedPreferences("config", Context.MODE_PRIVATE); - sp.edit().clear().apply(); - - Intent intent = new Intent(this, SplashActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - finish(); - } - - @OnClick(R.id.newMsgNotifyOptionItemView) - void notifySetting() { - - } - - @OnClick(R.id.aboutOptionItemView) - void about() { - Intent intent = new Intent(this, AboutActivity.class); - startActivity(intent); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/third/utils/TimeUtils.java b/chat/src/main/java/cn/wildfire/chat/kit/third/utils/TimeUtils.java deleted file mode 100644 index 677efee7efa26abc943ba4cc286a8010697e0b1f..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/third/utils/TimeUtils.java +++ /dev/null @@ -1,79 +0,0 @@ -package cn.wildfire.chat.kit.third.utils; - -import org.joda.time.DateTime; -import org.joda.time.DateTimeConstants; -import org.joda.time.Days; - -import androidx.annotation.NonNull; - -/** - * @创建者 CSDN_LQR - * @描述 时间工具(需要joda-time) - */ -public class TimeUtils { - - /** - * 得到仿微信日期格式输出 - * - * @param msgTimeMillis - * @return - */ - public static String getMsgFormatTime(long msgTimeMillis) { - DateTime nowTime = new DateTime(); -// LogUtils.sf("nowTime = " + nowTime); - DateTime msgTime = new DateTime(msgTimeMillis); -// LogUtils.sf("msgTime = " + msgTime); - int days = Math.abs(Days.daysBetween(msgTime, nowTime).getDays()); -// LogUtils.sf("days = " + days); - if (days < 1) { - //早上、下午、晚上 1:40 - return getTime(msgTime); - } else if (days == 1) { - //昨天 - return "昨天 " + getTime(msgTime); - } else if (days <= 7) { - //星期 - switch (msgTime.getDayOfWeek()) { - case DateTimeConstants.SUNDAY: - return "周日 " + getTime(msgTime); - case DateTimeConstants.MONDAY: - return "周一 " + getTime(msgTime); - case DateTimeConstants.TUESDAY: - return "周二 " + getTime(msgTime); - case DateTimeConstants.WEDNESDAY: - return "周三 " + getTime(msgTime); - case DateTimeConstants.THURSDAY: - return "周四 " + getTime(msgTime); - case DateTimeConstants.FRIDAY: - return "周五 " + getTime(msgTime); - case DateTimeConstants.SATURDAY: - return "周六 " + getTime(msgTime); - default: - break; - } - return ""; - } else { - //12月22日 - return msgTime.toString("MM月dd日 " + getTime(msgTime)); - } - } - - @NonNull - private static String getTime(DateTime msgTime) { - int hourOfDay = msgTime.getHourOfDay(); - String when; - if (hourOfDay >= 18) {//18-24 - when = "晚上"; - } else if (hourOfDay >= 13) {//13-18 - when = "下午"; - } else if (hourOfDay >= 11) {//11-13 - when = "中午"; - } else if (hourOfDay >= 5) {//5-11 - when = "早上"; - } else {//0-5 - when = "凌晨"; - } - return when + " " + msgTime.toString("hh:mm"); - } - -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/user/UserInfoActivity.java b/chat/src/main/java/cn/wildfire/chat/kit/user/UserInfoActivity.java deleted file mode 100644 index 3b8415a1f551b87d2dc37e8f37f8fdca7d1cbf97..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/user/UserInfoActivity.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.wildfire.chat.kit.user; - -import cn.wildfire.chat.kit.WfcBaseActivity; -import cn.wildfirechat.chat.R; -import cn.wildfirechat.model.UserInfo; - -public class UserInfoActivity extends WfcBaseActivity { - - @Override - protected int contentLayout() { - return R.layout.fragment_container_activity; - } - - @Override - protected void afterViews() { - UserInfo userInfo = getIntent().getParcelableExtra("userInfo"); - if (userInfo == null) { - finish(); - } else { - getSupportFragmentManager().beginTransaction() - .replace(R.id.containerFrameLayout, UserInfoFragment.newInstance(userInfo)) - .commit(); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - } -} diff --git a/chat/src/main/java/cn/wildfire/chat/kit/voip/FloatingVoipService.java b/chat/src/main/java/cn/wildfire/chat/kit/voip/FloatingVoipService.java deleted file mode 100644 index 35bd61c456bbe85c2621281cc15a040c79411988..0000000000000000000000000000000000000000 --- a/chat/src/main/java/cn/wildfire/chat/kit/voip/FloatingVoipService.java +++ /dev/null @@ -1,269 +0,0 @@ -package cn.wildfire.chat.kit.voip; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.os.Build; -import android.os.Handler; -import android.os.IBinder; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; - -import org.webrtc.StatsReport; - -import cn.wildfirechat.avenginekit.AVEngineKit; -import cn.wildfirechat.chat.R; - -import static org.webrtc.RendererCommon.ScalingType.SCALE_ASPECT_BALANCED; - -public class FloatingVoipService extends Service { - private static boolean isStarted = false; - private static final int NOTIFICATION_ID = 1; - - private WindowManager wm; - private View view; - private WindowManager.LayoutParams params; - private AVEngineKit.CallSession session; - private Intent resumeActivityIntent; - - private Handler handler = new Handler(); - - @Override - public void onCreate() { - super.onCreate(); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (isStarted) { - return START_NOT_STICKY; - } - - isStarted = true; - session = AVEngineKit.Instance().getCurrentSession(); - if (session == null || AVEngineKit.CallState.Idle == session.getState()) { - stopSelf(); - } - - resumeActivityIntent = new Intent(this, SingleVoipCallActivity.class); - resumeActivityIntent.putExtra(SingleVoipCallActivity.EXTRA_FROM_FLOATING_VIEW, true); - resumeActivityIntent.putExtra(SingleVoipCallActivity.EXTRA_MO, intent.getBooleanExtra(SingleVoipCallActivity.EXTRA_MO, false)); - resumeActivityIntent.putExtra(SingleVoipCallActivity.EXTRA_AUDIO_ONLY, intent.getBooleanExtra(SingleVoipCallActivity.EXTRA_AUDIO_ONLY, false)); - resumeActivityIntent.putExtra(SingleVoipCallActivity.EXTRA_TARGET, intent.getStringExtra(SingleVoipCallActivity.EXTRA_TARGET)); - resumeActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, resumeActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - String channelId = ""; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - channelId = "cn.wildfirechat.chat.voip"; - String channelName = "voip"; - NotificationChannel chan = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH); - chan.setLightColor(Color.BLUE); - chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); - NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - manager.createNotificationChannel(chan); - } - - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId); - - builder.setSmallIcon(R.mipmap.ic_launcher) - .setContentTitle("通话中...") - .setContentIntent(pendingIntent) - .setOngoing(true) - .build(); - startForeground(NOTIFICATION_ID, builder.build()); - try { - showFloatingWindow(); - } catch (Exception e) { - e.printStackTrace(); - } - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - super.onDestroy(); - wm.removeView(view); - isStarted = false; - } - - private void showFloatingWindow() { - wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - params = new WindowManager.LayoutParams(); - - int type; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - } else { - type = WindowManager.LayoutParams.TYPE_PHONE; - } - params.type = type; - params.flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - - params.format = PixelFormat.TRANSLUCENT; - params.width = ViewGroup.LayoutParams.WRAP_CONTENT; - params.height = ViewGroup.LayoutParams.WRAP_CONTENT; - params.gravity = Gravity.CENTER; - params.x = getResources().getDisplayMetrics().widthPixels; - params.y = 0; - - view = LayoutInflater.from(this).inflate(R.layout.av_voip_float_view, null); - view.setOnTouchListener(onTouchListener); - wm.addView(view, params); - if (session.isAudioOnly()) { - showAudioInfo(); - } else { - showVideoInfo(); - } - session.setCallback(new AVEngineKit.CallSessionCallback() { - @Override - public void didCallEndWithReason(AVEngineKit.CallEndReason reason) { - hideFloatBox(); - } - - @Override - public void didChangeState(AVEngineKit.CallState state) { - - } - - @Override - public void didChangeMode(boolean audioOnly) { - handler.post(() -> showAudioInfo()); - } - - @Override - public void didCreateLocalVideoTrack() { - - } - - @Override - public void didReceiveRemoteVideoTrack() { - - } - - @Override - public void didError(String error) { - hideFloatBox(); - } - - @Override - public void didGetStats(StatsReport[] reports) { - - } - }); - } - - public void hideFloatBox() { - stopSelf(); - } - - private void showAudioInfo() { - FrameLayout remoteVideoFrameLayout = view.findViewById(R.id.remoteVideoFrameLayout); - if (remoteVideoFrameLayout.getVisibility() == View.VISIBLE) { - session.setupRemoteVideo(null, SCALE_ASPECT_BALANCED); - remoteVideoFrameLayout.setVisibility(View.GONE); - wm.removeView(view); - wm.addView(view, params); -// wm.updateViewLayout(view, params); - } - - view.findViewById(R.id.audioLinearLayout).setVisibility(View.VISIBLE); - TextView timeV = view.findViewById(R.id.durationTextView); - ImageView mediaIconV = view.findViewById(R.id.av_media_type); - mediaIconV.setImageResource(R.drawable.av_float_audio); - refreshCallDurationInfo(timeV); - } - - private void showVideoInfo() { - view.findViewById(R.id.audioLinearLayout).setVisibility(View.GONE); - FrameLayout remoteVideoFrameLayout = view.findViewById(R.id.remoteVideoFrameLayout); - remoteVideoFrameLayout.setVisibility(View.VISIBLE); - SurfaceView surfaceView = session.createRendererView(); - if (surfaceView != null) { - remoteVideoFrameLayout.addView(surfaceView); - session.setupRemoteVideo(surfaceView, SCALE_ASPECT_BALANCED); - } - } - - private void clickToResume() { - startActivity(resumeActivityIntent); - } - - private void refreshCallDurationInfo(TextView timeView) { - AVEngineKit.CallSession session = AVEngineKit.Instance().getCurrentSession(); - if (session == null || !session.isAudioOnly()) { - return; - } - - long duration = (System.currentTimeMillis() - session.getStartTime()) / 1000; - if (duration >= 3600) { - timeView.setText(String.format("%d:%02d:%02d", duration / 3600, (duration % 3600) / 60, (duration % 60))); - } else { - timeView.setText(String.format("%02d:%02d", (duration % 3600) / 60, (duration % 60))); - } - handler.postDelayed(() -> refreshCallDurationInfo(timeView), 1000); - } - - View.OnTouchListener onTouchListener = new View.OnTouchListener() { - float lastX, lastY; - int oldOffsetX, oldOffsetY; - int tag = 0; - - @Override - public boolean onTouch(View v, MotionEvent event) { - final int action = event.getAction(); - float x = event.getX(); - float y = event.getY(); - if (tag == 0) { - oldOffsetX = params.x; - oldOffsetY = params.y; - } - if (action == MotionEvent.ACTION_DOWN) { - lastX = x; - lastY = y; - } else if (action == MotionEvent.ACTION_MOVE) { - // 减小偏移量,防止过度抖动 - params.x += (int) (x - lastX) / 3; - params.y += (int) (y - lastY) / 3; - tag = 1; - wm.updateViewLayout(v, params); - } else if (action == MotionEvent.ACTION_UP) { - int newOffsetX = params.x; - int newOffsetY = params.y; - if (Math.abs(oldOffsetX - newOffsetX) <= 20 && Math.abs(oldOffsetY - newOffsetY) <= 20) { - clickToResume(); - hideFloatBox(); - } else { - tag = 0; - } - } - return true; - } - }; -} diff --git a/chat/src/main/res/color/bottom_nav_color.xml b/chat/src/main/res/color/bottom_nav_color.xml index 8e924e9063c710f25b9b0fe07235f38a2ed167f7..9f2cb78fb426fc36dc3f07c46121245c963f1ed0 100644 --- a/chat/src/main/res/color/bottom_nav_color.xml +++ b/chat/src/main/res/color/bottom_nav_color.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/emojilibrary/src/main/res/drawable/selector_stick_top_item.xml b/chat/src/main/res/drawable/bottom_work_menu_selector.xml old mode 100755 new mode 100644 similarity index 36% rename from emojilibrary/src/main/res/drawable/selector_stick_top_item.xml rename to chat/src/main/res/drawable/bottom_work_menu_selector.xml index e3c12e899bed1b34d052cb91379e653da4b9692f..0f567fc4b00d84f138100e504a40a584ca837179 --- a/emojilibrary/src/main/res/drawable/selector_stick_top_item.xml +++ b/chat/src/main/res/drawable/bottom_work_menu_selector.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/chat/src/main/res/drawable/img_bubble_send.9.png b/chat/src/main/res/drawable/img_bubble_send.9.png deleted file mode 100644 index d52d8a46031f7d21cef71056ceaf1fc6750df5be..0000000000000000000000000000000000000000 Binary files a/chat/src/main/res/drawable/img_bubble_send.9.png and /dev/null differ diff --git a/chat/src/main/res/drawable/round_bg_8_white.xml b/chat/src/main/res/drawable/round_bg_8_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..4ab5f98e67be1d96c957027ee91bae2a9001e98f --- /dev/null +++ b/chat/src/main/res/drawable/round_bg_8_white.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/chat/src/main/res/layout/account_activity.xml b/chat/src/main/res/layout/account_activity.xml new file mode 100644 index 0000000000000000000000000000000000000000..5ae989c579248c5b7371336c5d003412e6238243 --- /dev/null +++ b/chat/src/main/res/layout/account_activity.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/chat/src/main/res/layout/activity_about.xml b/chat/src/main/res/layout/activity_about.xml index 55c9b874f26d3e4cee567c3969e2abbb36034afc..f873d9d2c2d5a7c053042b9a340c92f327f1d9c9 100644 --- a/chat/src/main/res/layout/activity_about.xml +++ b/chat/src/main/res/layout/activity_about.xml @@ -29,7 +29,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="2dp" - android:text="@string/app_name_in_about" + android:text="@string/app_name" android:textColor="@color/gray2" android:textSize="18sp" /> @@ -46,44 +46,21 @@ android:background="@color/white" android:orientation="vertical"> - + app:title="@string/function_introduction" /> - - - - - - - + app:title="用户协议" /> - + app:show_divider="false" + app:title="隐私政策" /> diff --git a/chat/src/main/res/layout/conversation_item_notification_containr.xml b/chat/src/main/res/layout/activity_diagnose.xml similarity index 34% rename from chat/src/main/res/layout/conversation_item_notification_containr.xml rename to chat/src/main/res/layout/activity_diagnose.xml index 757b50da9e02963f55bdedbd4cf8eacfaae2ba28..53c2e737420b2fdc717e6bffe70bfdcf55805f60 100644 --- a/chat/src/main/res/layout/conversation_item_notification_containr.xml +++ b/chat/src/main/res/layout/activity_diagnose.xml @@ -1,32 +1,37 @@ - + + +