├── .github └── workflows │ └── android.yml ├── .gitignore ├── .idea ├── caches │ ├── build_file_checksums.ser │ └── gradle_models.ser ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── hello │ │ └── leavesC │ │ └── chat │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── hello │ │ │ └── leavesC │ │ │ └── chat │ │ │ ├── ChatApplication.java │ │ │ ├── adapter │ │ │ ├── ChatAdapter.java │ │ │ ├── ConversationAdapter.java │ │ │ ├── FriendAdapter.java │ │ │ ├── GroupListAdapter.java │ │ │ ├── GroupMembersAdapter.java │ │ │ ├── SelectFriendAdapter.java │ │ │ └── SystemMessageAdapter.java │ │ │ ├── business │ │ │ └── InitBusiness.java │ │ │ ├── cache │ │ │ ├── FriendCache.java │ │ │ └── GroupCache.java │ │ │ ├── model │ │ │ ├── BaseConversation.java │ │ │ ├── BaseMessage.java │ │ │ ├── BaseProfile.java │ │ │ ├── ChatConversation.java │ │ │ ├── DefaultMessage.java │ │ │ ├── FriendProfile.java │ │ │ ├── GroupProfile.java │ │ │ ├── GroupTipsMessage.java │ │ │ ├── SystemConversation.java │ │ │ ├── SystemGroupMessage.java │ │ │ ├── SystemProfileTipsMessage.java │ │ │ ├── SystemSnsMessage.java │ │ │ └── TextMessage.java │ │ │ ├── utils │ │ │ ├── ConversationComparator.java │ │ │ ├── FriendProfileComparator.java │ │ │ ├── LetterUtil.java │ │ │ ├── MessageFactory.java │ │ │ ├── SpannableStringUtil.java │ │ │ ├── SystemMessageComparator.java │ │ │ └── TimeUtil.java │ │ │ └── view │ │ │ ├── MainActivity.java │ │ │ ├── base │ │ │ ├── BaseActivity.java │ │ │ └── BaseFragment.java │ │ │ ├── chat │ │ │ └── ChatActivity.java │ │ │ ├── contacts │ │ │ ├── AlterFriendRemarkActivity.java │ │ │ ├── ContactsFragment.java │ │ │ └── FriendProfileActivity.java │ │ │ ├── conversation │ │ │ ├── ConversationFragment.java │ │ │ └── SystemMessageListActivity.java │ │ │ ├── friendship │ │ │ ├── SearchUserActivity.java │ │ │ └── SearchUserResultActivity.java │ │ │ ├── group │ │ │ ├── GroupListActivity.java │ │ │ ├── GroupMemberProfileActivity.java │ │ │ ├── GroupMembersActivity.java │ │ │ ├── GroupProfileActivity.java │ │ │ ├── GroupProfileModifyActivity.java │ │ │ ├── InviteGroupMemberActivity.java │ │ │ └── SelectFriendToCreateGroupActivity.java │ │ │ ├── me │ │ │ ├── AboutActivity.java │ │ │ ├── AppIntroductionActivity.java │ │ │ ├── MeFragment.java │ │ │ └── ModifyInfoActivity.java │ │ │ └── open │ │ │ ├── LoginActivity.java │ │ │ ├── OpenActivity.java │ │ │ └── RegisterActivity.java │ └── res │ │ ├── drawable │ │ ├── avatar_friend.png │ │ ├── avatar_group.png │ │ ├── avatar_group_member.png │ │ ├── avatar_me.png │ │ ├── avatar_system.png │ │ ├── bird.png │ │ ├── button_blue_background.xml │ │ ├── button_red_background.xml │ │ ├── chat_more.png │ │ ├── divider.xml │ │ ├── edit_text_background.xml │ │ ├── edittext_background.xml │ │ ├── error.png │ │ ├── gender_female.png │ │ ├── gender_male.png │ │ ├── ic_launcher_background.xml │ │ ├── message_friend_background.xml │ │ ├── message_me_background.xml │ │ ├── message_normal_friend.9.png │ │ ├── message_normal_me.9.png │ │ ├── message_selected_friend.9.png │ │ ├── message_selected_me.9.png │ │ ├── point.png │ │ ├── pressed_background.xml │ │ ├── selected.png │ │ ├── text_view_hint_bg.xml │ │ └── unselected.png │ │ ├── layout │ │ ├── activity_about.xml │ │ ├── activity_alter_friend_remark.xml │ │ ├── activity_app_introduction.xml │ │ ├── activity_chat.xml │ │ ├── activity_friend_profile.xml │ │ ├── activity_group_list.xml │ │ ├── activity_group_member_profile.xml │ │ ├── activity_group_members.xml │ │ ├── activity_group_profile.xml │ │ ├── activity_group_profile_modify.xml │ │ ├── activity_invite_group_member.xml │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── activity_modify_info.xml │ │ ├── activity_open.xml │ │ ├── activity_register.xml │ │ ├── activity_search_user.xml │ │ ├── activity_search_user_result.xml │ │ ├── activity_select_friend.xml │ │ ├── activity_system_message_list.xml │ │ ├── fragment_contacts.xml │ │ ├── fragment_conversation.xml │ │ ├── fragment_me.xml │ │ ├── header_view_group.xml │ │ ├── item_conversation.xml │ │ ├── item_friend.xml │ │ ├── item_friend_select.xml │ │ ├── item_group.xml │ │ ├── item_group_member.xml │ │ ├── item_info.xml │ │ ├── item_message_friend.xml │ │ ├── item_message_me.xml │ │ ├── item_message_system_hint.xml │ │ ├── item_message_system_overall.xml │ │ ├── layout_tool_bar.xml │ │ └── layout_tool_bar_with_btn.xml │ │ ├── menu │ │ ├── chat.xml │ │ ├── main.xml │ │ └── set_remark.xml │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── hello │ └── leavesC │ └── chat │ └── ExampleUnitTest.java ├── build.gradle ├── common ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── hello │ │ └── leavesC │ │ └── common │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── hello │ │ │ └── leavesC │ │ │ └── common │ │ │ ├── common │ │ │ ├── CircleImageView.java │ │ │ ├── LetterIndexView.java │ │ │ └── OptionView.java │ │ │ ├── dialog │ │ │ ├── ListPickerDialog.java │ │ │ ├── LoadingDialog.java │ │ │ ├── MessageDialog.java │ │ │ └── SingleChoiceDialog.java │ │ │ ├── input │ │ │ ├── EmojiAdapter.java │ │ │ ├── EmojiFragment.java │ │ │ ├── EmojiKeyboard.java │ │ │ ├── model │ │ │ │ └── Emoji.java │ │ │ └── utils │ │ │ │ ├── EmojiUtils.java │ │ │ │ └── SpanStringUtils.java │ │ │ ├── recycler │ │ │ ├── common │ │ │ │ ├── CommonItemDecoration.java │ │ │ │ ├── CommonRecyclerViewAdapter.java │ │ │ │ └── CommonRecyclerViewHolder.java │ │ │ ├── util │ │ │ │ └── RecyclerViewUtil.java │ │ │ └── wrap │ │ │ │ └── WrapRecyclerViewAdapter.java │ │ │ └── utils │ │ │ └── ScreenUtils.java │ └── res │ │ ├── anim │ │ └── loading_dialog.xml │ │ ├── drawable │ │ ├── dialog_loading_bg.xml │ │ ├── emoji_unselected.png │ │ ├── loading.png │ │ ├── more.png │ │ ├── pressed_rectangle_background.xml │ │ ├── smiley_0.png │ │ ├── smiley_1.png │ │ ├── smiley_10.png │ │ ├── smiley_11.png │ │ ├── smiley_12.png │ │ ├── smiley_13.png │ │ ├── smiley_14.png │ │ ├── smiley_15.png │ │ ├── smiley_16.png │ │ ├── smiley_17.png │ │ ├── smiley_18.png │ │ ├── smiley_19.png │ │ ├── smiley_2.png │ │ ├── smiley_20.png │ │ ├── smiley_21.png │ │ ├── smiley_22.png │ │ ├── smiley_23.png │ │ ├── smiley_24.png │ │ ├── smiley_25.png │ │ ├── smiley_26.png │ │ ├── smiley_27.png │ │ ├── smiley_28.png │ │ ├── smiley_29.png │ │ ├── smiley_3.png │ │ ├── smiley_30.png │ │ ├── smiley_31.png │ │ ├── smiley_32.png │ │ ├── smiley_33.png │ │ ├── smiley_34.png │ │ ├── smiley_35.png │ │ ├── smiley_36.png │ │ ├── smiley_37.png │ │ ├── smiley_38.png │ │ ├── smiley_39.png │ │ ├── smiley_4.png │ │ ├── smiley_40.png │ │ ├── smiley_41.png │ │ ├── smiley_42.png │ │ ├── smiley_43.png │ │ ├── smiley_44.png │ │ ├── smiley_45.png │ │ ├── smiley_46.png │ │ ├── smiley_47.png │ │ ├── smiley_48.png │ │ ├── smiley_49.png │ │ ├── smiley_5.png │ │ ├── smiley_50.png │ │ ├── smiley_51.png │ │ ├── smiley_52.png │ │ ├── smiley_53.png │ │ ├── smiley_54.png │ │ ├── smiley_55.png │ │ ├── smiley_56.png │ │ ├── smiley_57.png │ │ ├── smiley_58.png │ │ ├── smiley_59.png │ │ ├── smiley_6.png │ │ ├── smiley_60.png │ │ ├── smiley_61.png │ │ ├── smiley_62.png │ │ ├── smiley_63.png │ │ ├── smiley_64.png │ │ ├── smiley_65.png │ │ ├── smiley_66.png │ │ ├── smiley_67.png │ │ ├── smiley_68.png │ │ ├── smiley_69.png │ │ ├── smiley_7.png │ │ ├── smiley_70.png │ │ ├── smiley_71.png │ │ ├── smiley_72.png │ │ ├── smiley_73.png │ │ ├── smiley_74.png │ │ ├── smiley_75.png │ │ ├── smiley_76.png │ │ ├── smiley_77.png │ │ ├── smiley_78.png │ │ ├── smiley_79.png │ │ ├── smiley_8.png │ │ ├── smiley_80.png │ │ ├── smiley_81.png │ │ ├── smiley_82.png │ │ ├── smiley_83.png │ │ ├── smiley_84.png │ │ ├── smiley_85.png │ │ ├── smiley_86.png │ │ ├── smiley_87.png │ │ ├── smiley_88.png │ │ ├── smiley_89.png │ │ ├── smiley_9.png │ │ └── sounds.png │ │ ├── layout │ │ ├── dialog_loading.xml │ │ ├── fragment_emoticon.xml │ │ ├── input_emoticon.xml │ │ ├── item_emoji.xml │ │ └── view_option.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── hello │ └── leavesC │ └── common │ └── ExampleUnitTest.java ├── doc └── key.jks ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── presenter ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── hello │ │ └── leavesC │ │ └── presenter │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── hello │ │ │ └── leavesC │ │ │ └── presenter │ │ │ ├── event │ │ │ ├── ChatActionEvent.java │ │ │ ├── ConversationActionEvent.java │ │ │ ├── FriendActionEvent.java │ │ │ ├── GlobalActionEvent.java │ │ │ ├── GroupActionEvent.java │ │ │ ├── MessageActionEvent.java │ │ │ ├── RefreshActionEvent.java │ │ │ ├── SearchUserActionEvent.java │ │ │ ├── SelfProfileActionEvent.java │ │ │ ├── SplashActionEvent.java │ │ │ └── base │ │ │ │ ├── BaseActionEvent.java │ │ │ │ ├── BaseCallbackEvent.java │ │ │ │ └── BaseEvent.java │ │ │ ├── liveData │ │ │ ├── FriendEventLiveData.java │ │ │ ├── GroupEventLiveData.java │ │ │ ├── MessageEventLiveData.java │ │ │ └── RefreshEventLiveData.java │ │ │ ├── log │ │ │ └── Logger.java │ │ │ ├── model │ │ │ ├── ConversationModel.java │ │ │ ├── GroupMemberInfo.java │ │ │ └── ProfileModel.java │ │ │ ├── utils │ │ │ └── TransformUtil.java │ │ │ └── viewModel │ │ │ ├── ChatViewModel.java │ │ │ ├── ConversationViewModel.java │ │ │ ├── GroupProfileViewModel.java │ │ │ ├── LoginViewModel.java │ │ │ ├── ModifyFriendProfileViewModel.java │ │ │ ├── ModifySelfProfileViewModel.java │ │ │ ├── RegisterViewModel.java │ │ │ ├── SearchUserViewModel.java │ │ │ ├── SelfProfileViewModel.java │ │ │ ├── SplashViewModel.java │ │ │ └── base │ │ │ ├── BaseAndroidViewModel.java │ │ │ ├── BaseViewModel.java │ │ │ └── IViewModelAction.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── hello │ └── leavesC │ └── presenter │ └── ExampleUnitTest.java ├── screenshot ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── _release_v2.2.9_2019-11-18_14_41_50.apk ├── alipay.jpg └── wechat.png ├── sdk ├── .gitignore ├── build.gradle ├── libs │ ├── imsdk.jar │ ├── imsdk_group_ext.jar │ ├── imsdk_msg_ext.jar │ ├── imsdk_sns_ext.jar │ ├── mobilepb.jar │ ├── pinyin4j-2.5.0.jar │ ├── qalsdk.jar │ ├── tls_sdk.jar │ └── wup-1.0.0-SNAPSHOT.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── hello │ │ └── leavesC │ │ └── sdk │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── hello │ │ │ └── leavesC │ │ │ └── sdk │ │ │ └── Constants.java │ ├── jniLibs │ │ ├── arm64-v8a │ │ │ ├── lib_imcore_group_ext_gyp.so │ │ │ ├── lib_imcore_jni_gyp.so │ │ │ ├── lib_imcore_msg_ext_gyp.so │ │ │ ├── lib_imcore_sns_ext_gyp.so │ │ │ ├── libgnustl_shared.so │ │ │ ├── libqalcodecwrapper.so │ │ │ ├── libqalmsfboot.so │ │ │ └── libwtcrypto.so │ │ ├── armeabi-v7a │ │ │ ├── lib_imcore_group_ext_gyp.so │ │ │ ├── lib_imcore_jni_gyp.so │ │ │ ├── lib_imcore_msg_ext_gyp.so │ │ │ ├── lib_imcore_sns_ext_gyp.so │ │ │ ├── libgnustl_shared.so │ │ │ ├── libqalcodecwrapper.so │ │ │ ├── libqalmsfboot.so │ │ │ └── libwtcrypto.so │ │ ├── armeabi │ │ │ ├── lib_imcore_group_ext_gyp.so │ │ │ ├── lib_imcore_jni_gyp.so │ │ │ ├── lib_imcore_msg_ext_gyp.so │ │ │ ├── lib_imcore_sns_ext_gyp.so │ │ │ ├── libgnustl_shared.so │ │ │ ├── libqalcodecwrapper.so │ │ │ ├── libqalmsfboot.so │ │ │ └── libwtcrypto.so │ │ ├── x86 │ │ │ ├── lib_imcore_group_ext_gyp.so │ │ │ ├── lib_imcore_jni_gyp.so │ │ │ ├── lib_imcore_msg_ext_gyp.so │ │ │ ├── lib_imcore_sns_ext_gyp.so │ │ │ ├── libgnustl_shared.so │ │ │ ├── libqalcodecwrapper.so │ │ │ ├── libqalmsfboot.so │ │ │ └── libwtcrypto.so │ │ └── x86_64 │ │ │ ├── lib_imcore_group_ext_gyp.so │ │ │ ├── lib_imcore_jni_gyp.so │ │ │ ├── lib_imcore_msg_ext_gyp.so │ │ │ ├── lib_imcore_sns_ext_gyp.so │ │ │ ├── libgnustl_shared.so │ │ │ ├── libqalcodecwrapper.so │ │ │ ├── libqalmsfboot.so │ │ │ └── libwtcrypto.so │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── hello │ └── leavesC │ └── sdk │ └── ExampleUnitTest.java └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Gradle 17 | run: ./gradlew build 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/caches/gradle_models.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/.idea/caches/gradle_models.ser -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Round 2 | > 以下内容于 2019/08/09 修改 3 | 4 | 一个简简单单的 IM 应用 5 | 6 | 这是一个简单的IM应用,写这个应用的本意只是想练练手,暂且实现了私聊和群聊功能,后边再根据用户反馈情况再来更新吧~应用完全开源,觉得还不错的同学不如点击应用内的“赞赏支持”来赞助我喝杯咖啡? 7 | 8 | ### 下载地址:[Round](screenshot/_release_v2.2.9_2019-11-18_14_41_50.apk) 9 | 10 | ![](screenshot/1.png) ![](screenshot/2.png) 11 | ![](screenshot/3.png) ![](screenshot/4.png) 12 | ![](screenshot/5.png) ![](screenshot/6.png) 13 | 14 | 15 | ### 账号 16 | 17 | 由于后台限制,目前只允许注册一百个账号,如果你发现注册不了,可以使用以下我预先注册好的几个账号 18 | 19 | - test1 20 | - test2 21 | - test3 22 | - test01 23 | - test02 24 | - test03 25 | 26 | 密码:12345678 27 | 28 | ### 赞赏支持 29 | 30 | ![](screenshot/alipay.jpg) ![](screenshot/wechat.png) 31 | 32 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -keep class com.tencent.**{*;} 23 | -dontwarn com.tencent.** 24 | -keep class tencent.**{*;} 25 | -dontwarn tencent.** 26 | -keep class qalsdk.**{*;} 27 | -dontwarn qalsdk.** 28 | 29 | -dontwarn com.tencent.bugly.** 30 | -keep public class com.tencent.bugly.**{*;} 31 | -keep class android.support.**{*;} 32 | 33 | -dontwarn net.soureceforge.pinyin4j.** 34 | -dontwarn demo.** 35 | -keep class net.sourceforge.pinyin4j.** { *;} 36 | -keep class demo.** { *;} -------------------------------------------------------------------------------- /app/src/androidTest/java/hello/leavesC/chat/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.czy.chat", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/ChatApplication.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat; 2 | 3 | import android.app.Application; 4 | 5 | import hello.leavesC.chat.business.InitBusiness; 6 | 7 | /** 8 | * 作者:leavesC 9 | * 时间:2017/11/29 20:57 10 | * 描述: 11 | * GitHub:https://github.com/leavesC 12 | * Blog:https://www.jianshu.com/u/9df45b87cfdf 13 | * QQ:1990724437 14 | */ 15 | public class ChatApplication extends Application { 16 | 17 | public static String identifier = ""; 18 | 19 | @Override 20 | public void onCreate() { 21 | super.onCreate(); 22 | InitBusiness.init(this); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/adapter/GroupListAdapter.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.adapter; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import androidx.annotation.NonNull; 6 | 7 | import java.util.List; 8 | 9 | import hello.leavesC.chat.R; 10 | import hello.leavesC.chat.model.GroupProfile; 11 | import hello.leavesC.common.recycler.common.CommonRecyclerViewAdapter; 12 | import hello.leavesC.common.recycler.common.CommonRecyclerViewHolder; 13 | 14 | /** 15 | * 作者:叶应是叶 16 | * 时间:2018/1/7 20:19 17 | * 说明:群组列表Adapter 18 | */ 19 | public class GroupListAdapter extends CommonRecyclerViewAdapter { 20 | 21 | public GroupListAdapter(Context context, List dataList) { 22 | super(context, dataList, R.layout.item_group); 23 | } 24 | 25 | @Override 26 | protected GroupProfile clone(GroupProfile data) { 27 | return new GroupProfile(data.getGroupDetailInfo(), data.getGroupBasicSelfInfo()); 28 | } 29 | 30 | @Override 31 | protected boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { 32 | return false; 33 | } 34 | 35 | @Override 36 | protected boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { 37 | return false; 38 | } 39 | 40 | @NonNull 41 | @Override 42 | protected Bundle getChangePayload(int oldItemPosition, int newItemPosition) { 43 | return new Bundle(); 44 | } 45 | 46 | @Override 47 | protected void partialBindData(CommonRecyclerViewHolder holder, @NonNull Bundle bundle) { 48 | 49 | } 50 | 51 | @Override 52 | protected void entirelyBindData(CommonRecyclerViewHolder holder, GroupProfile data, int position) { 53 | holder.setText(R.id.tv_groupList_name, data.getName()); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/adapter/GroupMembersAdapter.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.adapter; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import androidx.annotation.NonNull; 6 | 7 | import java.util.List; 8 | 9 | import hello.leavesC.chat.R; 10 | import hello.leavesC.chat.cache.FriendCache; 11 | import hello.leavesC.presenter.model.GroupMemberInfo; 12 | import hello.leavesC.common.recycler.common.CommonRecyclerViewAdapter; 13 | import hello.leavesC.common.recycler.common.CommonRecyclerViewHolder; 14 | 15 | /** 16 | * 作者:叶应是叶 17 | * 时间:2018/1/22 22:05 18 | * 说明:群组成员Adapter 19 | */ 20 | public class GroupMembersAdapter extends CommonRecyclerViewAdapter { 21 | 22 | public GroupMembersAdapter(Context context, List dataList) { 23 | super(context, dataList, R.layout.item_group_member); 24 | } 25 | 26 | @Override 27 | protected GroupMemberInfo clone(GroupMemberInfo data) { 28 | return new GroupMemberInfo(data.getIdentifier(), data.getJoinTime(), data.getRoleType()); 29 | } 30 | 31 | @Override 32 | protected boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { 33 | return false; 34 | } 35 | 36 | @Override 37 | protected boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { 38 | return false; 39 | } 40 | 41 | @NonNull 42 | @Override 43 | protected Bundle getChangePayload(int oldItemPosition, int newItemPosition) { 44 | return new Bundle(); 45 | } 46 | 47 | @Override 48 | protected void partialBindData(CommonRecyclerViewHolder holder, @NonNull Bundle bundle) { 49 | 50 | } 51 | 52 | @Override 53 | protected void entirelyBindData(CommonRecyclerViewHolder holder, GroupMemberInfo data, int position) { 54 | holder.setText(R.id.tv_groupMember_name, FriendCache.getInstance().getFriendName(data.getIdentifier())); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/adapter/SystemMessageAdapter.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.adapter; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import androidx.annotation.NonNull; 6 | 7 | import java.util.List; 8 | 9 | import hello.leavesC.chat.R; 10 | import hello.leavesC.chat.model.BaseMessage; 11 | import hello.leavesC.chat.model.SystemGroupMessage; 12 | import hello.leavesC.chat.model.SystemProfileTipsMessage; 13 | import hello.leavesC.chat.model.SystemSnsMessage; 14 | import hello.leavesC.chat.utils.TimeUtil; 15 | import hello.leavesC.common.recycler.common.CommonRecyclerViewAdapter; 16 | import hello.leavesC.common.recycler.common.CommonRecyclerViewHolder; 17 | 18 | /** 19 | * 作者:叶应是叶 20 | * 时间:2018/1/27 21:12 21 | * 说明:系统消息Adapter 22 | */ 23 | public class SystemMessageAdapter extends CommonRecyclerViewAdapter { 24 | 25 | public SystemMessageAdapter(Context context, List dataList) { 26 | super(context, dataList, R.layout.item_message_system_overall); 27 | } 28 | 29 | @Override 30 | protected BaseMessage clone(BaseMessage data) { 31 | if (data instanceof SystemGroupMessage) { 32 | return new SystemGroupMessage(data.getMessage()); 33 | } 34 | if (data instanceof SystemSnsMessage) { 35 | return new SystemSnsMessage(data.getMessage()); 36 | } 37 | if (data instanceof SystemProfileTipsMessage) { 38 | return new SystemProfileTipsMessage(data.getMessage()); 39 | } 40 | return null; 41 | } 42 | 43 | @Override 44 | protected boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { 45 | return false; 46 | } 47 | 48 | @Override 49 | protected boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { 50 | return false; 51 | } 52 | 53 | @NonNull 54 | @Override 55 | protected Bundle getChangePayload(int oldItemPosition, int newItemPosition) { 56 | return new Bundle(); 57 | } 58 | 59 | @Override 60 | protected void partialBindData(CommonRecyclerViewHolder holder, @NonNull Bundle bundle) { 61 | 62 | } 63 | 64 | @Override 65 | protected void entirelyBindData(CommonRecyclerViewHolder holder, BaseMessage data, int position) { 66 | holder.setText(R.id.tv_systemMessage_lastMsgTime, TimeUtil.formatTime(data.getMessageTime() * 1000, TimeUtil.FORMAT_YYYY_MM_DD_HH_MM_SS)) 67 | .setText(R.id.tv_systemMessage_msgContent, data.getMessageSummary()); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/business/InitBusiness.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.business; 2 | 3 | import android.app.ActivityManager; 4 | import android.app.Application; 5 | import android.content.Context; 6 | 7 | import com.squareup.leakcanary.LeakCanary; 8 | import com.tencent.imsdk.TIMLogLevel; 9 | import com.tencent.imsdk.TIMManager; 10 | import com.tencent.imsdk.TIMSdkConfig; 11 | 12 | import hello.leavesC.chat.BuildConfig; 13 | import hello.leavesC.sdk.Constants; 14 | 15 | /** 16 | * 作者:叶应是叶 17 | * 时间:2018/10/3 17:44 18 | * 描述: 19 | */ 20 | public class InitBusiness { 21 | 22 | public static void init(Application application) { 23 | initIM(application); 24 | initLeakCanary(application); 25 | } 26 | 27 | private static void initIM(Context context) { 28 | TIMSdkConfig sdkConfig = new TIMSdkConfig(Constants.SDK_APP_ID) 29 | .enableCrashReport(false) 30 | .setLogLevel(BuildConfig.isRelease ? TIMLogLevel.OFF : TIMLogLevel.WARN) 31 | .enableLogPrint(!BuildConfig.isRelease) 32 | .setLogPath("/chat/leavesC/logs/"); 33 | TIMManager.getInstance().init(context, sdkConfig); 34 | } 35 | 36 | private static void initLeakCanary(Application application) { 37 | if (LeakCanary.isInAnalyzerProcess(application)) { 38 | return; 39 | } 40 | LeakCanary.install(application); 41 | } 42 | 43 | private static boolean isMainProcess(Context context) { 44 | return context.getPackageName().equals(getCurrentProcessName(context)); 45 | } 46 | 47 | private static String getCurrentProcessName(Context context) { 48 | int pid = android.os.Process.myPid(); 49 | String processName = null; 50 | ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 51 | if (manager != null) { 52 | for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) { 53 | if (process.pid == pid) { 54 | processName = process.processName; 55 | } 56 | } 57 | } 58 | return processName; 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/model/BaseMessage.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.model; 2 | 3 | import android.text.SpannableString; 4 | 5 | import com.tencent.imsdk.TIMMessage; 6 | import com.tencent.imsdk.TIMMessageStatus; 7 | 8 | import hello.leavesC.chat.cache.FriendCache; 9 | 10 | /** 11 | * 作者:叶应是叶 12 | * 时间:2017/11/29 20:51 13 | * 说明:消息-基类 14 | */ 15 | public abstract class BaseMessage { 16 | 17 | //消息体 18 | protected TIMMessage message; 19 | 20 | //发送者账号 21 | private String sender; 22 | 23 | //发送者称呼 24 | private String senderName; 25 | 26 | //消息状态 27 | private TIMMessageStatus messageStatus; 28 | 29 | //是否系统消息 30 | private boolean isSystemMessage; 31 | 32 | BaseMessage(TIMMessage message) { 33 | this.message = message; 34 | init(); 35 | } 36 | 37 | private void init() { 38 | sender = message.getSender() == null ? "" : message.getSender(); 39 | FriendProfile friendProfile = FriendCache.getInstance().getProfile(sender); 40 | senderName = friendProfile == null ? sender : friendProfile.getName(); 41 | messageStatus = message.status(); 42 | } 43 | 44 | //获取消息摘要 45 | public abstract SpannableString getMessageSummary(); 46 | 47 | //获取消息 48 | public TIMMessage getMessage() { 49 | return message; 50 | } 51 | 52 | //消息是否自己发送的 53 | public boolean isSelf() { 54 | return message.isSelf(); 55 | } 56 | 57 | //获取消息发送时间 58 | public long getMessageTime() { 59 | return message == null ? 0 : message.timestamp(); 60 | } 61 | 62 | //消息ID 63 | public String getMsgId() { 64 | return message == null ? "" : message.getMsgId(); 65 | } 66 | 67 | public String getSender() { 68 | return sender; 69 | } 70 | 71 | public String getSenderName() { 72 | return senderName; 73 | } 74 | 75 | public TIMMessageStatus getMessageStatus() { 76 | return messageStatus; 77 | } 78 | 79 | public boolean isSystemMessage() { 80 | return isSystemMessage; 81 | } 82 | 83 | void setSystemMessage(boolean systemMessage) { 84 | isSystemMessage = systemMessage; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/model/BaseProfile.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.model; 2 | 3 | import androidx.annotation.DrawableRes; 4 | 5 | /** 6 | * 作者:叶应是叶 7 | * 时间:2017/11/29 20:52 8 | * 说明:资料-基类 9 | */ 10 | abstract class BaseProfile { 11 | 12 | abstract public String getIdentifier(); 13 | 14 | abstract public String getName(); 15 | 16 | @DrawableRes 17 | abstract public int getDefaultAvatarResource(); 18 | 19 | abstract public String getAvatarUrl(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/model/DefaultMessage.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.model; 2 | 3 | import android.text.SpannableString; 4 | 5 | import com.tencent.imsdk.TIMMessage; 6 | 7 | /** 8 | * 作者:leavesC 9 | * 时间:2019/5/17 22:18 10 | * 描述: 11 | * GitHub:https://github.com/leavesC 12 | * Blog:https://www.jianshu.com/u/9df45b87cfdf 13 | */ 14 | public class DefaultMessage extends BaseMessage { 15 | 16 | public DefaultMessage(TIMMessage message) { 17 | super(message); 18 | } 19 | 20 | @Override 21 | public SpannableString getMessageSummary() { 22 | return new SpannableString("[未知消息]"); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/model/FriendProfile.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.model; 2 | 3 | import com.tencent.imsdk.TIMFriendGenderType; 4 | import com.tencent.imsdk.TIMUserProfile; 5 | 6 | import hello.leavesC.chat.R; 7 | import hello.leavesC.chat.utils.LetterUtil; 8 | 9 | /** 10 | * 作者:叶应是叶 11 | * 时间:2017/11/29 20:52 12 | * 说明:好友资料 13 | */ 14 | public class FriendProfile extends BaseProfile { 15 | 16 | private TIMUserProfile userProfile; 17 | 18 | private boolean isSelected; 19 | 20 | public FriendProfile(TIMUserProfile userProfile) { 21 | this.userProfile = userProfile; 22 | } 23 | 24 | @Override 25 | public String getIdentifier() { 26 | return userProfile.getIdentifier(); 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | if (!"".equals(getRemark())) { 32 | return getRemark(); 33 | } else if (!"".equals(getNickname())) { 34 | return getNickname(); 35 | } 36 | return getIdentifier(); 37 | } 38 | 39 | @Override 40 | public int getDefaultAvatarResource() { 41 | return R.drawable.avatar_friend; 42 | } 43 | 44 | @Override 45 | public String getAvatarUrl() { 46 | return userProfile.getFaceUrl(); 47 | } 48 | 49 | public String getRemark() { 50 | return userProfile.getRemark(); 51 | } 52 | 53 | public String getNickname() { 54 | return userProfile.getNickName(); 55 | } 56 | 57 | public String getSelfSignature() { 58 | return userProfile.getSelfSignature(); 59 | } 60 | 61 | public TIMFriendGenderType getGender() { 62 | return userProfile.getGender(); 63 | } 64 | 65 | public String getLocation() { 66 | return userProfile.getLocation(); 67 | } 68 | 69 | public String getNameHeaderLetter() { 70 | return String.valueOf(LetterUtil.getHeaderLetter(getName())); 71 | } 72 | 73 | public TIMUserProfile getUserProfile() { 74 | return userProfile; 75 | } 76 | 77 | public boolean isSelected() { 78 | return isSelected; 79 | } 80 | 81 | public void setSelected(boolean selected) { 82 | isSelected = selected; 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/model/SystemConversation.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.model; 2 | 3 | import android.text.SpannableString; 4 | 5 | import com.tencent.imsdk.TIMConversation; 6 | import com.tencent.imsdk.TIMMessage; 7 | import com.tencent.imsdk.ext.message.TIMConversationExt; 8 | 9 | import java.util.List; 10 | 11 | import hello.leavesC.chat.utils.MessageFactory; 12 | 13 | /** 14 | * 作者:叶应是叶 15 | * 时间:2018/1/27 19:59 16 | * 说明:消息消息会话 17 | */ 18 | public class SystemConversation extends BaseConversation { 19 | 20 | private static final String TAG = "SystemConversation"; 21 | 22 | public SystemConversation(TIMConversation timConversation) { 23 | super(timConversation); 24 | } 25 | 26 | @Override 27 | protected void init() { 28 | peer = timConversation.getPeer(); 29 | conversationType = timConversation.getType(); 30 | name = "系统消息"; 31 | TIMConversationExt conversationExt = new TIMConversationExt(timConversation); 32 | List lastMessageList = conversationExt.getLastMsgs(1); 33 | if (lastMessageList.size() > 0) { 34 | lastMessage = MessageFactory.getMessage(lastMessageList.get(0)); 35 | } else { 36 | lastMessage = null; 37 | } 38 | lastMessageSummary = lastMessage == null ? new SpannableString("") : lastMessage.getMessageSummary(); 39 | lastMessageTime = lastMessage == null ? 0 : lastMessage.getMessage().timestamp(); 40 | unreadMessageNumber = timConversation == null ? 0 : conversationExt.getUnreadMessageNum(); 41 | } 42 | 43 | @Override 44 | public void setLastMessage(BaseMessage lastMessage) { 45 | this.lastMessage = lastMessage; 46 | TIMConversationExt conversationExt = new TIMConversationExt(timConversation); 47 | lastMessageSummary = lastMessage == null ? new SpannableString("") : lastMessage.getMessageSummary(); 48 | lastMessageTime = lastMessage == null ? 0 : lastMessage.getMessage().timestamp(); 49 | unreadMessageNumber = timConversation == null ? 0 : conversationExt.getUnreadMessageNum(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/model/SystemProfileTipsMessage.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.model; 2 | 3 | import android.text.SpannableString; 4 | 5 | import com.tencent.imsdk.TIMMessage; 6 | import com.tencent.imsdk.TIMProfileSystemElem; 7 | 8 | import hello.leavesC.chat.ChatApplication; 9 | import hello.leavesC.chat.cache.FriendCache; 10 | import hello.leavesC.chat.utils.SpannableStringUtil; 11 | 12 | /** 13 | * 作者:叶应是叶 14 | * 时间:2018/2/1 20:01 15 | * 说明: 16 | */ 17 | public class SystemProfileTipsMessage extends BaseMessage { 18 | 19 | private static final String TAG = "SystemProfileTipsMessage"; 20 | 21 | public SystemProfileTipsMessage(TIMMessage message) { 22 | super(message); 23 | } 24 | 25 | @Override 26 | public SpannableString getMessageSummary() { 27 | TIMProfileSystemElem profileSystemElem = (TIMProfileSystemElem) message.getElement(0); 28 | String user = profileSystemElem.getFromUser(); 29 | if (user.equals(ChatApplication.identifier)) { 30 | user = "你"; 31 | } else { 32 | user = FriendCache.getInstance().getFriendName(user); 33 | } 34 | String hint = user + " 修改了个人资料"; 35 | return SpannableStringUtil.parseForegroundColorSpan(hint, 0, user.length()); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/model/TextMessage.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.model; 2 | 3 | import android.text.SpannableString; 4 | 5 | import com.tencent.imsdk.TIMElem; 6 | import com.tencent.imsdk.TIMFaceElem; 7 | import com.tencent.imsdk.TIMMessage; 8 | import com.tencent.imsdk.TIMTextElem; 9 | import com.tencent.imsdk.ext.message.TIMMessageDraft; 10 | 11 | import java.nio.charset.Charset; 12 | 13 | /** 14 | * 作者:叶应是叶 15 | * 时间:2017/11/29 20:53 16 | * 说明:文本消息 17 | */ 18 | public class TextMessage extends BaseMessage { 19 | 20 | public TextMessage(TIMMessage message) { 21 | super(message); 22 | } 23 | 24 | public TextMessage(TIMMessageDraft draft) { 25 | super(getDraft(draft)); 26 | } 27 | 28 | public TextMessage(String content) { 29 | super(getMessage(content)); 30 | } 31 | 32 | private static TIMMessage getMessage(String content) { 33 | TIMTextElem textElem = new TIMTextElem(); 34 | textElem.setText(content); 35 | TIMMessage message = new TIMMessage(); 36 | message.addElement(textElem); 37 | return message; 38 | } 39 | 40 | private static TIMMessage getDraft(TIMMessageDraft draft) { 41 | TIMMessage message = new TIMMessage(); 42 | for (TIMElem elem : draft.getElems()) { 43 | message.addElement(elem); 44 | } 45 | return message; 46 | } 47 | 48 | @Override 49 | public SpannableString getMessageSummary() { 50 | StringBuilder result = new StringBuilder(); 51 | for (int i = 0; i < message.getElementCount(); ++i) { 52 | switch (message.getElement(i).getType()) { 53 | case Face: 54 | TIMFaceElem faceElem = (TIMFaceElem) message.getElement(i); 55 | byte[] data = faceElem.getData(); 56 | if (data != null) { 57 | result.append(new String(data, Charset.forName("UTF-8"))); 58 | } 59 | break; 60 | case Text: 61 | TIMTextElem textElem = (TIMTextElem) message.getElement(i); 62 | result.append(textElem.getText()); 63 | break; 64 | } 65 | } 66 | return new SpannableString(result); 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/utils/ConversationComparator.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.utils; 2 | 3 | import java.util.Comparator; 4 | 5 | import hello.leavesC.chat.model.BaseConversation; 6 | 7 | /** 8 | * 作者:叶应是叶 9 | * 时间:2017/12/10 13:20 10 | * 说明:会话列表按时间排序 11 | */ 12 | public class ConversationComparator implements Comparator { 13 | 14 | @Override 15 | public int compare(BaseConversation o1, BaseConversation o2) { 16 | long time1 = o1.getLastMessageTime(); 17 | long time2 = o2.getLastMessageTime(); 18 | return Long.compare(time2, time1); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/utils/FriendProfileComparator.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.utils; 2 | 3 | import java.util.Comparator; 4 | 5 | import hello.leavesC.chat.model.FriendProfile; 6 | 7 | /** 8 | * 作者:叶应是叶 9 | * 时间:2017/11/29 20:55 10 | * 说明:好友列表按称呼排序 11 | */ 12 | public class FriendProfileComparator implements Comparator { 13 | 14 | @Override 15 | public int compare(FriendProfile o1, FriendProfile o2) { 16 | String name_1 = o1.getName(); 17 | String name_2 = o2.getName(); 18 | if (name_1.equals(name_2)) { 19 | return 0; 20 | } 21 | int minLength = Math.min(name_1.length(), name_2.length()); 22 | char headerLetter_1; 23 | char headerLetter_2; 24 | for (int i = 0; i < minLength; i++) { 25 | headerLetter_1 = name_1.charAt(i); 26 | headerLetter_2 = name_2.charAt(i); 27 | if (headerLetter_1 == headerLetter_2) { 28 | continue; 29 | } 30 | return LetterUtil.getHeaderLetter(headerLetter_1) > LetterUtil.getHeaderLetter(headerLetter_2) ? 1 : -1; 31 | } 32 | return name_1.length() > name_2.length() ? 1 : -1; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/utils/MessageFactory.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.utils; 2 | 3 | import com.tencent.imsdk.TIMMessage; 4 | 5 | import hello.leavesC.chat.model.BaseMessage; 6 | import hello.leavesC.chat.model.DefaultMessage; 7 | import hello.leavesC.chat.model.GroupTipsMessage; 8 | import hello.leavesC.chat.model.SystemGroupMessage; 9 | import hello.leavesC.chat.model.SystemProfileTipsMessage; 10 | import hello.leavesC.chat.model.SystemSnsMessage; 11 | import hello.leavesC.chat.model.TextMessage; 12 | 13 | /** 14 | * 作者:叶应是叶 15 | * 时间:2017/11/29 20:58 16 | * 说明: 17 | */ 18 | public class MessageFactory { 19 | 20 | private static final String TAG = "MessageFactory"; 21 | 22 | private MessageFactory() { 23 | 24 | } 25 | 26 | public static BaseMessage getMessage(TIMMessage message) { 27 | switch (message.getElement(0).getType()) { 28 | case Text: 29 | return new TextMessage(message); 30 | case GroupTips: 31 | return new GroupTipsMessage(message); 32 | case GroupSystem: 33 | return new SystemGroupMessage(message); 34 | case SNSTips: 35 | return new SystemSnsMessage(message); 36 | case ProfileTips: 37 | return new SystemProfileTipsMessage(message); 38 | case Face: 39 | case Image: 40 | case Sound: 41 | case Location: 42 | case File: 43 | case Custom: 44 | case Video: 45 | case UGC: 46 | default: 47 | return new DefaultMessage(message); 48 | } 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/utils/SpannableStringUtil.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.utils; 2 | 3 | import android.graphics.Color; 4 | import android.text.SpannableString; 5 | import android.text.Spanned; 6 | import android.text.style.ForegroundColorSpan; 7 | 8 | /** 9 | * 作者:叶应是叶 10 | * 时间:2018/1/28 13:49 11 | * 说明: 12 | */ 13 | public class SpannableStringUtil { 14 | 15 | public static SpannableString parseForegroundColorSpan(String content, int startIndex, int endIndex) { 16 | SpannableString spannableString = new SpannableString(content); 17 | ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.parseColor("#30cfff")); 18 | spannableString.setSpan(foregroundColorSpan, startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_INCLUSIVE); 19 | return spannableString; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/utils/SystemMessageComparator.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.utils; 2 | 3 | import java.util.Comparator; 4 | 5 | import hello.leavesC.chat.model.BaseMessage; 6 | 7 | /** 8 | * 作者:叶应是叶 9 | * 时间:2018/1/27 21:57 10 | * 说明:系统消息按时间排序 11 | */ 12 | public class SystemMessageComparator implements Comparator { 13 | 14 | @Override 15 | public int compare(BaseMessage o1, BaseMessage o2) { 16 | long time1 = o1.getMessageTime(); 17 | long time2 = o2.getMessageTime(); 18 | return Long.compare(time2, time1); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/view/group/GroupListActivity.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.view.group; 2 | 3 | import android.os.Bundle; 4 | import androidx.core.content.ContextCompat; 5 | import androidx.recyclerview.widget.LinearLayoutManager; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | 8 | import com.tencent.imsdk.TIMConversationType; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | import hello.leavesC.chat.R; 15 | import hello.leavesC.chat.adapter.GroupListAdapter; 16 | import hello.leavesC.chat.cache.GroupCache; 17 | import hello.leavesC.chat.model.GroupProfile; 18 | import hello.leavesC.chat.view.base.BaseActivity; 19 | import hello.leavesC.chat.view.chat.ChatActivity; 20 | import hello.leavesC.common.recycler.common.CommonItemDecoration; 21 | import hello.leavesC.presenter.viewModel.base.BaseViewModel; 22 | 23 | /** 24 | * 作者:叶应是叶 25 | * 时间:2018/1/7 20:18 26 | * 说明:群组列表界面 27 | */ 28 | public class GroupListActivity extends BaseActivity { 29 | 30 | private List groupProfileList; 31 | 32 | private GroupListAdapter groupListAdapter; 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_group_list); 38 | setToolbarTitle("群列表"); 39 | RecyclerView rv_groupList = findViewById(R.id.rv_groupList); 40 | rv_groupList.setLayoutManager(new LinearLayoutManager(this)); 41 | rv_groupList.addItemDecoration(new CommonItemDecoration(ContextCompat.getDrawable(getContext(), R.drawable.divider), LinearLayoutManager.VERTICAL)); 42 | groupProfileList = new ArrayList<>(); 43 | groupProfileList.addAll(GroupCache.getInstance().getAllGroup()); 44 | groupListAdapter = new GroupListAdapter(this, groupProfileList); 45 | groupListAdapter.setClickListener(position -> { 46 | ChatActivity.navigation(GroupListActivity.this, groupProfileList.get(position).getIdentifier(), TIMConversationType.Group); 47 | finish(); 48 | }); 49 | rv_groupList.setAdapter(groupListAdapter); 50 | GroupCache.getInstance().observe(this, this::handle); 51 | } 52 | 53 | private void handle(Map> stringListMap) { 54 | groupProfileList.clear(); 55 | groupProfileList.addAll(GroupCache.getInstance().getAllGroup()); 56 | groupListAdapter.setData(groupProfileList); 57 | } 58 | 59 | @Override 60 | protected BaseViewModel initViewModel() { 61 | return null; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/view/me/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.view.me; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | 8 | import androidx.lifecycle.ViewModel; 9 | 10 | import butterknife.ButterKnife; 11 | import butterknife.OnClick; 12 | import hello.leavesC.chat.BuildConfig; 13 | import hello.leavesC.chat.R; 14 | import hello.leavesC.chat.view.base.BaseActivity; 15 | import hello.leavesC.common.common.OptionView; 16 | 17 | /** 18 | * 作者:叶应是叶 19 | * 时间:2018/1/21 19:28 20 | * 说明:关于 21 | */ 22 | public class AboutActivity extends BaseActivity { 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_about); 28 | setToolbarTitle("关于"); 29 | OptionView ov_version = findViewById(R.id.ov_version); 30 | ov_version.setContent("version:" + BuildConfig.VERSION_NAME + "\nbuildTime:" + BuildConfig.buildTime); 31 | ButterKnife.bind(this); 32 | } 33 | 34 | @Override 35 | protected ViewModel initViewModel() { 36 | return null; 37 | } 38 | 39 | @OnClick({R.id.ov_gitHub, R.id.ov_contact}) 40 | void onClick(View view) { 41 | switch (view.getId()) { 42 | case R.id.ov_gitHub: { 43 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/leavesC/Chat"))); 44 | break; 45 | } 46 | case R.id.ov_contact: { 47 | AppIntroductionActivity.navigation(AboutActivity.this, "联系方式", getString(R.string.about_contact)); 48 | break; 49 | } 50 | } 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/view/me/AppIntroductionActivity.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.view.me; 2 | 3 | import androidx.lifecycle.ViewModel; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.widget.TextView; 8 | 9 | import hello.leavesC.chat.R; 10 | import hello.leavesC.chat.view.base.BaseActivity; 11 | 12 | /** 13 | * 作者:叶应是叶 14 | * 时间:2018/1/21 21:25 15 | * 说明:应用简介界面 16 | */ 17 | public class AppIntroductionActivity extends BaseActivity { 18 | 19 | private static final String TITLE = "title"; 20 | 21 | private static final String CONTENT = "content"; 22 | 23 | public static void navigation(Context context, String title, String content) { 24 | Intent intent = new Intent(context, AppIntroductionActivity.class); 25 | intent.putExtra(TITLE, title); 26 | intent.putExtra(CONTENT, content); 27 | context.startActivity(intent); 28 | } 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_app_introduction); 34 | String title = getIntent().getStringExtra(TITLE); 35 | String content = getIntent().getStringExtra(CONTENT); 36 | TextView tv_introduction = findViewById(R.id.tv_appIntroduction); 37 | tv_introduction.setText(content); 38 | setToolbarTitle(title); 39 | } 40 | 41 | @Override 42 | protected ViewModel initViewModel() { 43 | return null; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/view/open/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.view.open; 2 | 3 | import androidx.lifecycle.ViewModel; 4 | import androidx.lifecycle.ViewModelProviders; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.text.TextUtils; 9 | import android.widget.EditText; 10 | 11 | import hello.leavesC.chat.ChatApplication; 12 | import hello.leavesC.chat.R; 13 | import hello.leavesC.chat.view.MainActivity; 14 | import hello.leavesC.chat.view.base.BaseActivity; 15 | import hello.leavesC.presenter.viewModel.LoginViewModel; 16 | 17 | /** 18 | * 作者:叶应是叶 19 | * 时间:2017/11/29 21:16 20 | * 说明:登录界面 21 | */ 22 | public class LoginActivity extends BaseActivity { 23 | 24 | public static final String IDENTIFIER = "identifier"; 25 | 26 | private LoginViewModel loginViewModel; 27 | 28 | public static void navToLogin(Context context, String identifier) { 29 | Intent intent = new Intent(context, LoginActivity.class); 30 | intent.putExtra(IDENTIFIER, identifier); 31 | context.startActivity(intent); 32 | } 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_login); 38 | initView(); 39 | } 40 | 41 | private void initView() { 42 | setToolbarTitle("登录"); 43 | EditText et_loginIdentifier = findViewById(R.id.et_loginIdentifier); 44 | EditText et_loginPassword = findViewById(R.id.et_loginPassword); 45 | String identifier = getIntent().getStringExtra(IDENTIFIER); 46 | if (TextUtils.isEmpty(identifier)) { 47 | identifier = loginViewModel.getLastUserIdentifier(); 48 | } 49 | et_loginIdentifier.setText(identifier); 50 | if (!TextUtils.isEmpty(et_loginIdentifier.getText())) { 51 | et_loginPassword.requestFocus(); 52 | } 53 | findViewById(R.id.btn_login).setOnClickListener(v -> { 54 | String identifier1 = et_loginIdentifier.getText().toString(); 55 | String password = et_loginPassword.getText().toString(); 56 | loginViewModel.login(identifier1, password); 57 | }); 58 | } 59 | 60 | @Override 61 | protected ViewModel initViewModel() { 62 | loginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class); 63 | loginViewModel.getLoginEventLiveData().observe(this, loginEvent -> { 64 | assert loginEvent != null; 65 | ChatApplication.identifier = loginEvent.getIdentifier(); 66 | startActivity(MainActivity.class); 67 | finish(); 68 | }); 69 | return loginViewModel; 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /app/src/main/java/hello/leavesC/chat/view/open/RegisterActivity.java: -------------------------------------------------------------------------------- 1 | package hello.leavesC.chat.view.open; 2 | 3 | import androidx.lifecycle.ViewModel; 4 | import androidx.lifecycle.ViewModelProviders; 5 | import android.os.Bundle; 6 | import android.widget.EditText; 7 | 8 | import hello.leavesC.chat.R; 9 | import hello.leavesC.chat.view.base.BaseActivity; 10 | import hello.leavesC.presenter.model.ProfileModel; 11 | import hello.leavesC.presenter.viewModel.RegisterViewModel; 12 | 13 | /** 14 | * 作者:叶应是叶 15 | * 时间:2017/11/29 21:17 16 | * 说明:注册页面 17 | */ 18 | public class RegisterActivity extends BaseActivity { 19 | 20 | private RegisterViewModel registerViewModel; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_register); 26 | initView(); 27 | } 28 | 29 | private void initView() { 30 | setToolbarTitle("注册"); 31 | findViewById(R.id.btn_register).setOnClickListener(v -> { 32 | EditText et_registerIdentifier = findViewById(R.id.et_registerIdentifier); 33 | EditText et_registerPassword = findViewById(R.id.et_registerPassword); 34 | String identifier = et_registerIdentifier.getText().toString(); 35 | String password = et_registerPassword.getText().toString(); 36 | registerViewModel.register(identifier, password); 37 | }); 38 | } 39 | 40 | @Override 41 | protected ViewModel initViewModel() { 42 | registerViewModel = ViewModelProviders.of(this).get(RegisterViewModel.class); 43 | registerViewModel.getRegSuccessLiveData().observe(this, this::handleRegisterEvent); 44 | return registerViewModel; 45 | } 46 | 47 | private void handleRegisterEvent(ProfileModel profileModel) { 48 | LoginActivity.navToLogin(this, profileModel.getIdentifier()); 49 | finish(); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatar_friend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/avatar_friend.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatar_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/avatar_group.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatar_group_member.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/avatar_group_member.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatar_me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/avatar_me.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatar_system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/avatar_system.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/bird.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_blue_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_red_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/chat_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/chat_more.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit_text_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 14 | 15 | 16 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edittext_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/error.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/gender_female.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/gender_female.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/gender_male.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/gender_male.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/message_friend_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/message_me_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/message_normal_friend.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/message_normal_friend.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/message_normal_me.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/message_normal_me.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/message_selected_friend.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/message_selected_friend.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/message_selected_me.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/message_selected_me.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/point.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/pressed_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/selected.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/text_view_hint_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/unselected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leavesCZY/Chat/584d675ada70c31f2235e5f34765496095150575/app/src/main/res/drawable/unselected.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 23 | 24 | 30 | 31 | 37 | 38 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_alter_friend_remark.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 18 | 19 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_app_introduction.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 16 | 17 | 21 | 22 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_group_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_group_members.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_group_profile_modify.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_invite_group_member.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 15 | 16 | 20 | 21 | 26 | 27 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 16 | 17 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_modify_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 18 | 19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_open.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 29 | 30 | 39 | 40 |