clark vor 11 Monaten
Commit
f1719fbb9c
100 geänderte Dateien mit 8226 neuen und 0 gelöschten Zeilen
  1. 6 0
      .gitignore
  2. 0 0
      Task
  3. 1 0
      apk.bat
  4. 1 0
      apk.sh
  5. 159 0
      app/build.gradle
  6. 93 0
      app/proguard-rules.pro
  7. 692 0
      app/src/main/AndroidManifest.xml
  8. BIN
      app/src/main/assets/style/map_night.sty
  9. 74 0
      app/src/main/java/com/bogo/Package.java
  10. 559 0
      app/src/main/java/com/bogo/android/BogoApplication.java
  11. 159 0
      app/src/main/java/com/bogo/android/account/api/AccountServiceManager.java
  12. 66 0
      app/src/main/java/com/bogo/android/account/api/LoginServiceManager.java
  13. 22 0
      app/src/main/java/com/bogo/android/account/api/MessageCodeServiceManager.java
  14. 54 0
      app/src/main/java/com/bogo/android/account/api/request/RegisterRequest.java
  15. 38 0
      app/src/main/java/com/bogo/android/account/api/request/ResetPasswordRequest.java
  16. 62 0
      app/src/main/java/com/bogo/android/account/api/response/OrganizationDTO.java
  17. 72 0
      app/src/main/java/com/bogo/android/account/api/response/PrimaryDataDTO.java
  18. 51 0
      app/src/main/java/com/bogo/android/account/api/response/SecondDataDTO.java
  19. 30 0
      app/src/main/java/com/bogo/android/account/api/response/SilentNotificationDTO.java
  20. 99 0
      app/src/main/java/com/bogo/android/account/api/response/UserDTO.java
  21. 43 0
      app/src/main/java/com/bogo/android/account/api/service/AccountService.java
  22. 36 0
      app/src/main/java/com/bogo/android/account/api/service/LoginService.java
  23. 17 0
      app/src/main/java/com/bogo/android/account/api/service/MessageCodeService.java
  24. 76 0
      app/src/main/java/com/bogo/android/account/dialog/InputUserNameDialog.java
  25. 118 0
      app/src/main/java/com/bogo/android/account/dialog/QuitAppDialog.java
  26. 61 0
      app/src/main/java/com/bogo/android/account/loader/CountryLoader.java
  27. 75 0
      app/src/main/java/com/bogo/android/account/ui/AccountSyncActivity.java
  28. 46 0
      app/src/main/java/com/bogo/android/account/ui/CountrySelectorActivity.java
  29. 80 0
      app/src/main/java/com/bogo/android/account/ui/EditEmailActivity.java
  30. 94 0
      app/src/main/java/com/bogo/android/account/ui/EditGenderActivity.java
  31. 72 0
      app/src/main/java/com/bogo/android/account/ui/EditMottoActivity.java
  32. 178 0
      app/src/main/java/com/bogo/android/account/ui/ForgotPasswordActivity.java
  33. 267 0
      app/src/main/java/com/bogo/android/account/ui/LoginActivity.java
  34. 81 0
      app/src/main/java/com/bogo/android/account/ui/PasswordActivity.java
  35. 282 0
      app/src/main/java/com/bogo/android/account/ui/ProfileActivity.java
  36. 46 0
      app/src/main/java/com/bogo/android/account/ui/QrCodeActivity.java
  37. 265 0
      app/src/main/java/com/bogo/android/account/ui/RegisterActivity.java
  38. 130 0
      app/src/main/java/com/bogo/android/account/widget/ColorBallView.java
  39. 127 0
      app/src/main/java/com/bogo/android/account/widget/MeteorWallpaperView.java
  40. 131 0
      app/src/main/java/com/bogo/android/account/widget/RainbowBallView.java
  41. 327 0
      app/src/main/java/com/bogo/android/common/App.java
  42. 25 0
      app/src/main/java/com/bogo/android/common/BindingCompat.java
  43. 216 0
      app/src/main/java/com/bogo/android/common/Global.java
  44. 27 0
      app/src/main/java/com/bogo/android/common/GlobalGlideModule.java
  45. 135 0
      app/src/main/java/com/bogo/android/common/GlobalVideoCache.java
  46. 76 0
      app/src/main/java/com/bogo/android/common/adapter/AlbumBucketListAdapter.java
  47. 186 0
      app/src/main/java/com/bogo/android/common/adapter/AlbumMediaGridAdapter.java
  48. 56 0
      app/src/main/java/com/bogo/android/common/adapter/CountrySelectorAdapter.java
  49. 124 0
      app/src/main/java/com/bogo/android/common/adapter/FileSelectorViewAdapter.java
  50. 82 0
      app/src/main/java/com/bogo/android/common/adapter/GalleryPhotoViewAdapter.java
  51. 48 0
      app/src/main/java/com/bogo/android/common/adapter/LoadMoreFooterAdapter.java
  52. 117 0
      app/src/main/java/com/bogo/android/common/adapter/MapAddressListAdapter.java
  53. 26 0
      app/src/main/java/com/bogo/android/common/adapter/holder/AlbumBucketViewHolder.java
  54. 29 0
      app/src/main/java/com/bogo/android/common/adapter/holder/AlbumItemViewHolder.java
  55. 17 0
      app/src/main/java/com/bogo/android/common/adapter/holder/ChipViewHolder.java
  56. 23 0
      app/src/main/java/com/bogo/android/common/adapter/holder/CountryViewHolder.java
  57. 27 0
      app/src/main/java/com/bogo/android/common/adapter/holder/FileItemViewHolder.java
  58. 12 0
      app/src/main/java/com/bogo/android/common/adapter/holder/FooterViewHolder.java
  59. 12 0
      app/src/main/java/com/bogo/android/common/adapter/holder/HeaderViewHolder.java
  60. 17 0
      app/src/main/java/com/bogo/android/common/adapter/holder/ImageViewHolder.java
  61. 25 0
      app/src/main/java/com/bogo/android/common/adapter/holder/LogoNameViewHolder.java
  62. 23 0
      app/src/main/java/com/bogo/android/common/adapter/holder/MapAddressViewHolder.java
  63. 16 0
      app/src/main/java/com/bogo/android/common/adapter/holder/TextViewHolder.java
  64. 16 0
      app/src/main/java/com/bogo/android/common/api/AppVersionServiceManager.java
  65. 152 0
      app/src/main/java/com/bogo/android/common/api/BaseServiceManager.java
  66. 17 0
      app/src/main/java/com/bogo/android/common/api/FileServiceManager.java
  67. 148 0
      app/src/main/java/com/bogo/android/common/api/OkHttpFactory.java
  68. 96 0
      app/src/main/java/com/bogo/android/common/api/UserServiceManager.java
  69. 60 0
      app/src/main/java/com/bogo/android/common/api/response/ApiResponse.java
  70. 37 0
      app/src/main/java/com/bogo/android/common/api/response/UploadSignDTO.java
  71. 15 0
      app/src/main/java/com/bogo/android/common/api/service/ConfigService.java
  72. 31 0
      app/src/main/java/com/bogo/android/common/api/service/FileService.java
  73. 28 0
      app/src/main/java/com/bogo/android/common/api/service/UserService.java
  74. 174 0
      app/src/main/java/com/bogo/android/common/constant/Constant.java
  75. 87 0
      app/src/main/java/com/bogo/android/common/constant/FileBucket.java
  76. 92 0
      app/src/main/java/com/bogo/android/common/constant/IntentAction.java
  77. 14 0
      app/src/main/java/com/bogo/android/common/constant/PermissionCode.java
  78. 45 0
      app/src/main/java/com/bogo/android/common/constant/ResponseCode.java
  79. 12 0
      app/src/main/java/com/bogo/android/common/constant/SessionType.java
  80. 16 0
      app/src/main/java/com/bogo/android/common/constant/SwitchState.java
  81. 19 0
      app/src/main/java/com/bogo/android/common/constant/Visible.java
  82. 77 0
      app/src/main/java/com/bogo/android/common/database/ConfigDatabase.java
  83. 28 0
      app/src/main/java/com/bogo/android/common/database/GlideVersionDatabase.java
  84. 91 0
      app/src/main/java/com/bogo/android/common/database/UserDatabase.java
  85. 15 0
      app/src/main/java/com/bogo/android/common/database/base/EmptyMigration.java
  86. 180 0
      app/src/main/java/com/bogo/android/common/database/base/PrivateRoomDatabase.java
  87. 49 0
      app/src/main/java/com/bogo/android/common/database/base/PublicRoomDatabase.java
  88. 33 0
      app/src/main/java/com/bogo/android/common/database/repository/ConfigRepository.java
  89. 20 0
      app/src/main/java/com/bogo/android/common/database/repository/GlideVersionRepository.java
  90. 58 0
      app/src/main/java/com/bogo/android/common/database/repository/UserRepository.java
  91. 38 0
      app/src/main/java/com/bogo/android/common/dialog/AlbumBucketWindow.java
  92. 73 0
      app/src/main/java/com/bogo/android/common/dialog/BlurAlterDialog.java
  93. 50 0
      app/src/main/java/com/bogo/android/common/dialog/BlurBottomSheetDialog.java
  94. 46 0
      app/src/main/java/com/bogo/android/common/dialog/CancelDialog.java
  95. 104 0
      app/src/main/java/com/bogo/android/common/dialog/CustomDialog.java
  96. 39 0
      app/src/main/java/com/bogo/android/common/dialog/CustomProgressDialog.java
  97. 38 0
      app/src/main/java/com/bogo/android/common/dialog/DownloadProgressDialog.java
  98. 42 0
      app/src/main/java/com/bogo/android/common/dialog/MediaMenuWindow.java
  99. 46 0
      app/src/main/java/com/bogo/android/common/dialog/ShowTimeDialog.java
  100. 31 0
      app/src/main/java/com/bogo/android/common/entity/Config.java

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+/bin/
+/build/
+/out/
+/.idea/
+/.gradle/
+/app/build/

+ 0 - 0
Task


+ 1 - 0
apk.bat

@@ -0,0 +1 @@
+gradlew clean assembleRelease

+ 1 - 0
apk.sh

@@ -0,0 +1 @@
+./gradlew clean assembleRelease

+ 159 - 0
app/build.gradle

@@ -0,0 +1,159 @@
+apply plugin: 'com.android.application'
+
+android {
+
+    namespace 'com.bogo.android'
+    defaultConfig {
+
+        /*
+  请修改如下您的app包名,该包名影响百度地图SDK使用,请根据包名创建地图应用
+  https://lbs.baidu.com/
+  */
+        applicationId "com.yourdomain.android"
+        minSdkVersion 26
+        targetSdkVersion 35
+        compileSdk 35
+        versionCode 440
+        versionName "4.4.0"
+
+        /*
+         修改为自己的
+         接口服务地址
+         */
+        buildConfigField("String", "API_SERVER_URL", '"http://47.239.183.4:8080"')
+
+        /*
+        修改为自己的
+        messaging服务器IP和端口
+        */
+
+        buildConfigField("String", "MESSAGING_SERVER_HOST", '"47.239.183.4"')
+        buildConfigField("Integer", "MESSAGING_SERVER_PORT", '8090')
+
+
+        buildConfigField("String", "LIVEKIT_URI", '"wss://livekit.wordok.vip"')
+
+        buildConfigField("String", "KEEP_LIVE_URL", '"https://alidocs.dingtalk.com/i/p/e3RmQ8DYbk6BbXaP/docs/jQPRqwxd3NLWjlOvOLedJYK6lrGM4795?dontjump=true"')
+
+        buildConfigField("String", "BUGLY_APP_ID", '"yours"')
+
+
+        ndk {
+            /*
+            armeabi-v7a:32位 arm芯片安装包
+            arm64-v8a:  64位 arm芯片安装包
+            x86_64:     64位 x86芯片安装包
+            x86:        32位 x86芯片安装包
+            一般模拟器和需要配置X86
+            */
+            abiFilters  "armeabi-v7a","arm64-v8a"  /*,"x86","x86_64" */
+        }
+
+        javaCompileOptions {
+            annotationProcessorOptions {
+                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
+            }
+        }
+    }
+
+   /*
+    请生成您自己的证书,然后修改如下配置
+    */
+    signingConfigs {
+        release {
+            keyAlias 'yourapp'
+            keyPassword 'yourpwd'
+            storeFile file('yourapp.jks')
+            storePassword 'yourpwd'
+        }
+    }
+
+    buildTypes {
+        release {
+            signingConfig signingConfigs.release
+            shrinkResources true
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+
+        debug {
+            signingConfig signingConfigs.release
+            shrinkResources false
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    packagingOptions {
+        resources {
+            excludes += ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt']
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility = '1.8'
+        targetCompatibility = '1.8'
+    }
+    lint {
+        abortOnError false
+        checkReleaseBuilds false
+    }
+    
+    buildFeatures {
+        viewBinding true
+    }
+
+    android.applicationVariants.configureEach { variant ->
+        variant.outputs.configureEach {
+            outputFileName = "app.apk"
+        }
+    }
+
+}
+
+
+dependencies {
+
+    implementation 'androidx.appcompat:appcompat:1.7.0'
+    implementation 'com.google.android.material:material:1.12.0'
+    implementation 'androidx.palette:palette:1.0.0'
+    implementation 'androidx.gridlayout:gridlayout:1.0.0'
+    implementation 'androidx.recyclerview:recyclerview:1.4.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
+    implementation "androidx.camera:camera-camera2:1.4.2"
+    implementation "androidx.camera:camera-view:1.4.2"
+    implementation "androidx.camera:camera-extensions:1.4.2"
+    implementation "androidx.camera:camera-lifecycle:1.4.2"
+    implementation "androidx.camera:camera-video:1.4.2"
+
+    implementation 'androidx.media3:media3-ui:1.6.0'
+    implementation 'androidx.media3:media3-exoplayer:1.6.0'
+    implementation "androidx.media3:media3-exoplayer-dash:1.6.0"
+
+    implementation 'com.github.bumptech.glide:glide:4.16.0'
+    implementation "com.github.bumptech.glide:okhttp3-integration:4.16.0"
+    annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
+    implementation 'jp.wasabeef:glide-transformations:4.3.0'
+    implementation 'io.livekit:livekit-android:2.13.0'
+    implementation 'io.livekit:livekit-android-camerax:2.13.0'
+
+
+    implementation 'com.github.chrisbanes:PhotoView:2.3.0'
+    implementation 'com.tencent.bugly:crashreport:latest.release'
+    implementation 'commons-io:commons-io:2.16.0'
+    implementation 'com.belerweb:pinyin4j:2.5.1'
+    implementation 'androidx.room:room-runtime:2.6.1'
+    annotationProcessor "androidx.room:room-compiler:2.6.1"
+
+    implementation 'com.squareup.retrofit2:retrofit:2.11.0'
+    implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
+    implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11'
+
+    implementation 'com.baidu.lbsyun:BaiduMapSDK_Map:7.6.4'
+    implementation 'com.baidu.lbsyun:BaiduMapSDK_Search:7.6.4'
+    implementation 'com.baidu.lbsyun:BaiduMapSDK_Util:7.6.4'
+    implementation 'com.baidu.lbsyun:BaiduMapSDK_Location:9.6.4'
+    implementation 'com.github.yalantis:ucrop:2.2.8-native'
+    implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
+    implementation 'com.google.protobuf:protobuf-javalite:3.25.4'
+
+}

+ 93 - 0
app/proguard-rules.pro

@@ -0,0 +1,93 @@
+-keep class **.R$* {   *;  }
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+-dontskipnonpubliclibraryclassmembers
+-keepattributes *Annotation*
+-keepattributes Signature
+
+-dontwarn cn.dreamtobe.kpswitch.**
+
+-dontwarn okio.**
+-dontwarn com.tencent.bugly.**
+-dontwarn com.baidu.**
+-dontwarn org.slf4j.**
+-dontwarn com.google.protobuf.**
+-dontwarn okhttp3.**
+-dontwarn com.bumptech.glide.**
+-dontwarn com.yalantis.ucrop**
+-dontwarn retrofit2.**
+
+-keep class retrofit2.** { *; }
+-keep class com.baidu.** {*;}
+-keep class mapsdkvi.com.** {*;}
+-keep class com.google.protobuf.** { *; }
+-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
+
+-keep class org.webrtc.** { *; }
+
+-keep class com.bogo.android.**.model.** { *; }
+-keep class com.bogo.android.**.entity.** { *; }
+-keep class com.bogo.android.**.api.request.** { *; }
+-keep class com.bogo.android.**.api.response.** { *; }
+
+
+
+-keep class mapsdkvi.com.gdi.bgl.android.java.** { *; }
+-keep class com.bogo.messaging.model.** {*;}
+-keep class vi.com.** {*;}
+-keep class com.baidu.vi.** {*;}
+-keep public class com.tencent.bugly.**{*;}
+-keep class com.yalantis.ucrop** { *; }
+-keep interface com.yalantis.ucrop** { *; }
+
+-keep class com.bumptech.glide.integration.okhttp3.OkHttpGlideModule
+-keep public class * implements com.bumptech.glide.module.GlideModule
+-keep public class * extends com.bumptech.glide.module.AppGlideModule
+-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
+  **[] $VALUES;
+  public *;
+}
+
+
+-keep public class * extends com.bumptech.glide.module.GlideModule
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends androidx.appcompat.app.AppCompatActivity
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Fragment
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+
+
+-keepclassmembers class * extends android.webkit.WebChromeClient{
+    public void openFileChooser(...);
+}
+
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+-keepclasseswithmembers class * {
+    public <init>(android.content.Context, android.util.AttributeSet);
+}
+ 
+-keepclasseswithmembers class * {
+    public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+ 
+-keepclassmembers class * extends android.app.Activity {
+   public void *(android.view.View);
+}
+ 
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+ 
+-keep class * implements android.os.Parcelable {
+  public static final android.os.Parcelable$Creator *;
+}

+ 692 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,692 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <uses-feature
+        android:name="android.hardware.camera"
+        android:required="false" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.REORDER_TASKS" />
+
+
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.hardware.sensor.accelerometer" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
+    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+    <uses-permission
+        android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
+    <uses-permission
+        android:name="android.permission.MANAGE_MEDIA_PROJECTION"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission
+        android:name="android.permission.READ_LOGS"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission
+        android:name="android.permission.CHANGE_CONFIGURATION"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+    <uses-permission
+        android:name="android.permission.READ_EXTERNAL_STORAGE"
+        android:maxSdkVersion="32" />
+    <uses-permission
+        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+        android:maxSdkVersion="32"
+        tools:ignore="ScopedStorage" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+    <uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE"/>
+
+
+    <application
+        android:name="com.bogo.android.BogoApplication"
+        android:allowBackup="true"
+        android:supportsRtl="true"
+        android:hardwareAccelerated="true"
+        android:icon="@drawable/icon_launcher"
+        android:allowTaskReparenting="true"
+        android:label="@string/app_name"
+        android:networkSecurityConfig="@xml/network_security_config"
+        android:persistent="true"
+        android:theme="@style/AppTheme"
+        tools:ignore="GoogleAppIndexingWarning">
+
+        <meta-data
+            android:name="com.baidu.lbsapi.API_KEY"
+            android:value="yours" />
+
+        <activity
+            android:name="com.bogo.android.home.ui.SplashActivity"
+            android:exported="true"
+             >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name="com.bogo.android.home.ui.HomeActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.account.ui.LoginActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustPan" />
+
+        <activity
+            android:name="com.bogo.android.account.ui.RegisterActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.home.ui.ForceOfflineAlertActivity"
+            android:launchMode="singleTask"
+            android:theme="@style/activityDialogStyle" />
+
+        <activity
+            android:name="com.bogo.android.home.ui.AppNewVersionActivity"
+            android:launchMode="singleTask"
+            android:theme="@style/activityDialogStyle" />
+
+        <activity
+            android:name="com.bogo.android.home.ui.AboutActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.account.ui.ProfileActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.account.ui.QrCodeActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.account.ui.PasswordActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.home.ui.MessageSettingActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.account.ui.EditMottoActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.account.ui.EditEmailActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.account.ui.EditGenderActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.account.ui.AccountSyncActivity"
+            android:launchMode="singleTask"
+            android:theme="@style/activityDialogStyle" />
+        <activity
+            android:name="com.bogo.android.home.ui.MixedSettingActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.moment.ui.TimelineMomentActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustNothing" />
+        <activity
+            android:name="com.bogo.android.moment.ui.MomentPublishActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustPan" />
+        <activity
+            android:name="com.bogo.android.moment.ui.MomentMessageActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.moment.ui.MomentDetailedActivity"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustNothing" />
+        <activity
+            android:name="com.bogo.android.moment.ui.MineMomentActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.moment.ui.FriendMomentActivity"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.moment.ui.MomentVisibleActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+           />
+
+        <activity
+            android:name="com.bogo.android.micro.ui.MicroServerWindowActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustNothing" />
+        <activity
+            android:name="com.bogo.android.message.ui.FriendChatActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustNothing" />
+            />
+
+        <activity
+            android:name="com.bogo.android.message.ui.ChatRecordSnapshotActivity"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustNothing" />
+        />
+
+        <activity
+            android:name="com.bogo.android.message.ui.FriendMessageTargetActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustNothing" />
+
+        <activity
+            android:name="com.bogo.android.message.ui.GroupMessageTargetActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustNothing" />
+
+
+        <activity
+            android:name="com.bogo.android.message.ui.GroupChatActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustNothing" />
+        <activity
+            android:name="com.bogo.android.message.ui.SystemMessageActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.common.ui.FileSelectorActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.message.ui.FileViewerActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.common.ui.MapLocationActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.common.ui.MapViewActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.common.ui.TextMagnifierActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.LivekitMeetingCallerActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustResize"
+            android:taskAffinity="com.bogo.webrtc.meeting"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.LivekitMeetingCalleeActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustResize"
+            android:taskAffinity="com.bogo.webrtc.meeting"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.PictureViewActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:taskAffinity="com.bogo.webrtc.meeting"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.LivekitMeetingHomeActivity"
+            android:windowSoftInputMode="adjustNothing"
+            android:screenOrientation="portrait"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.MeetingIncomingActivity"
+            android:launchMode="singleTask"
+            android:theme="@style/meetingIncomingDialogStyle" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.MeetingInviteMemberActivity"
+            android:windowSoftInputMode="adjustNothing"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.CreateDueMeetingActivity"
+            android:windowSoftInputMode="adjustNothing"
+            android:screenOrientation="portrait"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.LivekitJoinMeetingActivity"
+            android:screenOrientation="portrait"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.CreateFastMeetingActivity"
+            android:windowSoftInputMode="adjustNothing"
+            android:screenOrientation="portrait"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.MeetingRecordInfoActivity"
+            android:windowSoftInputMode="adjustNothing"
+            android:screenOrientation="portrait"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.MeetingVideoLivingActivity"
+            android:taskAffinity="com.bogo.webrtc.meeting"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.VoiceCallingActivity"
+            android:taskAffinity="com.bogo.webrtc.p2p"
+            android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout"
+            android:screenOrientation="portrait"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.VoiceIncomingCallActivity"
+            android:taskAffinity="com.bogo.webrtc.p2p"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:screenOrientation="portrait"
+            android:launchMode="singleTask" />
+
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.VideoCallingActivity"
+            android:taskAffinity="com.bogo.webrtc.p2p"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:screenOrientation="portrait"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.webrtc.ui.VideoIncomingCallActivity"
+            android:taskAffinity="com.bogo.webrtc.p2p"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:screenOrientation="portrait"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.common.ui.WebViewActivity"
+             android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.friend.ui.ContactSelectorActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" />
+        <activity
+            android:name="com.bogo.android.friend.ui.ProfileSelectorActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" />
+
+
+        <activity
+            android:name="com.bogo.android.message.ui.MessageForwardActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateHidden|adjustNothing" />
+
+        <activity
+            android:name="com.bogo.android.home.ui.AppSearchActivity"
+            android:launchMode="singleTask"
+            android:windowSoftInputMode="stateAlwaysVisible|adjustPan"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.message.ui.MessageSearchActivity"
+            android:launchMode="singleTask"
+            android:windowSoftInputMode="stateHidden|adjustPan"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.group.ui.GroupMessageSearchActivity"
+            android:launchMode="singleTask"
+            android:windowSoftInputMode="stateHidden|adjustPan"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.group.ui.CreateGroupActivity"
+            android:launchMode="singleTask"
+            android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.friend.ui.FriendRequestActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.organization.ui.OrganizationActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" />
+
+        <activity
+            android:name="com.bogo.android.group.ui.GroupDetailedActivity"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.group.ui.GroupSelectorActivity"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.friend.ui.UserDetailedActivity"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustResize" />
+
+        <activity
+            android:name="com.bogo.android.group.ui.GroupMemberListActivity"
+            android:launchMode="singleTask"
+            android:windowSoftInputMode="stateHidden"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.group.ui.GroupRobotListActivity"
+            android:launchMode="singleTask"
+            android:windowSoftInputMode="stateHidden"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.group.ui.CreateRobotActivity"
+            android:launchMode="singleTask"
+            android:windowSoftInputMode="stateHidden"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.group.ui.GroupRobotViewActivity"
+            android:launchMode="singleTask"
+            android:windowSoftInputMode="stateHidden"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.group.ui.GroupNoticeActivity"
+            android:launchMode="singleTask"
+            android:windowSoftInputMode="stateHidden"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.group.ui.GroupNoticeEditActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustResize|stateAlwaysVisible" />
+
+        <activity
+            android:name="com.bogo.android.group.ui.InviteGroupMemberActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateHidden|adjustNothing" />
+        <activity
+            android:name="com.bogo.android.group.ui.GroupListActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.micro.ui.MicroServerDetailedActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.micro.ui.MicroServerListActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.moment.ui.MomentRuleActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.friend.ui.FindFriendActivity"
+            android:launchMode="singleTask"
+            android:windowSoftInputMode="stateAlwaysVisible"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.friend.ui.NewFriendActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.micro.ui.FindMicroServerActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+
+        <activity
+            android:exported="true"
+            android:name="com.bogo.android.message.ui.MessageSharedActivity"
+            android:label="@string/label_share_to_contact"
+            android:launchMode="singleInstance"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="stateAlwaysHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="com.bogo.android.common.ui.PhotoSelectorActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name="com.bogo.android.common.ui.ScanCodeActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.common.ui.PhotoScreenActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:launchMode="singleTask" />
+        <activity
+            android:name="com.bogo.android.common.ui.PhotoGalleryActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:launchMode="singleTask" />
+        <activity
+            android:name="com.bogo.android.common.ui.VideoRecorderActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.common.ui.PhotoPreviewActivity"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait" />
+        <activity
+            android:name="com.bogo.android.common.ui.VideoPlayerActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.account.ui.CountrySelectorActivity"
+            android:configChanges="orientation"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.account.ui.ForgotPasswordActivity"
+            android:configChanges="orientation"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.emoticon.ui.EmoticonMallActivity"
+            android:configChanges="orientation"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.emoticon.ui.EmoticonPackageActivity"
+            android:configChanges="orientation"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.emoticon.ui.EmoticonManageActivity"
+            android:configChanges="orientation"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.emoticon.ui.EmoticonViewActivity"
+            android:configChanges="orientation"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.note.ui.NoteListActivity"
+            android:configChanges="orientation"
+            android:windowSoftInputMode="stateHidden|adjustPan"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.note.ui.NoteSelectorActivity"
+            android:configChanges="orientation"
+            android:windowSoftInputMode="stateHidden|adjustPan"
+            android:launchMode="singleTask" />
+
+        <activity
+            android:name="com.bogo.android.note.ui.NoteCreateActivity"
+            android:configChanges="orientation"
+            android:launchMode="singleTask" />
+        <activity
+            android:name="com.yalantis.ucrop.UCropActivity"
+            android:configChanges="orientation"
+            android:launchMode="singleTask" />
+
+        <service
+            android:name="com.bogo.android.home.service.ApkDownloaderService"
+            android:exported="false" />
+
+
+        <service
+            android:name="com.baidu.location.f"
+            android:enabled="true"
+            android:process=":location" />
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="${applicationId}.provider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/fileprovider" />
+        </provider>
+
+
+        <!-- ****************************************推送配置 begin*************************************** -->
+        <service
+            android:name="com.bogo.messaging.PushService"
+            android:foregroundServiceType="remoteMessaging"
+            android:process=":push"
+            android:exported="false" />
+
+        <provider
+            android:name="com.bogo.messaging.CacheProvider"
+            android:authorities="${applicationId}.messaging.config.provider"
+            android:exported="false" />
+
+        <!-- ****************************************推送配置 end*************************************** -->
+
+
+        <!--消息接受广播注册-->
+        <receiver
+            android:name="com.bogo.android.message.receiver.PushMessageReceiver"
+            android:exported="false">
+            <intent-filter android:priority="0x7fffffff">
+                <!-- 网络变事件action targetVersion 24之前 -->
+
+                <action android:name="com.bogo.messaging.NETWORK_CHANGED" />
+                <!-- 收到消息事件action -->
+                <action android:name="com.bogo.messaging.MESSAGE_RECEIVED" />
+                <!-- 发送sendBody完成事件action -->
+                <action android:name="com.bogo.messaging.SEND_FINISHED" />
+                <!--重新连接事件action -->
+                <action android:name="com.bogo.messaging.CONNECTION_RECOVERY" />
+                <!-- 连接关闭事件action -->
+                <action android:name="com.bogo.messaging.CONNECTION_CLOSED" />
+                <!-- 连接失败事件action -->
+                <action android:name="com.bogo.messaging.CONNECT_FAILED" />
+                <!-- 连接成功事件action-->
+                <action android:name="com.bogo.messaging.CONNECT_FINISHED" />
+                <!-- 收到replyBody事件action -->
+                <action android:name="com.bogo.messaging.REPLY_RECEIVED" />
+
+                <!-- 【可选】 一些常用的系统广播,增强pushService的复活机会-->
+                <action android:name="android.intent.action.USER_PRESENT" />
+                <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
+                <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+
+        <service
+            android:name="com.bogo.android.webrtc.service.P2PLivingNotificationService"
+            android:exported="false"
+            android:foregroundServiceType="microphone"
+             />
+
+
+        <service
+            android:name="com.bogo.android.webrtc.service.P2PIncomingNotificationService"
+            android:exported="false"
+            android:foregroundServiceType="phoneCall"
+             />
+
+        <service
+            android:name="com.bogo.android.webrtc.service.RoomLivingNotificationService"
+            android:exported="false"
+            android:foregroundServiceType="microphone"
+             />
+
+        <service
+            android:name="com.bogo.android.webrtc.service.RoomIncomingNotificationService"
+            android:exported="false"
+            android:foregroundServiceType="phoneCall"
+             />
+
+        <service
+            android:name="com.bogo.android.webrtc.service.ScreenCaptureService"
+            android:exported="false"
+            android:foregroundServiceType="mediaProjection" />
+
+        <service
+            android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
+            android:enabled="true"
+            android:exported="true">
+            <meta-data
+                android:name="autoStoreLocales"
+                android:value="true"/>
+        </service>
+
+    </application>
+</manifest>

BIN
app/src/main/assets/style/map_night.sty


+ 74 - 0
app/src/main/java/com/bogo/Package.java

@@ -0,0 +1,74 @@
+
+package com.bogo;
+
+/**
+ * 业务线包名说明
+ */
+public enum Package {
+
+    /*
+     * 长连接消息推送组件包
+     * 此包原则上不需要关注和维护
+     */
+    MESSAGING,
+
+
+    /*
+     以下为android目录下业务包
+     */
+
+    /*
+     * 当前账号相关业务
+     */
+    ACCOUNT,
+
+    /*
+     *表情包业务
+     */
+    EMOTICON,
+
+    /*
+     *好友相关业务
+     */
+    FRIEND,
+
+    /*
+     *公共的基础的业务
+     */
+    COMMON,
+
+    /*
+     *群组相关业务
+     */
+    GROUP,
+
+    /*
+     *APP首页相关业务
+     */
+    HOME,
+
+    /*
+     *消息和聊天业务业务
+     */
+    MESSAGE,
+
+    /*
+     *公众号相关业务
+     */
+    MICRO,
+
+    /*
+     *朋友圈相关业务
+     */
+    MOMENT,
+
+    /*
+     *组织部门相关业务
+     */
+    ORGANIZATION,
+
+    /*
+     *音视频通话相关业务
+     */
+    WEBRTC
+}

+ 559 - 0
app/src/main/java/com/bogo/android/BogoApplication.java

@@ -0,0 +1,559 @@
+
+package com.bogo.android;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.Application;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.media.AudioAttributes;
+import android.media.RingtoneManager;
+import android.os.Bundle;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.ActivityOptionsCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.util.Pair;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.baidu.location.LocationClient;
+import com.baidu.mapapi.SDKInitializer;
+import com.bogo.android.common.App;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.GlobalVideoCache;
+import com.bogo.android.common.constant.Constant;
+import com.bogo.android.common.database.base.PrivateRoomDatabase;
+import com.bogo.android.common.model.CloudImage;
+import com.bogo.android.common.model.CloudVideo;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.common.ui.PhotoGalleryActivity;
+import com.bogo.android.common.ui.PhotoScreenActivity;
+import com.bogo.android.common.ui.TextMagnifierActivity;
+import com.bogo.android.common.ui.VideoPlayerActivity;
+import com.bogo.android.common.util.AppSettings;
+import com.bogo.android.common.util.AppTools;
+import com.bogo.android.common.util.CloudImageLoaderFactory;
+import com.bogo.android.common.util.DebugLogger;
+import com.bogo.android.common.util.FileManager;
+import com.bogo.android.home.service.ApkDownloaderService;
+import com.bogo.android.message.entity.ChatSession;
+import com.bogo.android.webrtc.model.LivekitRoom;
+import com.bogo.android.webrtc.ui.LivekitMeetingCalleeActivity;
+import com.bogo.android.webrtc.ui.LivekitMeetingCallerActivity;
+import com.bogo.android.webrtc.ui.VideoCallingActivity;
+import com.bogo.android.webrtc.ui.VideoIncomingCallActivity;
+import com.bogo.android.webrtc.ui.VoiceCallingActivity;
+import com.bogo.android.webrtc.ui.VoiceIncomingCallActivity;
+import com.bogo.messaging.PushManager;
+import com.google.android.material.color.DynamicColors;
+import com.tencent.bugly.crashreport.CrashReport;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Stack;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class BogoApplication extends Application implements Application.ActivityLifecycleCallbacks {
+
+    private static BogoApplication context;
+
+    public static BogoApplication getInstance() {
+        return context;
+    }
+
+    private final Stack<Activity> activityList = new Stack<>();
+
+    private final AtomicInteger activeCounter = new AtomicInteger();
+
+    private GlobalVideoCache globalVideoCache;
+
+    @Override
+    public void onCreate() {
+
+        super.onCreate();
+
+        registerActivityLifecycleCallbacks(this);
+
+        context = this;
+
+        SDKInitializer.setAgreePrivacy(this,true);
+
+        LocationClient.setAgreePrivacy(true);
+
+        SDKInitializer.initialize(this);
+
+        boolean isMainProcess = isMainProcess();
+
+        initBuglyCrashReport(isMainProcess());
+
+        FileManager.initCacheFolders();
+
+        if (isMainProcess){
+            initCurrentDatabase();
+        }
+
+        AppCompatDelegate.setDefaultNightMode(AppSettings.getThemeMode());
+
+        if (isMainProcess && AppSettings.isDynamicColorEnable()){
+            DynamicColors.applyToActivitiesIfAvailable(this);
+        }
+
+        globalVideoCache = new GlobalVideoCache(this);
+    }
+
+    private static String getProcessName(int pid) {
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new FileReader("/proc/" + pid + "/cmdline"));
+            String processName = reader.readLine();
+            if (!TextUtils.isEmpty(processName)) {
+                processName = processName.trim();
+            }
+            return processName;
+        } catch (Throwable ignore) {
+        } finally {
+            IOUtils.closeQuietly(reader);
+        }
+        return null;
+    }
+
+    private void initCurrentDatabase(){
+
+        if (Global.hasLogin()){
+            PrivateRoomDatabase.init(Global.getUid());
+        }
+
+    }
+
+
+    private boolean isMainProcess() {
+        String processName = getProcessName(android.os.Process.myPid());
+        return processName == null || getPackageName().equals(processName);
+    }
+
+    private void initBuglyCrashReport(boolean isMainProcess) {
+
+        if (BuildConfig.DEBUG){
+            return;
+        }
+
+        CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(this);
+        strategy.setUploadProcess(isMainProcess);
+        CrashReport.initCrashReport(getApplicationContext(), BuildConfig.BUGLY_APP_ID, BuildConfig.DEBUG, strategy);
+
+        if (Global.hasAccount()){
+            CrashReport.setUserId(Global.getUid().toString());
+        }
+    }
+
+    public void startDownloadService(String url, File file) {
+        Intent intent = new Intent(this, ApkDownloaderService.class);
+        intent.setAction(ApkDownloaderService.ACTION_DOWNLOAD_APK_FILE);
+        intent.putExtra(Constant.ATTR_URL, url);
+        intent.putExtra(Constant.ATTR_FILE, file);
+        this.startService(intent);
+    }
+
+    public void checkNewAppVersion() {
+        Intent intent = new Intent(this, ApkDownloaderService.class);
+        intent.setAction(ApkDownloaderService.ACTION_CHECK_APP_VERSION);
+        this.startService(intent);
+    }
+
+
+
+
+    public void checkNewAppVersionQuietly() {
+        Intent intent = new Intent(this, ApkDownloaderService.class);
+        intent.setAction(ApkDownloaderService.ACTION_CHECK_APP_VERSION_QUIETLY);
+        this.startService(intent);
+    }
+
+
+    public void restartSelf() {
+        finishAll();
+        Intent intent = getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        startActivity(intent);
+        android.os.Process.killProcess(android.os.Process.myPid());
+    }
+
+    public static void sendGlobalBroadcast(Intent intent){
+        intent.setPackage(getInstance().getPackageName());
+        getInstance().sendBroadcast(intent);
+    }
+
+    public static void sendLocalBroadcast(Intent intent){
+        LocalBroadcastManager.getInstance(getInstance()).sendBroadcast(intent);
+    }
+
+    public static void sendLocalBroadcast(String action){
+        LocalBroadcastManager.getInstance(getInstance()).sendBroadcast(new Intent(action));
+    }
+
+
+    public static void unregisterLocalReceiver(BroadcastReceiver receiver){
+        if (receiver == null){
+            return;
+        }
+        LocalBroadcastManager.getInstance(getInstance()).unregisterReceiver(receiver);
+    }
+    public static void registerLocalReceiver(@NonNull BroadcastReceiver receiver,@NonNull IntentFilter filter){
+        LocalBroadcastManager.getInstance(getInstance()).registerReceiver(receiver,filter);
+    }
+
+    public static void stopService(Class<? extends Service> serviceClass){
+        getInstance().stopService(new Intent(getInstance(),serviceClass));
+    }
+
+    public static void registerLocalReceiver(@NonNull BroadcastReceiver receiver,String action){
+        LocalBroadcastManager.getInstance(getInstance()).registerReceiver(receiver,new IntentFilter(action));
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        super.onTrimMemory(level);
+        if (level >= TRIM_MEMORY_UI_HIDDEN){
+            CloudImageLoaderFactory.get().clearMemory();
+        }
+    }
+
+    @Override
+    public void startActivity(Intent intent) {
+        try {
+            super.startActivity(intent);
+        } catch (Exception e){
+            DebugLogger.e(BogoApplication.class.getSimpleName(),"启动Activity失败",e);
+        }
+    }
+
+    private void startSceneTransitionActivity(Context context, View target, Intent intent) {
+        if (context instanceof Activity) {
+            ActivityOptions option = ActivityOptions.makeSceneTransitionAnimation((Activity) context, target, "imageSenseView");
+            ActivityCompat.startActivity(context, intent, option.toBundle());
+        } else {
+            ActivityCompat.startActivity(context, intent, null);
+        }
+    }
+
+    public void startPhotoActivity(Context context, @NonNull CloudImage image, @NonNull View target) {
+        Intent intent = new Intent(context, PhotoScreenActivity.class);
+        intent.putExtra(CloudImage.class.getName(), image);
+        startSceneTransitionActivity(context, target, intent);
+    }
+
+    public void startGalleryPhotoActivity(Context context, @NonNull List<? extends CloudImage> imageList,int index, @NonNull View anchor) {
+        Intent intent = new Intent(context, PhotoGalleryActivity.class);
+        intent.putExtra(CloudImage.class.getName(), (Serializable) imageList);
+        intent.putExtra(Constant.ATTR_INDEX, index);
+        if (context instanceof Activity) {
+            CloudImage target = imageList.get(index);
+            String transitionName = AppTools.getCloudThumbUrl(target);
+            ActivityOptionsCompat option = ActivityOptionsCompat.makeSceneTransitionAnimation((Activity) context,
+                    new Pair<>(anchor, transitionName)
+            );
+            ActivityCompat.startActivity(context, intent, option.toBundle());
+        } else {
+            ActivityCompat.startActivity(context, intent, null);
+        }
+    }
+
+    public void startVideoActivity(Context context, @NonNull CloudVideo video, @NonNull View target) {
+        Intent intent = new Intent(context, VideoPlayerActivity.class);
+        intent.putExtra(CloudVideo.class.getName(), video);
+        ActivityOptions option = ActivityOptions.makeSceneTransitionAnimation((Activity) context, target, "imageSenseView");
+        ActivityCompat.startActivity(context, intent, option.toBundle());
+    }
+
+    public void connectPushServer() {
+        PushManager.connect(this, BuildConfig.MESSAGING_SERVER_HOST,BuildConfig.MESSAGING_SERVER_PORT);
+    }
+
+
+    private void createNotificationChannel(){
+
+        NotificationManager notificationMgr = ContextCompat.getSystemService(this,NotificationManager.class);
+
+        if (notificationMgr == null) {
+            return;
+        }
+
+        if (notificationMgr.getNotificationChannel(Constant.NEW_MESSAGE_NTF_CHANNEL_ID) == null) {
+            NotificationChannel channel = new NotificationChannel(Constant.NEW_MESSAGE_NTF_CHANNEL_ID,
+                    Constant.NEW_MESSAGE_NTF_CHANNEL_NAME,
+                    NotificationManager.IMPORTANCE_HIGH);
+            channel.setShowBadge(true);
+            channel.enableLights(true);
+            AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build();
+            channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),audioAttributes);
+            channel.setLightColor(Color.GREEN);
+            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+            notificationMgr.createNotificationChannel(channel);
+        }
+
+        if (notificationMgr.getNotificationChannel(Constant.HIGH_NOTIFICATION_CHANNEL_ID) == null) {
+            NotificationChannel channel = new NotificationChannel(Constant.HIGH_NOTIFICATION_CHANNEL_ID,
+                    Constant.HIGH_NOTIFICATION_CHANNEL_NAME,
+                    NotificationManager.IMPORTANCE_HIGH);
+            channel.setShowBadge(true);
+            channel.enableLights(true);
+            AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build();
+            channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),audioAttributes);
+            channel.setLightColor(Color.GREEN);
+            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+            notificationMgr.createNotificationChannel(channel);
+        }
+
+
+        if (notificationMgr.getNotificationChannel(Constant.CALLING_NTF_CHANNEL_ID) == null) {
+            NotificationChannel channel = new NotificationChannel(Constant.CALLING_NTF_CHANNEL_ID,Constant.CALLING_NTF_CHANNEL_NAME,  NotificationManager.IMPORTANCE_DEFAULT);
+            channel.setSound(null,null);
+            channel.setShowBadge(false);
+            channel.enableLights(false);
+            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+            notificationMgr.createNotificationChannel(channel);
+        }
+
+        if (notificationMgr.getNotificationChannel(Constant.CALL_INCOMING_NTF_CHANNEL_ID) == null) {
+            NotificationChannel channel = new NotificationChannel(Constant.CALL_INCOMING_NTF_CHANNEL_ID,Constant.CALL_INCOMING_NTF_CHANNEL_NAME,  NotificationManager.IMPORTANCE_HIGH);
+
+            channel.setSound(null,null);
+            channel.setVibrationPattern(new long[]{100, 200, 300});
+            channel.enableVibration(true);
+            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+            channel.setShowBadge(true);
+            channel.enableLights(true);
+            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+            notificationMgr.createNotificationChannel(channel);
+        }
+
+        if (notificationMgr.getNotificationChannel(Constant.DOWNLOAD_NTF_CHANNEL_ID) == null) {
+            NotificationChannel channel = new NotificationChannel(Constant.DOWNLOAD_NTF_CHANNEL_ID,Constant.DOWNLOAD_NTF_CHANNEL_NAME,  NotificationManager.IMPORTANCE_HIGH);
+            channel.setSound(null,null);
+            channel.setShowBadge(false);
+            channel.enableLights(false);
+            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+            notificationMgr.createNotificationChannel(channel);
+        }
+
+        if (notificationMgr.getNotificationChannel(Constant.SCREEN_CAPTURE_NTF_CHANNEL_ID) == null) {
+            NotificationChannel channel = new NotificationChannel(Constant.SCREEN_CAPTURE_NTF_CHANNEL_ID,Constant.SCREEN_CAPTURE_NTF_CHANNEL_NAME,  NotificationManager.IMPORTANCE_DEFAULT);
+            channel.setSound(null,null);
+            channel.setShowBadge(false);
+            channel.enableLights(false);
+            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+            notificationMgr.createNotificationChannel(channel);
+        }
+    }
+
+    public void initApplicationFeatures(Activity activity) {
+
+        Global.setIncomingCallState(TelephonyManager.CALL_STATE_IDLE);
+
+        createNotificationChannel();
+
+        PushManager.setLoggerEnable(this,BuildConfig.DEBUG);
+
+        String[] permissions = new String[]{Manifest.permission.READ_PHONE_STATE,Manifest.permission.POST_NOTIFICATIONS,Manifest.permission.MODIFY_AUDIO_SETTINGS};
+
+        if(!App.hasAllPermissions(this,permissions)){
+            ActivityCompat.requestPermissions(activity,permissions,0);
+        }
+    }
+
+    public void startCallAudioActivity(ChatSession session){
+
+        if (session == null){
+            return;
+        }
+
+        Intent intentVoice = new Intent(this,VoiceCallingActivity.class);
+
+        if (isActivityStarted(VideoIncomingCallActivity.class)){
+            intentVoice.setClass(this,VideoIncomingCallActivity.class);
+        }
+
+        if (isActivityStarted(VideoCallingActivity.class)){
+            intentVoice.setClass(this,VideoCallingActivity.class);
+        }
+
+        if (isActivityStarted(VoiceIncomingCallActivity.class)){
+            intentVoice.setClass(this,VoiceIncomingCallActivity.class);
+        }
+
+        if (isActivityStarted(VideoIncomingCallActivity.class)){
+            intentVoice.setClass(this,VideoIncomingCallActivity.class);
+        }
+
+        intentVoice.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intentVoice.putExtra(Constant.ATTR_CHAT_SESSION,session);
+        startActivity(intentVoice);
+    }
+
+    public void startCallVideoActivity(ChatSession session){
+
+        if (session == null){
+            return;
+        }
+
+        Intent intentVideo = new Intent(this, VideoCallingActivity.class);
+
+        if (isActivityStarted(VideoIncomingCallActivity.class)){
+            intentVideo.setClass(this,VideoIncomingCallActivity.class);
+        }
+
+        if (isActivityStarted(VoiceCallingActivity.class)){
+            intentVideo.setClass(this,VoiceCallingActivity.class);
+        }
+
+        if (isActivityStarted(VoiceIncomingCallActivity.class)){
+            intentVideo.setClass(this,VoiceIncomingCallActivity.class);
+        }
+
+        if (isActivityStarted(VideoIncomingCallActivity.class)){
+            intentVideo.setClass(this,VideoIncomingCallActivity.class);
+        }
+
+        intentVideo.putExtra(Constant.ATTR_CHAT_SESSION,session);
+        intentVideo.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intentVideo);
+    }
+
+
+    public void startTextMagnifierActivity(String content) {
+        Intent intent = new Intent(this, TextMagnifierActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(Constant.ATTR_TEXT,content);
+        startActivity(intent);
+    }
+
+    public void startMeetRingActivity(LivekitRoom room){
+
+        if (isActivityStarted(VideoCallingActivity.class)
+                || isActivityStarted(VoiceIncomingCallActivity.class)
+                || isActivityStarted(VideoIncomingCallActivity.class)
+                || isActivityStarted(VoiceCallingActivity.class)
+                || isActivityStarted(VideoCallingActivity.class)
+                || isActivityStarted(LivekitMeetingCallerActivity.class)
+                || isActivityStarted(LivekitMeetingCalleeActivity.class)){
+
+            AppTools.showToastView(this, R.string.tips_phone_busy_calling);
+            return;
+        }
+
+        Intent intent = new Intent(this, LivekitMeetingCallerActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(LivekitRoom.class.getName(),room);
+        startActivity(intent);
+    }
+
+    public void startMeetCalleeActivity(LivekitRoom room){
+
+        App.finish(LivekitMeetingCalleeActivity.class);
+        App.finish(LivekitMeetingCallerActivity.class);
+
+        Intent intent = new Intent(this, LivekitMeetingCalleeActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(LivekitRoom.class.getName(),room);
+        startActivity(intent);
+    }
+
+    public boolean isActivityStarted(Class<? extends Activity> tClass){
+        for (Activity activity : activityList){
+            if (activity.getClass() == tClass){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void finish(Class<? extends Activity> tClass){
+        for (Activity activity : activityList){
+            if (activity.getClass() == tClass){
+                activity.finish();
+            }
+        }
+    }
+
+    public void finishAll(){
+        for (Activity activity : activityList){
+            activity.finish();
+        }
+    }
+
+    public GlobalVideoCache getGlobalVideoCache() {
+        return globalVideoCache;
+    }
+
+    public boolean isAppInBackground(){
+            return activeCounter.get() == 0;
+    }
+
+    public Activity getMainActivity(){
+        return !activityList.isEmpty() ? activityList.get(0) : null;
+    }
+
+
+    @Override
+    public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
+        activityList.add(activity);
+    }
+
+    @Override
+    public void onActivityStarted(@NonNull Activity activity) {
+        activeCounter.getAndIncrement();
+    }
+
+    @Override
+    public void onActivityResumed(@NonNull Activity activity) {
+        activityList.remove(activity);
+        activityList.add(activity);
+    }
+
+    @Override
+    public void onActivityPaused(@NonNull Activity activity) {
+
+    }
+
+    @Override
+    public void onActivityStopped(@NonNull Activity activity) {
+        activeCounter.decrementAndGet();
+        if (activeCounter.get() == 0){
+            for (Activity baseActivity : activityList){
+                if (baseActivity instanceof BaseActivity){
+                    ((BaseActivity)baseActivity).onAppInBackground();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
+
+    }
+
+    @Override
+    public void onActivityDestroyed(@NonNull Activity activity) {
+        activityList.remove(activity);
+    }
+
+}

+ 159 - 0
app/src/main/java/com/bogo/android/account/api/AccountServiceManager.java

@@ -0,0 +1,159 @@
+
+package com.bogo.android.account.api;
+
+import android.content.Intent;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.account.api.response.PrimaryDataDTO;
+import com.bogo.android.account.api.response.SecondDataDTO;
+import com.bogo.android.account.api.response.SilentNotificationDTO;
+import com.bogo.android.account.api.service.AccountService;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.api.BaseServiceManager;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.constant.IntentAction;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.listener.SimpleHttpRequestListener;
+import com.bogo.android.common.util.AppTools;
+import com.bogo.android.emoticon.database.EmoticonDatabase;
+import com.bogo.android.friend.database.FriendDatabase;
+import com.bogo.android.friend.entity.Friend;
+import com.bogo.android.group.database.GroupDatabase;
+import com.bogo.android.message.api.MessageServiceManager;
+import com.bogo.android.message.database.ChatSessionDatabase;
+import com.bogo.android.micro.database.MicroServerDatabase;
+import com.bogo.android.moment.database.MomentRuleDatabase;
+import com.bogo.android.moment.entity.MomentRule;
+import com.bogo.android.note.api.NoteServiceManager;
+import com.bogo.android.note.database.NoteDatabase;
+import com.bogo.android.organization.database.OrganizationDatabase;
+
+public class AccountServiceManager extends BaseServiceManager {
+
+
+    private static final AccountService accountService = createService(AccountService.class);
+
+
+    public static void updatePassword(String oldPassword, String newPassword, HttpResponseListener<Void> responseListener) {
+        accountService.updatePassword(oldPassword, newPassword).enqueue(new MainThreadCallback<>(responseListener));
+
+    }
+
+    public static void updateName(String name, HttpResponseListener<Void> responseListener) {
+        accountService.updateName(name).enqueue(new MainThreadCallback<>(responseListener));
+    }
+
+    public static void updateMotto(String motto, HttpResponseListener<Void> responseListener) {
+        accountService.updateMotto(motto).enqueue(new MainThreadCallback<>(responseListener));
+    }
+
+    public static void updateEmail(String email, HttpResponseListener<Void> responseListener) {
+        accountService.updateEmail(email).enqueue(new MainThreadCallback<>(responseListener));
+    }
+
+    public static void updateGender(byte gender, HttpResponseListener<Void> responseListener) {
+        accountService.updateGender(gender).enqueue(new MainThreadCallback<>(responseListener));
+    }
+
+    public static void loadPrimaryData() {
+
+        long uid = Global.getUid();
+
+        if (Global.isPrimaryDataLoadFinished(uid)){
+            return;
+        }
+
+        accountService.getPrimaryData().enqueue(new WorkerThreadCallback<>(new SimpleHttpRequestListener<PrimaryDataDTO>() {
+            @Override
+            public void onHttpResponse(ApiResponse<PrimaryDataDTO> response) {
+
+                save(response.data);
+
+                Global.setPrimaryDataLoadFinished(Global.getUid());
+
+                MessageServiceManager.loadPatchMessages();
+
+            }
+        }));
+
+    }
+
+    public static void loadSecondData() {
+
+        long uid = Global.getUid();
+
+        if (Global.isSecondDataLoadFinished(uid)){
+            return;
+        }
+
+        accountService.getSecondData().enqueue(new WorkerThreadCallback<>(new SimpleHttpRequestListener<SecondDataDTO>() {
+            @Override
+            public void onHttpResponse(ApiResponse<SecondDataDTO> response) {
+
+                save(response.data);
+
+                Global.setSecondDataLoadFinished(Global.getUid());
+
+            }
+        }));
+    }
+
+    public static void syncAccountData(){
+
+        accountService.getPrimaryData().enqueue(new WorkerThreadCallback<>(new SimpleHttpRequestListener<PrimaryDataDTO>() {
+            @Override
+            public void onHttpResponse(ApiResponse<PrimaryDataDTO> response) {
+
+                save(response.data);
+
+                accountService.getSecondData().enqueue(new WorkerThreadCallback<>(new SimpleHttpRequestListener<SecondDataDTO>() {
+                    @Override
+                    public void onHttpResponse(ApiResponse<SecondDataDTO> response) {
+
+                        save(response.data);
+
+                        BogoApplication.sendLocalBroadcast(new Intent(IntentAction.ACTION_ACCOUNT_SYNC_FINISHED));
+                    }
+                }));
+            }
+        }));
+
+
+    }
+
+
+    private static void save(PrimaryDataDTO data){
+
+        OrganizationDatabase.add(data.getOrganization());
+
+        GroupDatabase.add(data.getGroupList());
+        MicroServerDatabase.add(data.getMicroServerList());
+
+        FriendDatabase.add(data.getFriendList(), Friend.TYPE_FRIEND);
+        FriendDatabase.add(data.getContactList(),Friend.TYPE_ORG_MATE);
+        FriendDatabase.add(data.getBothList(),Friend.TYPE_BOTH);
+
+        /*
+         * 需要把自己设置为陌生人
+         * 避免很多地方把自己查询出来
+         */
+        FriendDatabase.delete(Global.getUid(),Friend.TYPE_ORG_MATE);
+    }
+
+    private static void save(SecondDataDTO data){
+        MomentRuleDatabase.add(data.getMomentBlackedList(), MomentRule.TYPE_BLOCK);
+        MomentRuleDatabase.add(data.getMomentIgnoredList(), MomentRule.TYPE_IGNORE);
+        EmoticonDatabase.add(data.getEmoticonList());
+        NoteDatabase.add(NoteServiceManager.getAll());
+
+        if (AppTools.isEmpty(data.getSilentNotificationList())) {
+          return;
+        }
+
+        for (SilentNotificationDTO dto : data.getSilentNotificationList()){
+            ChatSessionDatabase.makeSilent(dto.getTargetId(),dto.getType());
+        }
+        BogoApplication.sendLocalBroadcast(new Intent(IntentAction.ACTION_RECENT_REFRESH_LIST));
+    }
+
+}

+ 66 - 0
app/src/main/java/com/bogo/android/account/api/LoginServiceManager.java

@@ -0,0 +1,66 @@
+
+package com.bogo.android.account.api;
+
+import com.bogo.android.account.api.request.RegisterRequest;
+import com.bogo.android.account.api.request.ResetPasswordRequest;
+import com.bogo.android.account.api.service.LoginService;
+import com.bogo.android.common.api.BaseServiceManager;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.entity.User;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.util.MD5;
+
+import retrofit2.Call;
+
+public class LoginServiceManager extends BaseServiceManager {
+
+    private static final LoginService loginService = createService(LoginService.class);
+
+    public static void findId(String telephone, HttpResponseListener<Long> responseListener) {
+        loginService.findId(telephone).enqueue(new MainThreadCallback<>(responseListener));
+    }
+     
+    /**
+     * 用户登录
+     *
+     * @param telephone
+     * @param password
+     * @param responseListener
+     */
+    public static void login(String telephone, String password, HttpResponseListener<User> responseListener) {
+
+        Call<ApiResponse<User>> call = loginService.login(telephone, MD5.digest(password));
+
+        call.enqueue(new MainThreadCallback<>(responseListener));
+
+    }
+
+    /**
+     * 用户注册
+     *
+     * @param request
+     * @param responseListener
+     */
+    public static void register(RegisterRequest request, HttpResponseListener<User> responseListener) {
+
+        Call<ApiResponse<User>> call = loginService.register(request);
+
+        call.enqueue(new MainThreadCallback<>(responseListener));
+
+    }
+
+
+    public static void resetPassword(ResetPasswordRequest request, HttpResponseListener<Void> responseListener) {
+
+        Call<ApiResponse<Void>> call = loginService.resetPassword(request);
+
+        call.enqueue(new MainThreadCallback<>(responseListener));
+
+    }
+
+    public static void logout() {
+        loginService.logout().enqueue(VOID_CALLBACK);
+    }
+
+
+}

+ 22 - 0
app/src/main/java/com/bogo/android/account/api/MessageCodeServiceManager.java

@@ -0,0 +1,22 @@
+
+package com.bogo.android.account.api;
+
+import com.bogo.android.account.api.service.MessageCodeService;
+import com.bogo.android.common.api.BaseServiceManager;
+import com.bogo.android.common.listener.HttpResponseListener;
+
+public class MessageCodeServiceManager extends BaseServiceManager {
+
+    private static final MessageCodeService messageCodeService = createService(MessageCodeService.class);
+
+    public static void register(String telephone, HttpResponseListener<String> responseListener) {
+        messageCodeService.register(telephone).enqueue(new MainThreadCallback<>(responseListener));
+
+    }
+
+    public static void forgot(String telephone, HttpResponseListener<String> responseListener) {
+        messageCodeService.forgot(telephone).enqueue(new MainThreadCallback<>(responseListener));
+    }
+
+
+}

+ 54 - 0
app/src/main/java/com/bogo/android/account/api/request/RegisterRequest.java

@@ -0,0 +1,54 @@
+
+package com.bogo.android.account.api.request;
+
+import java.io.Serializable;
+
+
+public class RegisterRequest implements Serializable {
+    private String name;
+    private String telephone;
+    private String code;
+    private String password;
+    private String gender;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getTelephone() {
+        return telephone;
+    }
+
+    public void setTelephone(String telephone) {
+        this.telephone = telephone;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getGender() {
+        return gender;
+    }
+
+    public void setGender(String gender) {
+        this.gender = gender;
+    }
+
+}

+ 38 - 0
app/src/main/java/com/bogo/android/account/api/request/ResetPasswordRequest.java

@@ -0,0 +1,38 @@
+
+package com.bogo.android.account.api.request;
+
+import java.io.Serializable;
+
+
+public class ResetPasswordRequest implements Serializable {
+    private String telephone;
+    private String code;
+    private String password;
+
+
+    public String getTelephone() {
+        return telephone;
+    }
+
+    public void setTelephone(String telephone) {
+        this.telephone = telephone;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+
+}

+ 62 - 0
app/src/main/java/com/bogo/android/account/api/response/OrganizationDTO.java

@@ -0,0 +1,62 @@
+
+package com.bogo.android.account.api.response;
+
+
+import com.bogo.android.organization.entity.Department;
+import com.bogo.android.organization.entity.DepartmentMember;
+
+import java.util.Collections;
+import java.util.List;
+
+public class OrganizationDTO {
+
+    public Long id;
+
+    public String name;
+
+    public List<Department> departmentList;
+
+    public List<DepartmentMember> memberList;
+
+    private List<UserDTO> contactList;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<Department> getDepartmentList() {
+        return departmentList == null ? Collections.emptyList() : departmentList;
+    }
+
+    public void setDepartmentList(List<Department> departmentList) {
+        this.departmentList = departmentList;
+    }
+
+    public List<DepartmentMember> getMemberList() {
+        return memberList == null ? Collections.emptyList() : memberList;
+    }
+
+    public void setMemberList(List<DepartmentMember> memberList) {
+        this.memberList = memberList;
+    }
+
+    public List<UserDTO> getContactList() {
+        return contactList == null ? Collections.emptyList() : contactList;
+    }
+
+    public void setContactList(List<UserDTO> contactList) {
+        this.contactList = contactList;
+    }
+}

+ 72 - 0
app/src/main/java/com/bogo/android/account/api/response/PrimaryDataDTO.java

@@ -0,0 +1,72 @@
+
+package com.bogo.android.account.api.response;
+
+import com.bogo.android.group.entity.Group;
+import com.bogo.android.micro.entity.MicroServer;
+import com.bogo.android.organization.entity.Organization;
+
+import java.util.List;
+
+
+public class PrimaryDataDTO {
+
+    private List<Group> groupList;
+
+    private List<MicroServer> microServerList;
+
+    private Organization organization;
+
+    private List<UserDTO> friendList;
+
+    private List<UserDTO> contactList;
+
+    private List<UserDTO> bothList;
+
+    public List<Group> getGroupList() {
+        return groupList;
+    }
+
+    public void setGroupList(List<Group> groupList) {
+        this.groupList = groupList;
+    }
+
+    public List<MicroServer> getMicroServerList() {
+        return microServerList;
+    }
+
+    public void setMicroServerList(List<MicroServer> microServerList) {
+        this.microServerList = microServerList;
+    }
+
+    public Organization getOrganization() {
+        return organization;
+    }
+
+    public void setOrganization(Organization organization) {
+        this.organization = organization;
+    }
+
+    public List<UserDTO> getFriendList() {
+        return friendList;
+    }
+
+    public void setFriendList(List<UserDTO> friendList) {
+        this.friendList = friendList;
+    }
+
+    public List<UserDTO> getContactList() {
+        return contactList;
+    }
+
+    public void setContactList(List<UserDTO> contactList) {
+        this.contactList = contactList;
+    }
+
+    public List<UserDTO> getBothList() {
+        return bothList;
+    }
+
+    public void setBothList(List<UserDTO> bothList) {
+        this.bothList = bothList;
+    }
+}

+ 51 - 0
app/src/main/java/com/bogo/android/account/api/response/SecondDataDTO.java

@@ -0,0 +1,51 @@
+package com.bogo.android.account.api.response;
+
+import com.bogo.android.emoticon.entity.Emoticon;
+
+import java.util.List;
+
+/**
+ * 次要初始数据
+ */
+public class SecondDataDTO {
+
+    private List<Long> momentIgnoredList;
+
+    private List<Long> momentBlackedList;
+
+    private List<Emoticon> emoticonList;
+
+    private List<SilentNotificationDTO> silentNotificationList;
+
+    public List<Long> getMomentIgnoredList() {
+        return momentIgnoredList;
+    }
+
+    public void setMomentIgnoredList(List<Long> momentIgnoredList) {
+        this.momentIgnoredList = momentIgnoredList;
+    }
+
+    public List<Long> getMomentBlackedList() {
+        return momentBlackedList;
+    }
+
+    public void setMomentBlackedList(List<Long> momentBlackedList) {
+        this.momentBlackedList = momentBlackedList;
+    }
+
+    public List<Emoticon> getEmoticonList() {
+        return emoticonList;
+    }
+
+    public void setEmoticonList(List<Emoticon> emoticonList) {
+        this.emoticonList = emoticonList;
+    }
+
+    public List<SilentNotificationDTO> getSilentNotificationList() {
+        return silentNotificationList;
+    }
+
+    public void setSilentNotificationList(List<SilentNotificationDTO> silentNotificationList) {
+        this.silentNotificationList = silentNotificationList;
+    }
+}

+ 30 - 0
app/src/main/java/com/bogo/android/account/api/response/SilentNotificationDTO.java

@@ -0,0 +1,30 @@
+
+package com.bogo.android.account.api.response;
+
+/**
+ * 消息免提醒
+ */
+public class SilentNotificationDTO {
+
+	private Long targetId;
+
+	private Byte type;
+
+
+	public Long getTargetId() {
+		return targetId;
+	}
+
+	public void setTargetId(Long targetId) {
+		this.targetId = targetId;
+	}
+
+	public Byte getType() {
+		return type;
+	}
+
+	public void setType(Byte type) {
+		this.type = type;
+	}
+
+}

+ 99 - 0
app/src/main/java/com/bogo/android/account/api/response/UserDTO.java

@@ -0,0 +1,99 @@
+
+package com.bogo.android.account.api.response;
+
+import com.bogo.android.friend.entity.Friend;
+
+public class UserDTO {
+
+    private Long id;
+
+    private String name;
+
+    private String alias;
+
+    private String telephone;
+
+    private Long organizationId;
+
+    private String email;
+
+    private byte gender;
+
+    private String motto;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public void setAlias(String alias) {
+        this.alias = alias;
+    }
+
+    public String getTelephone() {
+        return telephone;
+    }
+
+    public void setTelephone(String telephone) {
+        this.telephone = telephone;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public Long getOrganizationId() {
+        return organizationId;
+    }
+
+    public void setOrganizationId(Long organizationId) {
+        this.organizationId = organizationId;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public byte getGender() {
+        return gender;
+    }
+
+    public void setGender(byte gender) {
+        this.gender = gender;
+    }
+
+    public String getMotto() {
+        return motto;
+    }
+
+    public void setMotto(String motto) {
+        this.motto = motto;
+    }
+
+    public Friend ofFriend(){
+        Friend friend = new Friend();
+        friend.alias = alias;
+        friend.id = id;
+        friend.name = name;
+        friend.email = email;
+        friend.telephone = telephone;
+        friend.motto = motto;
+        friend.gender = gender;
+        return friend;
+    }
+}

+ 43 - 0
app/src/main/java/com/bogo/android/account/api/service/AccountService.java

@@ -0,0 +1,43 @@
+package com.bogo.android.account.api.service;
+
+
+import com.bogo.android.account.api.response.PrimaryDataDTO;
+import com.bogo.android.account.api.response.SecondDataDTO;
+import com.bogo.android.common.api.response.ApiResponse;
+
+import retrofit2.Call;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.PATCH;
+
+public interface AccountService {
+
+    @FormUrlEncoded
+    @PATCH("user/password")
+    Call<ApiResponse<Void>> updatePassword(@Field("oldPassword") String oldPassword, @Field("newPassword") String newPassword);
+
+    @FormUrlEncoded
+    @PATCH("user/name")
+    Call<ApiResponse<Void>> updateName(@Field("name") String name);
+
+
+    @FormUrlEncoded
+    @PATCH("user/motto")
+    Call<ApiResponse<Void>> updateMotto(@Field("motto") String motto);
+
+    @FormUrlEncoded
+    @PATCH("user/email")
+    Call<ApiResponse<Void>> updateEmail(@Field("email") String email);
+
+    @FormUrlEncoded
+    @PATCH("user/gender")
+    Call<ApiResponse<Void>> updateGender(@Field("gender") byte gender);
+
+
+    @GET("account/data")
+    Call<ApiResponse<PrimaryDataDTO>> getPrimaryData();
+
+    @GET("account/data/second")
+    Call<ApiResponse<SecondDataDTO>> getSecondData();
+}

+ 36 - 0
app/src/main/java/com/bogo/android/account/api/service/LoginService.java

@@ -0,0 +1,36 @@
+package com.bogo.android.account.api.service;
+
+
+import com.bogo.android.account.api.request.RegisterRequest;
+import com.bogo.android.account.api.request.ResetPasswordRequest;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.entity.User;
+
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+
+public interface LoginService {
+
+    @GET("user/id/{telephone}")
+    Call<ApiResponse<Long>> findId(@Path("telephone") String telephone);
+
+    @FormUrlEncoded
+    @POST("user/login")
+    Call<ApiResponse<User>> login(@Field("telephone") String telephone, @Field("password") String password);
+
+    @POST("user/register")
+    Call<ApiResponse<User>> register(@Body RegisterRequest request);
+
+    @POST("user/forgot")
+    Call<ApiResponse<Void>> resetPassword(@Body ResetPasswordRequest request);
+
+    @GET("user/logout")
+    Call<ApiResponse<Void>> logout();
+
+
+}

+ 17 - 0
app/src/main/java/com/bogo/android/account/api/service/MessageCodeService.java

@@ -0,0 +1,17 @@
+package com.bogo.android.account.api.service;
+
+
+import com.bogo.android.common.api.response.ApiResponse;
+
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Path;
+
+public interface MessageCodeService {
+
+    @GET("code/register/{telephone}")
+    Call<ApiResponse<String>> register(@Path("telephone") String telephone);
+
+    @GET("code/forgot/{telephone}")
+    Call<ApiResponse<String>> forgot(@Path("telephone") String telephone);
+}

+ 76 - 0
app/src/main/java/com/bogo/android/account/dialog/InputUserNameDialog.java

@@ -0,0 +1,76 @@
+
+package com.bogo.android.account.dialog;
+
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.text.Html;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.bogo.android.R;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.dialog.BlurAlterDialog;
+import com.bogo.android.common.listener.OnInputCompleteClickListener;
+import com.bogo.android.common.util.FileURLBuilder;
+import com.bogo.android.databinding.DialogLogoInputEditBinding;
+
+
+public class InputUserNameDialog extends BlurAlterDialog<DialogLogoInputEditBinding> implements View.OnClickListener, DialogInterface.OnShowListener {
+
+    private OnInputCompleteClickListener onInputCompleteClickListener;
+
+
+    public InputUserNameDialog(Context context) {
+
+        super(context,R.style.floatLogoDialogStyle);
+        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+        binding.footer.leftButton.setOnClickListener(this);
+        binding.footer.rightButton.setOnClickListener(this);
+        binding.footer.rightButton.setText(R.string.common_save);
+
+        binding.logo.load(FileURLBuilder.getUserIconUrl(Global.getUid()),0);
+
+        binding.inputText.setText(Global.getName());
+
+        binding.inputText.setHint(Html.fromHtml(context.getString(R.string.hint_input_profile_name)));
+
+        binding.inputLayout.setHint(R.string.common_name);
+
+        setCanceledOnTouchOutside(false);
+
+        setOnShowListener(this);
+    }
+
+    @Override
+    public BindingCompat<DialogLogoInputEditBinding> getBinding() {
+        DialogLogoInputEditBinding binding = DialogLogoInputEditBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    public void setOnInputCompleteClickListener(OnInputCompleteClickListener onInputCompleteClickListener) {
+        this.onInputCompleteClickListener = onInputCompleteClickListener;
+    }
+
+    @Override
+    public void onClick(View view) {
+
+        if (view.getId() == R.id.leftButton) {
+            dismiss();
+            return;
+        }
+
+        if (!TextUtils.isEmpty(binding.inputText.getText()) && view.getId() == R.id.rightButton){
+            onInputCompleteClickListener.onInputCompleted(binding.inputText.getText().toString().trim());
+            dismiss();
+        }
+    }
+
+    @Override
+    public void onShow(DialogInterface dialog) {
+        binding.inputText.setSelection(binding.inputText.getText().length());
+        binding.inputText.requestFocus();
+    }
+}

+ 118 - 0
app/src/main/java/com/bogo/android/account/dialog/QuitAppDialog.java

@@ -0,0 +1,118 @@
+
+package com.bogo.android.account.dialog;
+
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.graphics.RenderEffect;
+import android.graphics.Shader;
+import android.os.Build;
+import android.view.View;
+import android.view.Window;
+
+import androidx.core.app.NotificationManagerCompat;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.R;
+import com.bogo.android.account.api.LoginServiceManager;
+import com.bogo.android.common.App;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.dialog.BlurBottomSheetDialog;
+import com.bogo.android.common.dialog.CustomDialog;
+import com.bogo.android.common.listener.OnDialogButtonClickListener;
+import com.bogo.android.databinding.LayoutQuitAppDialogBinding;
+import com.bogo.android.home.ui.HomeActivity;
+import com.bogo.android.home.ui.MixedSettingActivity;
+import com.bogo.messaging.PushManager;
+
+public class QuitAppDialog extends BlurBottomSheetDialog<LayoutQuitAppDialogBinding> implements View.OnClickListener, OnDialogButtonClickListener, DialogInterface.OnDismissListener, DialogInterface.OnShowListener {
+    private final CustomDialog dialog;
+    private final Window window;
+
+    public QuitAppDialog(Activity activity) {
+        super(activity);
+        window = activity.getWindow();
+        setOwnerActivity(activity);
+        binding.menuExit.setOnClickListener(this);
+        binding.menuLogout.setOnClickListener(this);
+        dialog = new CustomDialog(activity);
+        dialog.setOwnerActivity(activity);
+        dialog.setOnDialogButtonClickListener(this);
+        setOnDismissListener(this);
+        setOnShowListener(this);
+    }
+
+
+    @Override
+    public BindingCompat<LayoutQuitAppDialogBinding> getBinding() {
+        LayoutQuitAppDialogBinding binding = LayoutQuitAppDialogBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+
+    private void doLogout() {
+        LoginServiceManager.logout();
+        Global.removeToken();
+        PushManager.stop(BogoApplication.getInstance());
+        App.finish(HomeActivity.class);
+        App.finish(MixedSettingActivity.class);
+        BogoApplication.getInstance().restartSelf();
+        NotificationManagerCompat.from(getContext()).cancelAll();
+    }
+
+    private void doKillApp() {
+        PushManager.destroy(BogoApplication.getInstance());
+        App.finish(MixedSettingActivity.class);
+        App.finish(HomeActivity.class);
+    }
+
+    @Override
+    public void onClick(View v) {
+        this.dismiss();
+        if (v.getId() == R.id.menu_logout) {
+            dialog.setMessage(R.string.label_logout_hint);
+            dialog.setTag(R.id.menu_logout);
+            dialog.show();
+        }
+        if (v.getId() == R.id.menu_exit) {
+            dialog.setMessage(R.string.label_kill_app_hint);
+            dialog.setTag(R.id.menu_exit);
+            dialog.show();
+        }
+    }
+
+    @Override
+    public void onLeftButtonClicked() {
+        dialog.dismiss();
+    }
+
+    @Override
+    public void onRightButtonClicked() {
+        dialog.dismiss();
+        if (dialog.getTag().equals(R.id.menu_logout)) {
+            doLogout();
+        }
+        if (dialog.getTag().equals(R.id.menu_exit)) {
+            doKillApp();
+        }
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        View decorView = window.getDecorView();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            decorView.setRenderEffect(null);
+        }
+    }
+
+    @Override
+    public void onShow(DialogInterface dialog) {
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            View decorView = window.getDecorView();
+            decorView.setRenderEffect(RenderEffect.createBlurEffect(25F,25F, Shader.TileMode.CLAMP));
+        }
+
+    }
+}

+ 61 - 0
app/src/main/java/com/bogo/android/account/loader/CountryLoader.java

@@ -0,0 +1,61 @@
+
+package com.bogo.android.account.loader;
+
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.appcompat.widget.PopupMenu;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.R;
+import com.bogo.android.common.model.Country;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class CountryLoader{
+    private static final CountryLoader countryLoader = new CountryLoader();
+
+    private final List<Country> countries = new ArrayList<>();
+
+    public static CountryLoader getLoader(){
+        return countryLoader;
+    }
+
+
+    private CountryLoader(){
+        PopupMenu popup = new PopupMenu(BogoApplication.getInstance(), null);
+        Menu menu = popup.getMenu();
+        popup.getMenuInflater().inflate(R.menu.country,menu);
+        int count = menu.size();
+        for (int i = 0; i < count; i++) {
+
+            MenuItem item = menu.getItem(i);
+
+            String[] data = item.getTitleCondensed().toString().split("_");
+            Country country = new Country();
+            country.id = data[0];
+            country.code = data[1];
+            country.name = item.getTitle().toString();
+            country.icon = Country.getIcon(country.id);
+            countries.add(country);
+        }
+    }
+
+
+    public List<Country> getCountries() {
+        return countries;
+    }
+
+    public Country match(String telephone){
+
+        for (Country country : countries){
+            if (telephone.startsWith(country.code)){
+                return country;
+            }
+        }
+        return Country.NORMAL;
+    }
+
+}

+ 75 - 0
app/src/main/java/com/bogo/android/account/ui/AccountSyncActivity.java

@@ -0,0 +1,75 @@
+
+package com.bogo.android.account.ui;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.R;
+import com.bogo.android.account.api.AccountServiceManager;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.constant.IntentAction;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.common.util.AppTools;
+import com.bogo.android.databinding.ActivityAccountSyncBinding;
+
+
+public class AccountSyncActivity extends BaseActivity<ActivityAccountSyncBinding> implements View.OnClickListener{
+
+    private SyncFinishReceiver finishReceiver;
+
+    @Override
+    public BindingCompat<ActivityAccountSyncBinding> getBinding() {
+        ActivityAccountSyncBinding binding = ActivityAccountSyncBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    @Override
+    public void initComponents() {
+        binding.cancel.setOnClickListener(v -> finish());
+        binding.button.setOnClickListener(this);
+        finishReceiver = new SyncFinishReceiver();
+        BogoApplication.registerLocalReceiver(finishReceiver,IntentAction.ACTION_ACCOUNT_SYNC_FINISHED);
+
+    }
+
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        BogoApplication.unregisterLocalReceiver(finishReceiver);
+    }
+
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN && AppTools.isOutOfBounds(this, event)) {
+            return true;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    @Override
+    public void onClick(View v) {
+        binding.cancel.setVisibility(View.GONE);
+        binding.button.setText(R.string.button_account_syncing);
+        binding.button.setOnClickListener(null);
+        binding.button.setEnabled(false);
+        binding.progressBar.setVisibility(View.VISIBLE);
+        AccountServiceManager.syncAccountData();
+    }
+
+    private class SyncFinishReceiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            binding.progressBar.setVisibility(View.INVISIBLE);
+            binding.button.setEnabled(true);
+            binding.button.setText(R.string.button_account_sync_done);
+            binding.button.setOnClickListener(v -> finish());
+        }
+    }
+}

+ 46 - 0
app/src/main/java/com/bogo/android/account/ui/CountrySelectorActivity.java

@@ -0,0 +1,46 @@
+
+package com.bogo.android.account.ui;
+
+import android.content.Intent;
+import android.view.View;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.bogo.android.R;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.adapter.CountrySelectorAdapter;
+import com.bogo.android.common.listener.OnItemClickedListener;
+import com.bogo.android.common.model.Country;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.databinding.ActivityCommonRecyclerviewBinding;
+
+public class CountrySelectorActivity extends BaseActivity<ActivityCommonRecyclerviewBinding> implements OnItemClickedListener<Country> {
+
+    @Override
+    public void initComponents() {
+        binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
+        CountrySelectorAdapter selectorAdapter = new CountrySelectorAdapter();
+        selectorAdapter.setOnItemClickedListener(this);
+        binding.recyclerView.setAdapter(selectorAdapter);
+    }
+
+    @Override
+    public BindingCompat<ActivityCommonRecyclerviewBinding> getBinding() {
+        ActivityCommonRecyclerviewBinding binding = ActivityCommonRecyclerviewBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+
+    @Override
+    public int getToolbarTitle() {
+        return R.string.title_country_selector;
+    }
+
+    @Override
+    public void onItemClicked(Country country, View view) {
+        Intent intent = new Intent();
+        intent.putExtra(Country.class.getName(),country);
+        setResult(RESULT_OK,intent);
+        finish();
+    }
+}

+ 80 - 0
app/src/main/java/com/bogo/android/account/ui/EditEmailActivity.java

@@ -0,0 +1,80 @@
+
+package com.bogo.android.account.ui;
+
+
+import android.view.Menu;
+import android.view.View;
+import android.widget.Button;
+
+import com.bogo.android.R;
+import com.bogo.android.account.api.AccountServiceManager;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.entity.User;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.databinding.ActivityModifyEmailBinding;
+
+/**
+ * 修改签名
+ */
+public class EditEmailActivity extends BaseActivity<ActivityModifyEmailBinding> implements HttpResponseListener<Void> {
+
+
+
+    @Override
+    public void initComponents() {
+        User user = Global.getCurrentUser();
+        binding.email.setText(user.email);
+        binding.email.requestFocus();
+    }
+
+    @Override
+    public BindingCompat<ActivityModifyEmailBinding> getBinding() {
+        ActivityModifyEmailBinding binding = ActivityModifyEmailBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    @Override
+    public void onClick(View view) {
+        String email = binding.email.getText().toString().trim();
+
+        if (view.getId() == R.id.button && !email.isEmpty()){
+            showProgressDialog(R.string.tips_save_loading);
+            AccountServiceManager.updateEmail(email,this);
+        }
+    }
+
+    @Override
+    public int getToolbarTitle() {
+        return R.string.label_setting_modify_email;
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.single_button, menu);
+        Button button = menu.findItem(R.id.menu_button).getActionView().findViewById(R.id.button);
+        button.setOnClickListener(this);
+        button.setText(R.string.common_save);
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public void onHttpResponse(ApiResponse<Void> result) {
+        hideProgressDialog();
+        if (result.isSuccess()){
+            showToastView(R.string.tips_update_success);
+            Global.setEmail(binding.email.getText().toString());
+            finish();
+        }else {
+            showToastView(result.message);
+        }
+    }
+
+    @Override
+    public void onHttpException(Exception e) {
+        hideProgressDialog();
+        showToastView(R.string.tips_update_failed);
+    }
+}

+ 94 - 0
app/src/main/java/com/bogo/android/account/ui/EditGenderActivity.java

@@ -0,0 +1,94 @@
+
+package com.bogo.android.account.ui;
+
+
+import android.view.Menu;
+import android.view.View;
+import android.widget.Button;
+
+import com.bogo.android.R;
+import com.bogo.android.account.api.AccountServiceManager;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.entity.User;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.databinding.ActivityModifyGenderBinding;
+import com.bogo.android.friend.entity.Friend;
+
+/**
+ * 修改签名
+ */
+public class EditGenderActivity extends BaseActivity<ActivityModifyGenderBinding> implements HttpResponseListener<Void> {
+
+
+    @Override
+    public void initComponents() {
+        User user = Global.getCurrentUser();
+        binding.man.setChecked(Friend.GENDER_MAN == user.gender);
+        binding.woman.setChecked(Friend.GENDER_WOMAN == user.gender);
+
+        binding.woman.setOnClickListener(this);
+        binding.man.setOnClickListener(this);
+    }
+
+    @Override
+    public BindingCompat<ActivityModifyGenderBinding> getBinding() {
+        ActivityModifyGenderBinding binding = ActivityModifyGenderBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view.getId() == R.id.button){
+            showProgressDialog(R.string.tips_save_loading);
+            AccountServiceManager.updateGender(getGender(),this);
+        }
+
+        if (view == binding.woman){
+            binding.man.setChecked(false);
+            binding.woman.setChecked(true);
+        }
+        if (view == binding.man){
+            binding.man.setChecked(true);
+            binding.woman.setChecked(false);
+        }
+    }
+
+    private byte getGender(){
+        return binding.man.isChecked() ? Friend.GENDER_MAN : Friend.GENDER_WOMAN;
+    }
+
+    @Override
+    public int getToolbarTitle() {
+        return R.string.label_setting_modify_gender;
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.single_button, menu);
+        Button button = menu.findItem(R.id.menu_button).getActionView().findViewById(R.id.button);
+        button.setOnClickListener(this);
+        button.setText(R.string.common_save);
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public void onHttpResponse(ApiResponse<Void> result) {
+        hideProgressDialog();
+        if (result.isSuccess()){
+            showToastView(R.string.tips_update_success);
+            Global.setGender(getGender());
+            finish();
+        }else {
+            showToastView(result.message);
+        }
+    }
+
+    @Override
+    public void onHttpException(Exception e) {
+        hideProgressDialog();
+        showToastView(R.string.tips_update_failed);
+    }
+}

+ 72 - 0
app/src/main/java/com/bogo/android/account/ui/EditMottoActivity.java

@@ -0,0 +1,72 @@
+
+package com.bogo.android.account.ui;
+
+
+import android.view.Menu;
+import android.view.View;
+import android.widget.Button;
+
+import com.bogo.android.R;
+import com.bogo.android.account.api.AccountServiceManager;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.databinding.ActivityModifyMottoBinding;
+
+/**
+ * 修改签名
+ */
+public class EditMottoActivity extends BaseActivity<ActivityModifyMottoBinding> implements HttpResponseListener<Void> {
+
+
+    @Override
+    public BindingCompat<ActivityModifyMottoBinding> getBinding() {
+        ActivityModifyMottoBinding binding = ActivityModifyMottoBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    @Override
+    public void initComponents() {
+        binding.motto.setText(Global.getMotto());
+        binding.motto.requestFocus();
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view.getId() == R.id.button){
+            showProgressDialog(R.string.tips_save_loading);
+            String motto =  binding.motto.getText().toString().trim();
+            AccountServiceManager.updateMotto(motto,this);
+        }
+    }
+
+    @Override
+    public int getToolbarTitle() {
+        return R.string.label_setting_modify_motto;
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.single_button, menu);
+        Button button = menu.findItem(R.id.menu_button).getActionView().findViewById(R.id.button);
+        button.setOnClickListener(this);
+        button.setText(R.string.common_save);
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public void onHttpResponse(ApiResponse<Void> result) {
+        hideProgressDialog();
+        showToastView(R.string.tips_update_success);
+        Global.setMotto(binding.motto.getText().toString());
+        finish();
+    }
+
+    @Override
+    public void onHttpException(Exception e) {
+        hideProgressDialog();
+        showToastView(R.string.tips_update_failed);
+    }
+}

+ 178 - 0
app/src/main/java/com/bogo/android/account/ui/ForgotPasswordActivity.java

@@ -0,0 +1,178 @@
+
+package com.bogo.android.account.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.CountDownTimer;
+import android.telephony.PhoneNumberUtils;
+import android.text.Html;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.bogo.android.R;
+import com.bogo.android.account.api.LoginServiceManager;
+import com.bogo.android.account.api.MessageCodeServiceManager;
+import com.bogo.android.account.api.request.ResetPasswordRequest;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.constant.ResponseCode;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.listener.SimpleHttpRequestListener;
+import com.bogo.android.common.model.Country;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.common.util.MD5;
+import com.bogo.android.databinding.ActivityForgotPasswordBinding;
+
+public class ForgotPasswordActivity extends BaseActivity<ActivityForgotPasswordBinding> implements HttpResponseListener<Void> {
+
+ 
+    private Country country = Country.NORMAL;
+
+    @Override
+    public BindingCompat<ActivityForgotPasswordBinding> getBinding() {
+        ActivityForgotPasswordBinding binding = ActivityForgotPasswordBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    
+    @Override
+    public void initComponents() {
+  
+        binding.telephone.setHint(Html.fromHtml(getString(R.string.hint_input_register_telephone)));
+        binding.password.setHint(Html.fromHtml(getString(R.string.hint_input_new_password)));
+        binding.code.setHint(Html.fromHtml(getString(R.string.hint_input_validation_code)));
+
+        initCountryViews();
+    }
+
+    private void initCountryViews(){
+        binding.countryName.setText(country.name);
+        binding.countryCode.setText(country.code);
+        binding.iconCountry.setImageResource(country.icon);
+    }
+
+
+    private final CountDownTimer countDownTimer = new CountDownTimer(60 * 1000,1000) {
+        @Override
+        public void onTick(long millisUntilFinished) {
+            binding.codeButton.setText(getString(R.string.label_count_down_timer,millisUntilFinished / 1000));
+        }
+
+        @Override
+        public void onFinish() {
+            binding.codeButton.setEnabled(true);
+            binding.codeButton.setText(R.string.label_get_message_code);
+        }
+    };
+
+    protected int getToolbarTitle(){return R.string.title_reset_password;}
+
+
+    public void onCodeButtonClicked(View view) {
+
+        String telephone = binding.telephone.getText().toString().trim();
+
+        if (telephone.isEmpty()){
+            showToastView(R.string.tips_input_telephone);
+            return;
+        }
+
+        final String e164Number = PhoneNumberUtils.formatNumberToE164(telephone,country.id);
+        if (TextUtils.isEmpty(e164Number)){
+           showToastView(R.string.tips_telephone_format_error);
+           return;
+        }
+
+
+        LoginServiceManager.findId(e164Number,new SimpleHttpRequestListener<Long>(){
+            public void onHttpResponse(ApiResponse<Long> result) {
+                if (result.code == ResponseCode.CODE_404){
+                    showToastView(getString(R.string.tips_telephone_not_registered));
+                    return;
+                }
+
+                MessageCodeServiceManager.forgot(e164Number,new SimpleHttpRequestListener<String>(){
+                    public void onHttpResponse(ApiResponse<String> result) {
+                        countDownTimer.start();
+                        binding.codeButton.setEnabled(false);
+                        binding.code.setText(result.data);
+                    }
+                });
+            }
+        });
+
+    }
+
+    public void onResetButtonClicked(View view) {
+
+        if (TextUtils.isEmpty(binding.telephone.getText().toString().trim())){
+            showToastView(R.string.tips_input_telephone);
+            return;
+        }
+
+        String e164Number = PhoneNumberUtils.formatNumberToE164(binding.telephone.getText().toString().trim(),country.id);
+        if (TextUtils.isEmpty(e164Number)){
+            showToastView(R.string.tips_telephone_format_error);
+            return;
+        }
+
+        if (binding.code.getText().length() < 6){
+            showToastView(R.string.tips_input_message_code);
+            return;
+        }
+
+        if (binding.password.getText().length() < 6){
+            showToastView(R.string.tips_input_register_password);
+            return;
+        }
+
+
+        ResetPasswordRequest request = new ResetPasswordRequest();
+        request.setCode(binding.code.getText().toString().trim());
+        request.setTelephone(e164Number);
+        request.setPassword(MD5.digest(binding.password.getText().toString().trim()));
+
+        showProgressDialog(getString(R.string.tips_progress_reset_password));
+        LoginServiceManager.resetPassword(request,this);
+
+    }
+
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (resultCode == Activity.RESULT_OK && requestCode == 19) {
+            country = (Country) data.getSerializableExtra(Country.class.getName());
+            initCountryViews();
+        }
+    }
+
+    @Override
+    public void onHttpResponse(ApiResponse<Void> result) {
+
+        hideProgressDialog();
+
+        if (result.code == ResponseCode.CODE_403){
+            showToastView(getString(R.string.tips_message_code_wrong));
+            return;
+        }
+
+        showToastView(getString(R.string.tips_reset_password_done));
+        finish();
+    }
+
+    @Override
+    public void onHttpException(Exception e) {
+         hideProgressDialog();
+    }
+
+    public void onCountryClicked(View view) {
+        startActivityForResult(new Intent(this, CountrySelectorActivity.class), 19);
+    }
+
+    public void onClearButtonClicked(View view) {
+        binding.telephone.setText(null);
+    }
+}

+ 267 - 0
app/src/main/java/com/bogo/android/account/ui/LoginActivity.java

@@ -0,0 +1,267 @@
+
+package com.bogo.android.account.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.telephony.PhoneNumberUtils;
+import android.text.Editable;
+import android.text.Html;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.HideReturnsTransformationMethod;
+import android.text.method.PasswordTransformationMethod;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.core.app.NotificationManagerCompat;
+
+import com.bogo.android.R;
+import com.bogo.android.account.api.LoginServiceManager;
+import com.bogo.android.account.loader.CountryLoader;
+import com.bogo.android.common.App;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.constant.ResponseCode;
+import com.bogo.android.common.database.base.PrivateRoomDatabase;
+import com.bogo.android.common.entity.User;
+import com.bogo.android.common.listener.CloudImageApplyListener;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.listener.SimpleHttpRequestListener;
+import com.bogo.android.common.model.Country;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.common.util.FileURLBuilder;
+import com.bogo.android.databinding.ActivityLoginBinding;
+import com.bogo.android.home.ui.HomeActivity;
+import com.bogo.android.home.ui.SplashActivity;
+import com.bogo.messaging.PushManager;
+
+
+public class LoginActivity extends BaseActivity<ActivityLoginBinding> implements TextWatcher, CloudImageApplyListener, HttpResponseListener<User>, TextView.OnEditorActionListener {
+    private Country country = Country.NORMAL;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().setBackgroundDrawable(App.getAppBackground(this));
+    }
+
+    @Override
+    public BindingCompat<ActivityLoginBinding> getBinding() {
+        ActivityLoginBinding binding = ActivityLoginBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+    
+    @Override
+    public void initComponents() {
+        super.setStatusBarColor(Color.TRANSPARENT);
+        cancelNotification();
+        setDisplayHomeAsUpEnabled(false);
+
+        binding.telephone.setHint(Html.fromHtml(getString(R.string.hint_input_login_telephone)));
+        binding.password.setHint(Html.fromHtml(getString(R.string.hint_input_login_password)));
+
+        if (Global.hasAccount()) {
+            binding.icon.load(FileURLBuilder.getUserIconUrl(Global.getUid()),LoginActivity.this);
+            String telephone = Global.getTelephone();
+            country = CountryLoader.getLoader().match(telephone);
+            binding.telephone.setText(country.format(telephone));
+            binding.password.requestFocus();
+        }else {
+            binding.telephone.requestFocus();
+        }
+
+        initCountryViews();
+
+        binding.password.setOnEditorActionListener(this);
+        binding.telephone.addTextChangedListener(this);
+
+        binding.loginPanel.getBackground().setAlpha(232);
+    }
+
+    private void initCountryViews(){
+        binding.countryName.setText(country.name);
+        binding.countryCode.setText(country.code);
+        binding.iconCountry.setImageResource(country.icon);
+    }
+
+
+    public void onLoginButtonClicked(View v) {
+
+        if (!checkAccountAndPassword()){
+            return;
+        }
+
+        String e164Number = PhoneNumberUtils.formatNumberToE164(binding.telephone.getText().toString().trim(),country.id);
+        if (TextUtils.isEmpty(e164Number)){
+            showToastView(R.string.tips_telephone_format_error);
+            return;
+        }
+
+        showProgressDialog(R.string.tips_login_processing);
+
+        String password = binding.password.getText().toString().trim();
+        LoginServiceManager.login(e164Number,password,this);
+    }
+
+    public void onRegisterButtonClicked(View v) {
+        startActivity(new Intent(this, RegisterActivity.class));
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+        String telephone = binding.telephone.getText().toString().trim();
+
+        String e164Number = PhoneNumberUtils.formatNumberToE164(binding.telephone.getText().toString().trim(),country.id);
+
+        if (telephone.isEmpty() || TextUtils.isEmpty(e164Number)) {
+            binding.icon.setImageResource(R.drawable.icon_def_head);
+            return;
+        }
+
+        LoginServiceManager.findId(e164Number,new SimpleHttpRequestListener<Long>(){
+            public void onHttpResponse(ApiResponse<Long> response) {
+                if (response.code == ResponseCode.CODE_200){
+                    binding.icon.load(FileURLBuilder.getUserIconUrl(response.data),R.drawable.icon_def_head,LoginActivity.this);
+                }
+            }
+        });
+    }
+
+    private boolean checkAccountAndPassword() {
+        if (binding.telephone.getText().toString().isEmpty()) {
+            showToastView(R.string.tips_input_login_telephone);
+            return false;
+        }
+        if (binding.password.getText().toString().isEmpty()) {
+            showToastView(R.string.tips_input_login_password);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void onHttpResponse(ApiResponse<User> response) {
+        hideProgressDialog();
+        if (response.isSuccess()) {
+            handleLoginSuccess(response.data,response.token, response.timestamp);
+            return;
+        }
+        if (ResponseCode.CODE_403 == (response.code)) {
+            showToastView(R.string.tips_account_or_password_error);
+        }
+        if (ResponseCode.CODE_404 == (response.code)) {
+            showToastView(R.string.tips_account_not_found);
+        }
+        if (ResponseCode.CODE_423 == (response.code)) {
+            showToastView(R.string.tips_account_disabled);
+        }
+    }
+
+    private void handleLoginSuccess(User user,String token,Long timestamp) {
+
+        Global.addAccount(user,token,timestamp);
+
+        PrivateRoomDatabase.init(user.id);
+
+        Global.clearPrimaryDataLoadMark(user.id);
+        Global.clearSecondDataLoadMark(user.id);
+
+        startActivity(new Intent(this, HomeActivity.class));
+        overridePendingTransition(R.anim.activity_appear, R.anim.activity_disappear);
+
+        App.finish(SplashActivity.class);
+        finish();
+    }
+
+    @Override
+    public void onHttpException(Exception e) {
+        hideProgressDialog();
+        showToastView(R.string.tips_conn_server_failed);
+    }
+
+    @Override
+    public void onBackAction() {
+        if (getIntent().getAction() == null) {
+            PushManager.destroy(this);
+            android.os.Process.killProcess(android.os.Process.myPid());
+        }
+        this.finish();
+    }
+
+    @Override
+    public void onImageApplyFailure(Object key, ImageView target) {
+        binding.icon.setImageBitmap(null);
+    }
+
+    @Override
+    public void onImageApplySucceed(Object key, ImageView target, Drawable resource) {
+        binding.icon.startAnimation(AnimationUtils.loadAnimation(this, R.anim.appear));
+    }
+
+    private void cancelNotification() {
+        NotificationManagerCompat.from(this).cancelAll();
+    }
+
+
+    public void onClearButtonClicked(View view) {
+        binding.telephone.setText(null);
+    }
+
+    public void onLookButtonClicked(View view) {
+        if (view.isSelected()){
+            binding.password.setTransformationMethod(PasswordTransformationMethod.getInstance());
+        }else {
+            binding.password.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
+        }
+
+        view.setSelected(!view.isSelected());
+
+        binding.password.setSelection(binding.password.getText().length());
+
+    }
+
+
+
+    @Override
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        onLoginButtonClicked(null);
+        return false;
+    }
+
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (resultCode == Activity.RESULT_OK && requestCode == 19) {
+            country = (Country) data.getSerializableExtra(Country.class.getName());
+            initCountryViews();
+        }
+    }
+
+
+    public void onCountryClicked(View view) {
+        startActivityForResult(new Intent(this, CountrySelectorActivity.class), 19);
+    }
+
+    public void onForgotPasswordClicked(View view) {
+        startActivityForResult(new Intent(this, ForgotPasswordActivity.class), 19);
+    }
+}

+ 81 - 0
app/src/main/java/com/bogo/android/account/ui/PasswordActivity.java

@@ -0,0 +1,81 @@
+
+package com.bogo.android.account.ui;
+
+
+import android.text.Html;
+import android.view.View;
+
+import com.bogo.android.R;
+import com.bogo.android.account.api.AccountServiceManager;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.common.util.AppTools;
+import com.bogo.android.common.util.MD5;
+import com.bogo.android.databinding.ActivityModifyPasswordBinding;
+
+/**
+ * 修改密码
+ */
+public class PasswordActivity extends BaseActivity<ActivityModifyPasswordBinding> implements HttpResponseListener<Void> {
+
+
+    @Override
+    public void initComponents() {
+        binding.oldPassword.setHint(Html.fromHtml(getString(R.string.hint_input_old_password),Html.FROM_HTML_MODE_COMPACT));
+
+        binding.newPassword.setHint(Html.fromHtml(getString(R.string.hint_input_new_password),Html.FROM_HTML_MODE_COMPACT));
+
+    }
+
+    @Override
+    public BindingCompat<ActivityModifyPasswordBinding> getBinding() {
+        ActivityModifyPasswordBinding binding = ActivityModifyPasswordBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    public void onSaveButtonClicked(View view) {
+        if (AppTools.isNotBlank(binding.newPassword.getText().toString())
+                && AppTools.isNotBlank(binding.oldPassword.getText().toString())) {
+            performUpdateRequest();
+        }
+    }
+
+
+    private void performUpdateRequest() {
+
+        showProgressDialog(getString(R.string.tips_save_loading));
+        String oldPassword = MD5.digest(binding.oldPassword.getText().toString());
+        String newPassword = MD5.digest(binding.newPassword.getText().toString());
+
+        AccountServiceManager.updatePassword(oldPassword,newPassword,this);
+    }
+
+   @Override
+    public void onHttpResponse(ApiResponse<Void> result) {
+       hideProgressDialog();
+       if (result.isSuccess()) {
+            showToastView(R.string.tips_save_complete);
+            finish();
+            return;
+        }
+        if (!result.isSuccess()) {
+            showToastView(R.string.tips_old_password_error);
+        }
+    }
+
+    @Override
+    public void onHttpException(Exception e) {
+        hideProgressDialog();
+        showToastView(R.string.tips_update_failed);
+    }
+
+
+    @Override
+    public int getToolbarTitle() {
+        return R.string.label_setting_modify_password;
+    }
+
+
+}

+ 282 - 0
app/src/main/java/com/bogo/android/account/ui/ProfileActivity.java

@@ -0,0 +1,282 @@
+
+package com.bogo.android.account.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Color;
+import android.net.Uri;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import androidx.palette.graphics.Palette;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.R;
+import com.bogo.android.account.api.AccountServiceManager;
+import com.bogo.android.account.dialog.InputUserNameDialog;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.constant.Constant;
+import com.bogo.android.common.constant.FileBucket;
+import com.bogo.android.common.constant.IntentAction;
+import com.bogo.android.common.database.GlideVersionDatabase;
+import com.bogo.android.common.entity.User;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.listener.OSSFileUploadListener;
+import com.bogo.android.common.listener.OnInputCompleteClickListener;
+import com.bogo.android.common.model.FileResource;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.common.util.AppTools;
+import com.bogo.android.common.util.FileURLBuilder;
+import com.bogo.android.common.util.FileUploader;
+import com.bogo.android.databinding.ActivityProfileBinding;
+import com.bogo.android.friend.entity.Friend;
+import com.bogo.android.organization.database.DepartmentDatabase;
+import com.bogo.android.organization.database.DepartmentMemberDatabase;
+import com.bogo.android.organization.database.OrganizationDatabase;
+import com.bogo.android.organization.entity.Department;
+import com.bogo.android.organization.entity.DepartmentMember;
+import com.bogo.android.organization.entity.Organization;
+import com.yalantis.ucrop.UCrop;
+
+public class ProfileActivity extends BaseActivity<ActivityProfileBinding> implements OSSFileUploadListener, OnInputCompleteClickListener {
+
+
+    private User user;
+    private InputUserNameDialog nameDialog;
+
+    @Override
+    public BindingCompat<ActivityProfileBinding> getBinding() {
+        ActivityProfileBinding binding = ActivityProfileBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    @Override
+    public void initComponents() {
+        super.setStatusBarColor(Color.TRANSPARENT);
+
+        binding.itemIcon.setOnClickListener(this);
+        binding.itemMotto.setOnClickListener(this);
+        binding.itemName.setOnClickListener(this);
+        binding.itemEmail.setOnClickListener(this);
+        binding.itemGender.setOnClickListener(this);
+
+        nameDialog = new InputUserNameDialog(this);
+        nameDialog.setOnInputCompleteClickListener(this);
+
+        initOrganizationView();
+
+        initAppbarLayout();
+
+    }
+
+
+    private void initAppbarLayout(){
+
+
+
+        binding.wallpaper.setOnPaletteCompletedListener(palette -> {
+            Palette.Swatch swatch = palette.getVibrantSwatch();
+            if (swatch != null){
+               binding.collapsingToolbar.setContentScrimColor(swatch.getRgb());
+               binding.collapsingToolbar.setCollapsedTitleTextColor(swatch.getTitleTextColor());
+            }
+        });
+
+        binding.appbar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
+            if (-verticalOffset == appBarLayout.getTotalScrollRange()){
+               binding.collapsingToolbar.setTitle(getString(R.string.label_setting_profile));
+            }else {
+               binding.collapsingToolbar.setTitle(null);
+            }
+        });
+
+        binding.wallpaper.loadBannerOnly(FileURLBuilder.getUserBannerUrl(Global.getUid()));
+    }
+
+    private void initOrganizationView(){
+
+        long uid = Global.getUid();
+
+        Organization organization = OrganizationDatabase.findOne();
+        if (organization == null){
+            return;
+        }
+
+        binding.organizationView.organizationView.setVisibility(View.VISIBLE);
+
+        binding.organizationView.organizationName.setText(organization.name);
+
+        DepartmentMember member = DepartmentMemberDatabase.findOne(organization.id,uid);
+        if (member == null){
+            return;
+        }
+
+        binding.organizationView.memberTitle.setText(member.title);
+
+        Department department = DepartmentDatabase.findOne(member.departmentId);
+        if (department != null){
+            binding.organizationView.departmentName.setText(department.name);
+        }
+    }
+
+    private void initBasicInfoViews(){
+        user = Global.getCurrentUser();
+        binding.name.setText(user.name);
+        binding.email.setText(user.email);
+        binding.gender.setText(user.gender == Friend.GENDER_MAN ? R.string.gender_man : R.string.gender_woman);
+        binding.telephone.setText(user.telephone);
+        binding.motto.setText(user.motto);
+        binding.uid.setText(String.valueOf(user.id));
+        binding.icon.load(FileURLBuilder.getUserIconUrl(user.id), R.drawable.icon_def_head);
+    }
+
+    @Override
+    protected boolean isWindowFullscreen(){
+        return true;
+    }
+
+
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        initBasicInfoViews();
+    }
+
+    @Override
+    public void onClick(View v) {
+
+        if (v.getId() == R.id.item_icon){
+            AppTools.openImageDocument(this);
+        }
+
+
+        if (v.getId() == R.id.item_email){
+            startActivity(new Intent(this, EditEmailActivity.class));
+        }
+
+        if (v.getId() == R.id.item_gender){
+            startActivity(new Intent(this, EditGenderActivity.class));
+        }
+
+        if (v.getId() == R.id.item_motto){
+            startActivity(new Intent(this, EditMottoActivity.class));
+        }
+
+        if (v.getId() == R.id.item_name){
+            nameDialog.show();
+        }
+    }
+
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (resultCode == Activity.RESULT_OK && requestCode == Constant.SYSTEM_IMAGE_REQUEST_CODE) {
+            AppTools.startIconPhotoCrop(this, data.getData());
+        }
+
+        if (resultCode == Activity.RESULT_OK && requestCode == Constant.SYSTEM_BANNER_REQUEST_CODE) {
+            AppTools.startWallpaperCrop(this, data.getData(),Constant.SYSTEM_BANNER_CROP_REQUEST_CODE);
+        }
+
+
+        if (resultCode == RESULT_OK && requestCode == UCrop.REQUEST_CROP) {
+            Uri resultUri = UCrop.getOutput(data);
+            FileResource resource = FileResource.of(FileBucket.USER_ICON,String.valueOf(user.id),resultUri);
+            FileUploader.asyncUpload(resource, this);
+            showProgressDialog(getString(R.string.tips_file_uploading, 0));
+        }
+
+        if (resultCode == RESULT_OK && requestCode == Constant.SYSTEM_BANNER_CROP_REQUEST_CODE) {
+            Uri resultUri = UCrop.getOutput(data);
+            FileResource resource = FileResource.of(FileBucket.USER_BANNER,String.valueOf(user.id),resultUri);
+            FileUploader.asyncUpload(resource, this);
+            showProgressDialog(getString(R.string.tips_file_uploading, 0));
+        }
+    }
+
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+
+        getMenuInflater().inflate(R.menu.user_profile, menu);
+
+        return super.onCreateOptionsMenu(menu);
+    }
+
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+
+        if (item.getItemId() == R.id.menu_password) {
+            startActivity(new Intent(this, PasswordActivity.class));
+        }
+
+        if (item.getItemId() == R.id.menu_wallpaper) {
+            AppTools.openImageDocument(this, Constant.SYSTEM_BANNER_REQUEST_CODE);
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+
+    @Override
+    public void onUploadCompleted(FileResource resource) {
+        hideProgressDialog();
+
+        if (resource.bucket == FileBucket.USER_ICON){
+            String iconUrl = FileURLBuilder.getUserIconUrl(user.id);
+            GlideVersionDatabase.add(iconUrl);
+            binding.icon.load(iconUrl);
+            BogoApplication.sendLocalBroadcast(new Intent(IntentAction.ACTION_LOGO_CHANGED));
+        }
+
+
+        if (resource.bucket == FileBucket.USER_BANNER){
+            String bannerUrl = FileURLBuilder.getUserBannerUrl(user.id);
+            GlideVersionDatabase.add(bannerUrl);
+            binding.wallpaper.loadBannerOnly(bannerUrl);
+        }
+    }
+
+    @Override
+    public void onUploadProgress(String key, float progress) {
+        showProgressDialog(getString(R.string.tips_file_uploading, (int) progress));
+    }
+
+    @Override
+    public void onUploadFailed(FileResource resource, Exception e) {
+        hideProgressDialog();
+        showToastView(R.string.tips_logo_update_error);
+    }
+
+    @Override
+    public void onInputCompleted(String text) {
+        showProgressDialog(R.string.tips_save_loading);
+
+        AccountServiceManager.updateName(text, new HttpResponseListener<Void>() {
+            @Override
+            public void onHttpResponse(ApiResponse<Void> result) {
+                hideProgressDialog();
+
+                showToastView(R.string.tips_update_success);
+
+                Global.setName(text);
+
+                binding.name.setText(text);
+
+            }
+
+            @Override
+            public void onHttpException(Exception e) {
+                hideProgressDialog();
+                showToastView(R.string.tips_update_failed);
+            }
+        });
+    }
+
+
+}

+ 46 - 0
app/src/main/java/com/bogo/android/account/ui/QrCodeActivity.java

@@ -0,0 +1,46 @@
+
+package com.bogo.android.account.ui;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+
+import com.bogo.android.R;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.constant.Constant;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.common.util.FileURLBuilder;
+import com.bogo.android.databinding.ActivityQrCodeBinding;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.journeyapps.barcodescanner.BarcodeEncoder;
+
+public class QrCodeActivity extends BaseActivity<ActivityQrCodeBinding> {
+
+    @Override
+    public void initComponents() {
+
+
+        try {
+            int size = Resources.getSystem().getDisplayMetrics().heightPixels;
+            QRCodeWriter writer = new QRCodeWriter();
+            BitMatrix matrix = writer.encode(Constant.UID_QR_PATH + Global.getUid(), BarcodeFormat.QR_CODE, size,size);
+            BarcodeEncoder encoder = new BarcodeEncoder();
+            Bitmap bitmap = encoder.createBitmap(matrix);
+            binding.qrCode.setImageBitmap(bitmap);
+        } catch (WriterException ignore) {}
+
+        binding.icon.load(FileURLBuilder.getUserIconUrl(Global.getUid()), R.drawable.icon_def_head);
+        binding.name.setText(Global.getName());
+    }
+
+    @Override
+    public BindingCompat<ActivityQrCodeBinding> getBinding() {
+        ActivityQrCodeBinding binding = ActivityQrCodeBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+
+}

+ 265 - 0
app/src/main/java/com/bogo/android/account/ui/RegisterActivity.java

@@ -0,0 +1,265 @@
+
+package com.bogo.android.account.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.CountDownTimer;
+import android.telephony.PhoneNumberUtils;
+import android.text.Html;
+import android.text.TextUtils;
+import android.view.View;
+
+import androidx.annotation.StringRes;
+
+import com.bogo.android.R;
+import com.bogo.android.account.api.LoginServiceManager;
+import com.bogo.android.account.api.MessageCodeServiceManager;
+import com.bogo.android.account.api.request.RegisterRequest;
+import com.bogo.android.common.App;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.constant.FileBucket;
+import com.bogo.android.common.constant.ResponseCode;
+import com.bogo.android.common.database.base.PrivateRoomDatabase;
+import com.bogo.android.common.entity.User;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.listener.OSSFileUploadListener;
+import com.bogo.android.common.listener.SimpleHttpRequestListener;
+import com.bogo.android.common.model.Country;
+import com.bogo.android.common.model.FileResource;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.common.util.AppTools;
+import com.bogo.android.common.util.FileUploader;
+import com.bogo.android.common.util.MD5;
+import com.bogo.android.databinding.ActivityRegisterBinding;
+import com.bogo.android.home.ui.HomeActivity;
+import com.bogo.android.home.ui.SplashActivity;
+import com.yalantis.ucrop.UCrop;
+
+public class RegisterActivity extends BaseActivity<ActivityRegisterBinding> implements HttpResponseListener<User>, OSSFileUploadListener {
+
+
+    private FileResource fileResource;
+    private Country country = Country.NORMAL;
+
+    @Override
+    public BindingCompat<ActivityRegisterBinding> getBinding() {
+        ActivityRegisterBinding binding = ActivityRegisterBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+    
+    @Override
+    public void initComponents() {
+
+        binding.telephone.setHint(Html.fromHtml(getString(R.string.hint_input_register_telephone)));
+        binding.password.setHint(Html.fromHtml(getString(R.string.hint_input_register_password)));
+        binding.code.setHint(Html.fromHtml(getString(R.string.hint_input_validation_code)));
+        binding.name.setHint(Html.fromHtml(getString(R.string.hint_input_register_name)));
+
+        initCountryViews();
+
+    }
+
+    private void initCountryViews(){
+        binding.countryCode.setText(country.code);
+        binding.iconCountry.setImageResource(country.icon);
+    }
+
+
+    private final CountDownTimer countDownTimer = new CountDownTimer(60 * 1000,1000) {
+        @Override
+        public void onTick(long millisUntilFinished) {
+            binding.codeButton.setText(getString(R.string.label_count_down_timer,millisUntilFinished / 1000));
+        }
+
+        @Override
+        public void onFinish() {
+            binding.codeButton.setEnabled(true);
+            binding.codeButton.setText(R.string.label_get_message_code);
+        }
+    };
+
+
+    @Override
+    public int getToolbarTitle() {
+        return R.string.title_register;
+    }
+
+    public void onLogoClicked(View view) {
+        AppTools.openImageDocument(this);
+    }
+
+    public void onCodeButtonClicked(View view) {
+
+        String telephone = binding.telephone.getText().toString().trim();
+
+        if (telephone.length() == 0){
+            showToastView(R.string.tips_input_telephone);
+            return;
+        }
+
+        String e164Number = PhoneNumberUtils.formatNumberToE164(telephone,country.id);
+        if (TextUtils.isEmpty(e164Number)){
+           showToastView(R.string.tips_telephone_format_error);
+           return;
+        }
+
+        MessageCodeServiceManager.register(e164Number,new SimpleHttpRequestListener<String>(){
+            public void onHttpResponse(ApiResponse<String> result) {
+                countDownTimer.start();
+                binding.codeButton.setEnabled(false);
+                binding.code.setText(result.data);
+            }
+        });
+    }
+
+    public void onRegisterButtonClicked(View view) {
+
+        if (TextUtils.isEmpty(binding.name.getText().toString().trim())){
+            showToastView(R.string.tips_input_name);
+            return;
+        }
+
+        if (TextUtils.isEmpty(binding.telephone.getText().toString().trim())){
+            showToastView(R.string.tips_input_telephone);
+            return;
+        }
+
+        String e164Number = PhoneNumberUtils.formatNumberToE164(binding.telephone.getText().toString().trim(),country.id);
+        if (TextUtils.isEmpty(e164Number)){
+            showToastView(R.string.tips_telephone_format_error);
+            return;
+        }
+
+        if (binding.code.getText().length() < 6){
+            showToastView(R.string.tips_input_message_code);
+            return;
+        }
+
+        if (binding.password.getText().length() < 6){
+            showToastView(R.string.tips_input_register_password);
+            return;
+        }
+
+
+        RegisterRequest request = new RegisterRequest();
+        request.setCode(binding.code.getText().toString().trim());
+        request.setName(binding.name.getText().toString().trim());
+        request.setTelephone(e164Number);
+        request.setPassword(MD5.digest(binding.password.getText().toString().trim()));
+
+        showProgressDialog(getString(R.string.tips_progress_register_ing));
+        LoginServiceManager.register(request,this);
+
+    }
+
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (resultCode == Activity.RESULT_OK && requestCode == 9) {
+            AppTools.startIconPhotoCrop(this, data.getData());
+        }
+
+        if (resultCode == RESULT_OK && requestCode == UCrop.REQUEST_CROP) {
+            Uri resultUri = UCrop.getOutput(data);
+            fileResource = FileResource.of(FileBucket.USER_ICON,null,resultUri);
+            binding.logo.load(resultUri, 0);
+        }
+
+        if (resultCode == Activity.RESULT_OK && requestCode == 19) {
+            country = (Country) data.getSerializableExtra(Country.class.getName());
+            initCountryViews();
+        }
+    }
+
+    @Override
+    public void onHttpResponse(ApiResponse<User> response) {
+
+        if (response.code == ResponseCode.CODE_403){
+            showErrorToastMessage(R.string.tips_message_code_wrong);
+            return;
+        }
+
+        if (response.code == ResponseCode.CODE_409){
+            showErrorToastMessage(R.string.tips_register_telephone_existing);
+            return;
+        }
+
+        if (response.code != ResponseCode.CODE_200 && !TextUtils.isEmpty(response.message)){
+            showErrorToastMessage(response.message);
+            return;
+        }
+
+        PrivateRoomDatabase.init(response.data.id);
+
+        Global.addAccount(response.data,response.token,response.timestamp);
+
+        if (fileResource == null){
+            gotoHomeActivity();
+            return;
+        }
+
+        fileResource.name = String.valueOf(response.data.id);
+
+        /*
+         * 需要监听头像上传完成再进入首页
+         */
+        FileUploader.asyncUpload(fileResource,this);
+
+    }
+
+    private void showErrorToastMessage(@StringRes int stringId){
+        showErrorToastMessage(getString(stringId));
+    }
+
+    private void showErrorToastMessage(String message){
+        hideProgressDialog();
+        showToastView(message);
+    }
+    private void gotoHomeActivity(){
+
+        hideProgressDialog();
+
+        startActivity(new Intent(this, HomeActivity.class));
+
+        overridePendingTransition(R.anim.activity_appear, R.anim.activity_disappear);
+
+        App.finish(SplashActivity.class);
+        App.finish(LoginActivity.class);
+
+        finish();
+    }
+
+    @Override
+    public void onHttpException(Exception e) {
+         hideProgressDialog();
+    }
+
+    public void onCountryClicked(View view) {
+        startActivityForResult(new Intent(this, CountrySelectorActivity.class), 19);
+    }
+
+    public void onClearButtonClicked(View view) {
+        binding.telephone.setText(null);
+    }
+
+    @Override
+    public void onUploadCompleted(FileResource resource) {
+        gotoHomeActivity();
+    }
+
+    @Override
+    public void onUploadProgress(String key, float progress) {
+
+    }
+
+    @Override
+    public void onUploadFailed(FileResource resource, Exception e) {
+        gotoHomeActivity();
+    }
+}

+ 130 - 0
app/src/main/java/com/bogo/android/account/widget/ColorBallView.java

@@ -0,0 +1,130 @@
+
+package com.bogo.android.account.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.FrameLayout;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+
+import java.util.Random;
+
+
+public class ColorBallView extends View implements Animation.AnimationListener {
+    private final Paint mPaint;
+    private final int oX;
+    private final int oY;
+    private final int mRadius;
+    private int mX;
+    private int mY;
+    private TranslateAnimation animation;
+    private final int moveRange;
+    private final boolean canMove;
+
+    private int mStartColor;
+    private int mEndColor;
+    private final int shadowRadius ;
+
+    public ColorBallView(Context context, int mX, int mY, int mRadius, int color, boolean canMove,int shadowRadius) {
+        super(context, null);
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setStyle(Paint.Style.FILL);
+        this.mRadius = mRadius;
+        this.oX = mX;
+        this.oY = mY;
+        mPaint.setColor(color);
+        this.canMove = canMove;
+        moveRange = (int) (Resources.getSystem().getDisplayMetrics().density * 5);
+        this.shadowRadius = shadowRadius;
+    }
+    public ColorBallView(Context context, int mX, int mY, int mRadius, int color, boolean canMove) {
+        this(context,mX,mY,mRadius,color,canMove,20);
+    }
+    public void setColor(@ColorInt int startColor, @ColorInt int endColor) {
+        mStartColor = startColor;
+        mEndColor = endColor;
+        invalidate();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (changed && canMove) {
+            this.mX = left;
+            this.mY = top;
+            startAnim();
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (animation != null) {
+            animation.cancel();
+        }
+    }
+
+    public void setMargin(int left, int top) {
+        ((FrameLayout.LayoutParams) getLayoutParams()).setMargins(left, top, 0, 0);
+    }
+
+    @Override
+    protected void onDraw(@NonNull Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mStartColor != 0 && mEndColor != 0) {
+            mPaint.setShader(new LinearGradient(oX, oY, mRadius, mRadius, mStartColor, mEndColor, Shader.TileMode.MIRROR));
+        }
+
+        mPaint.setShadowLayer(shadowRadius, 0, 0, mPaint.getColor());
+
+        canvas.drawCircle(oX, oY, mRadius, mPaint);
+
+
+    }
+
+    private void startAnim() {
+
+        int random = new Random().nextInt(moveRange) * -1 + moveRange;
+        int tX = (int) (getX() + random);
+        random = new Random().nextInt(moveRange) * -1 + moveRange;
+        int tY = (int) (getY() + random);
+        animation = new TranslateAnimation(mX, tX, mY, tY);
+        int duration = 2000;
+        animation.setDuration(duration);
+        animation.setRepeatCount(0);
+        animation.setAnimationListener(this);
+        animation.setFillAfter(true);
+        this.startAnimation(animation);
+        mY = tY;
+        mX = tX;
+    }
+
+    @Override
+    public void onAnimationStart(Animation animation) {
+    }
+
+    @Override
+    public void onAnimationEnd(Animation animation) {
+        startAnim();
+    }
+
+    @Override
+    public void onAnimationRepeat(Animation animation) {
+
+    }
+
+    public void stopAnimation() {
+        animation.cancel();
+        this.clearAnimation();
+    }
+}

+ 127 - 0
app/src/main/java/com/bogo/android/account/widget/MeteorWallpaperView.java

@@ -0,0 +1,127 @@
+
+package com.bogo.android.account.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Shader;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.bogo.android.common.util.AppTools;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class MeteorWallpaperView extends View {
+    private static final int METEOR_SIZE = 7;
+    private static final double METEOR_ANGEL = 45 * (Math.PI / 180);
+    private static final int MOVE_RATE = 1;
+
+    private final Paint mColorPaint;
+    private final DisplayMetrics displayMetrics;
+    private final List<Meteor> meteors = new ArrayList<>(METEOR_SIZE);
+    private final int[] speedArray = new int[]{8,9,10,11,12,13,14,15,16};
+    private final int[] colors = new int[]{0XFFFFFFFF,0XA0FFFFFF,0x00000000};
+    private final float[] positions = new float[]{0F,0.03F,1F};
+    private final Random random = new Random();
+
+
+    public MeteorWallpaperView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mColorPaint = new Paint();
+        mColorPaint.setAntiAlias(true);
+        mColorPaint.setStyle(Paint.Style.FILL);
+        mColorPaint.setColor(0xFFFFFFFF);
+        mColorPaint.setStrokeCap( Paint.Cap.ROUND );       // 线帽,即画的线条两端是否带有圆角,ROUND,圆角
+        displayMetrics = Resources.getSystem().getDisplayMetrics();
+        batchCreateMeteors();
+    }
+
+    private void batchCreateMeteors(){
+       for (int i = 0 ;i < METEOR_SIZE;i++){
+           meteors.add(createOneMeteor());
+       }
+        mHandler.sendEmptyMessageDelayed(0,MOVE_RATE);
+    }
+
+    private void recreateMeteor(Meteor meteor ){
+        meteor.alpha = random.nextInt(156) + 100;
+        meteor.length = (10 + random.nextInt(8)) * displayMetrics.widthPixels / 100;
+        meteor.size = AppTools.dip2px(random.nextInt(3) + 1);
+        meteor.speed = speedArray[random.nextInt(speedArray.length)];
+        meteor.origin = new Point();
+
+        int deviation = meteor.length * random.nextInt(6);
+
+        meteor.origin.x = displayMetrics.widthPixels + deviation;
+        meteor.origin.y = (int) ((displayMetrics.heightPixels  * ( -40 + random.nextInt(75)) / 100F) -  deviation);
+        meteor.current  = meteor.origin;
+    }
+
+    private Meteor createOneMeteor(){
+        Meteor meteor = new Meteor();
+        recreateMeteor(meteor);
+        return meteor;
+    }
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(@NonNull Message message) {
+            for (Meteor meteor : meteors){
+                int endX = (int) (meteor.current.x + Math.cos(METEOR_ANGEL) * meteor.length);
+                int endY = (int) (meteor.current.y - Math.sin(METEOR_ANGEL) * meteor.length);
+                if (endX <= 0 || endY >= displayMetrics.heightPixels){
+                    recreateMeteor(meteor);
+                }else {
+
+                    meteor.current.x -= meteor.speed;
+                    meteor.current.y += meteor.speed;
+                }
+            }
+            invalidate();
+        }
+    };
+
+
+    @Override
+    public void onDraw(@NonNull Canvas canvas) {
+        for (Meteor meteor : meteors){
+            int endX = (int) (meteor.current.x + Math.cos(METEOR_ANGEL) * meteor.length);
+            int endY = (int) (meteor.current.y - Math.sin(METEOR_ANGEL) * meteor.length);
+            mColorPaint.setStrokeWidth(meteor.size);
+            mColorPaint.setAlpha(Math.max(0,meteor.alpha -= 0.1));
+            mColorPaint.setShader(new LinearGradient(meteor.current.x,meteor.current.y,endX,endY,colors,positions,Shader.TileMode.MIRROR));
+            canvas.drawLine(meteor.current.x,meteor.current.y, endX,endY,mColorPaint);
+        }
+
+        mHandler.sendEmptyMessageDelayed(0,MOVE_RATE);
+
+    }
+
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    private static class Meteor{
+
+        Point origin;
+        Point current;
+        int length;
+        int size;
+        int speed;
+        int alpha;
+    }
+
+}

+ 131 - 0
app/src/main/java/com/bogo/android/account/widget/RainbowBallView.java

@@ -0,0 +1,131 @@
+
+package com.bogo.android.account.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.animation.TranslateAnimation;
+import android.widget.FrameLayout;
+
+public class RainbowBallView extends FrameLayout {
+    private final int mWidth;
+    private final int deviationRange;
+    private ColorBallView yellowBall;
+    private ColorBallView redBall;
+    private ColorBallView blueBall;
+    private ColorBallView greenBall;
+    private ColorBallView pinkBall;
+    private ColorBallView orangeBall;
+
+    public RainbowBallView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        deviationRange = (int) (Resources.getSystem().getDisplayMetrics().density * 25);
+        mWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
+    }
+
+
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+
+
+
+        blueBall = new ColorBallView(getContext(), (int) (- deviationRange * 2.1), (int) (mWidth / 3.5), (mWidth / 2), 0xFF6BCFF4, true);
+        addView(blueBall, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+        blueBall.setMargin(-deviationRange - deviationRange / 5, -deviationRange - deviationRange / 5);
+        blueBall.setColor(0xc86BCFF4, 0xFF6BCFF4);
+
+
+        redBall = new ColorBallView(getContext(), (int) (- deviationRange * 1.9), (mWidth / 7), (mWidth / 2), 0xFFFE3E47, true);
+        addView(redBall, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+        redBall.setColor(0xc8FF4C23, 0xffE21674);
+        redBall.setMargin(-deviationRange , -deviationRange);
+
+
+        yellowBall = new ColorBallView(getContext(), (int) (- deviationRange * 1.7), 0, (mWidth / 2), 0xFFF4C900, true);
+        addView(yellowBall, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+        yellowBall.setColor(0xc8F4C900, 0xFFF4C900);
+        yellowBall.setMargin(-deviationRange + deviationRange / 5, -deviationRange);
+
+
+
+        pinkBall = new ColorBallView(getContext(), (int)(mWidth + deviationRange * 2.1), (int) (mWidth / 3.5), (mWidth / 2), 0xFF6BCFF4, true);
+        addView(pinkBall, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+        pinkBall.setMargin(Math.abs(-deviationRange - deviationRange / 5), -deviationRange - deviationRange / 5);
+        pinkBall.setColor(0xC89c27b0, 0xFf9c27b0);
+
+
+        orangeBall = new ColorBallView(getContext(), (int) (mWidth + deviationRange * 1.9), (mWidth / 7), (mWidth / 2), 0xFFFE3E47, true);
+        addView(orangeBall, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+        orangeBall.setColor(0xC8ff9800, 0xFfff9800);
+        orangeBall.setMargin(deviationRange , -deviationRange);
+
+
+        greenBall= new ColorBallView(getContext(), (int) (mWidth + deviationRange * 1.7), 0, (mWidth / 2), 0xFFF4C900, true);
+        addView(greenBall, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+        greenBall.setColor(0xC808995A, 0xFf99CC66);
+        greenBall.setMargin(Math.abs(-deviationRange + deviationRange / 5), -deviationRange);
+
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+        int mHeight = (int) (mWidth / 3.5  + (mWidth / 2) - 2 * deviationRange);
+
+        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY));
+    }
+
+
+    public void runaway() {
+
+
+        TranslateAnimation animation = new TranslateAnimation(0, -(mWidth / 2), 0, 0);
+        animation.setRepeatCount(0);
+        animation.setDuration(200);
+        animation.setFillAfter(true);
+
+        TranslateAnimation animation1 = new TranslateAnimation(0, -(mWidth / 2), 0, 0);
+        animation1.setRepeatCount(0);
+        animation1.setDuration(300);
+        animation1.setFillAfter(true);
+
+        TranslateAnimation animation2 = new TranslateAnimation(0, -(mWidth / 2), 0, 0);
+        animation2.setRepeatCount(0);
+        animation2.setDuration(400);
+        animation2.setFillAfter(true);
+
+        TranslateAnimation animation3 = new TranslateAnimation(0, mWidth - (mWidth / 2), 0, 0);
+        animation3.setRepeatCount(0);
+        animation3.setDuration(200);
+        animation3.setFillAfter(true);
+
+        TranslateAnimation animation4 = new TranslateAnimation(0, mWidth - (mWidth / 2), 0, 0);
+        animation4.setRepeatCount(0);
+        animation4.setDuration(300);
+        animation4.setFillAfter(true);
+
+        TranslateAnimation animation5 = new TranslateAnimation(0, mWidth - (mWidth / 2), 0, 0);
+        animation5.setRepeatCount(0);
+        animation5.setDuration(400);
+        animation5.setFillAfter(true);
+
+
+        blueBall.setAnimation(animation);
+        redBall.setAnimation(animation1);
+        yellowBall.setAnimation(animation2);
+
+        orangeBall.setAnimation(animation3);
+        pinkBall.setAnimation(animation4);
+        greenBall.setAnimation(animation5);
+
+        animation.start();
+        animation1.start();
+        animation2.start();
+        animation3.start();
+        animation4.start();
+        animation5.start();
+    }
+
+
+}

+ 327 - 0
app/src/main/java/com/bogo/android/common/App.java

@@ -0,0 +1,327 @@
+package com.bogo.android.common;
+
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.AttrRes;
+import androidx.annotation.ColorRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.appcompat.view.ContextThemeWrapper;
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.NotificationManagerCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.core.view.WindowInsetsControllerCompat;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.R;
+import com.bogo.android.common.model.CloudFile;
+import com.bogo.android.common.model.MapAddress;
+import com.bogo.android.common.ui.BaseActivity;
+import com.bogo.android.common.ui.MapViewActivity;
+import com.bogo.android.common.ui.WebViewActivity;
+import com.bogo.android.common.util.CharacterParser;
+import com.bogo.android.home.model.NameSort;
+import com.bogo.android.message.model.ChatLink;
+import com.bogo.android.message.model.ChatMap;
+import com.bogo.android.message.ui.FileViewerActivity;
+import com.bogo.android.webrtc.ui.LivekitMeetingCalleeActivity;
+import com.bogo.android.webrtc.ui.LivekitMeetingCallerActivity;
+import com.bogo.android.webrtc.ui.VideoCallingActivity;
+import com.bogo.android.webrtc.ui.VideoIncomingCallActivity;
+import com.bogo.android.webrtc.ui.VoiceCallingActivity;
+import com.bogo.android.webrtc.ui.VoiceIncomingCallActivity;
+import com.google.android.material.color.MaterialColors;
+
+public class App {
+    public static void finish(Class<? extends Activity> tClass){
+        BogoApplication.getInstance().finish(tClass);
+    }
+
+    public static boolean isInBackground(){
+        return BogoApplication.getInstance().isAppInBackground();
+    }
+
+    public static boolean isInForeground(){
+        return !isInBackground();
+    }
+
+    public static boolean isFinished(Class<? extends Activity> tClass){
+        return !isRunning(tClass);
+    }
+
+
+    public static boolean isRunning(Class<? extends Activity> tClass){
+        return BogoApplication.getInstance().isActivityStarted(tClass);
+    }
+
+    public static void openTextMagnifier(String content) {
+        BogoApplication.getInstance().startTextMagnifierActivity(content);
+    }
+
+    public static void openFileView(CloudFile file){
+        Intent intent = new Intent(BogoApplication.getInstance(), FileViewerActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(CloudFile.class.getName(),file);
+        BogoApplication.getInstance().startActivity(intent);
+    }
+
+    public static void openMapView(ChatMap map){
+        Intent intent = new Intent(BogoApplication.getInstance(), MapViewActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(MapAddress.class.getName(), map);
+        BogoApplication.getInstance().startActivity(intent);
+    }
+    public static void openWebView(ChatLink link) {
+        Intent intent = new Intent(BogoApplication.getInstance(), WebViewActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setData(Uri.parse(link.url));
+        BogoApplication.getInstance().startActivity(intent);
+    }
+
+    public static boolean isPhoneCallBusy(){
+        return Global.getPhoneCallState() != TelephonyManager.CALL_STATE_IDLE
+                || Global.getIncomingCallState() != TelephonyManager.CALL_STATE_IDLE
+                || isRunning(VoiceCallingActivity.class)
+                || isRunning(VoiceIncomingCallActivity.class)
+                || isRunning(VideoCallingActivity.class)
+                || isRunning(VideoIncomingCallActivity.class)
+                || isRunning(LivekitMeetingCallerActivity.class)
+                || isRunning(LivekitMeetingCalleeActivity.class);
+    }
+
+
+    public static int getColor(@NonNull View view, @AttrRes int colorAttributeResId){
+        return MaterialColors.getColor(view, colorAttributeResId);
+    }
+
+    public static ColorStateList getColorStateList(@NonNull View view, @AttrRes int colorAttributeResId){
+        return MaterialColors.getColorStateListOrNull(view.getContext(), colorAttributeResId);
+    }
+    public static int getColor(Context context,@AttrRes int colorAttributeResId){
+        return MaterialColors.getColor(context, colorAttributeResId,colorAttributeResId);
+    }
+
+    public static void setMaterialTextColor(TextView textView, @AttrRes int colorAttributeResId){
+        textView.setTextColor(MaterialColors.getColor(textView, colorAttributeResId));
+    }
+
+    public static void setTextColor(TextView textView, @ColorRes int colorAttributeResId){
+        textView.setTextColor(ContextCompat.getColor(textView.getContext(),colorAttributeResId));
+    }
+
+    public static void startActivityForResult(View view,Intent intent,int code){
+        Activity activity;
+        if (view.getContext() instanceof Activity){
+            activity = (Activity) view.getContext();
+        }else {
+            activity = (Activity) ((ContextWrapper) view.getContext()).getBaseContext();
+        }
+        activity.startActivityForResult(intent,code);
+    }
+
+    public static AppCompatActivity getActivity(View view){
+        return getActivity(view.getContext());
+    }
+
+    public static AppCompatActivity getActivity(Context context){
+        if (context instanceof AppCompatActivity){
+            return (AppCompatActivity) context;
+        }
+        if (context instanceof ContextThemeWrapper){
+            Context baseContext = ((ContextWrapper) context).getBaseContext();
+            return (AppCompatActivity) baseContext;
+        }
+        if (context instanceof android.view.ContextThemeWrapper){
+            Context baseContext = ((android.view.ContextThemeWrapper) context).getBaseContext();
+            return (AppCompatActivity) baseContext;
+        }
+        return null;
+    }
+
+    public static void dismiss(Dialog dialog){
+        if (dialog != null && dialog.isShowing()){
+            dialog.dismiss();
+        }
+    }
+
+    public static void moveMainTaskToFront(){
+        Activity mainActivity = BogoApplication.getInstance().getMainActivity();
+        if (mainActivity == null){
+            return;
+        }
+        ActivityManager activityManager = ContextCompat.getSystemService(mainActivity,ActivityManager.class);
+        if (activityManager == null){
+            return;
+        }
+        activityManager.moveTaskToFront(mainActivity.getTaskId(), ActivityManager.MOVE_TASK_WITH_HOME);
+    }
+
+    public static boolean hasAllPermissions(Context context,String... permissions){
+        for (String permission : permissions){
+            if (!hasPermission(context,permission)){
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean hasPermission(Context context,String permission){
+        return ActivityCompat.checkSelfPermission(context,permission) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    public static boolean hasAnyPermissions(Context context,String... permissions){
+        for (String permission : permissions){
+            if (hasPermission(context,permission)){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static void requestPermissions(Activity activity,int code,String... permissions){
+        ActivityCompat.requestPermissions(activity,permissions,code);
+    }
+
+    public static Drawable getAppBackground(Context context){
+
+        BitmapDrawable drawable = (BitmapDrawable) ContextCompat.getDrawable(context, R.drawable.main_theme_background);
+        if (drawable == null || drawable.getBitmap() == null){
+            return null;
+        }
+
+        Bitmap bitmap = drawable.getBitmap();
+
+        int windowWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
+
+        int windowHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
+
+        int imageWidth = bitmap.getWidth();
+
+        int imageHeight = bitmap.getHeight();
+
+        int cropHeight =  windowHeight * imageWidth / windowWidth;
+
+        if (cropHeight <= imageHeight){
+            Bitmap cropBitmap = Bitmap.createBitmap(bitmap,0,imageHeight - cropHeight,imageWidth,cropHeight);
+            return new BitmapDrawable(context.getResources(),cropBitmap);
+        }
+
+        int cropWidth =  windowWidth * imageHeight / windowHeight;
+
+        Bitmap cropBitmap = Bitmap.createBitmap(bitmap,(imageWidth-cropWidth) / 2,0,cropWidth,imageHeight);
+        return new BitmapDrawable(context.getResources(),cropBitmap);
+
+    }
+
+    public static String getString(@StringRes int resId, Object... formatArgs){
+        Context context = ContextCompat.getContextForLanguage(BogoApplication.getInstance());
+        return context.getString(resId,formatArgs);
+    }
+
+    public static String getString(@StringRes int resId){
+        Context context = ContextCompat.getContextForLanguage(BogoApplication.getInstance());
+        return context.getString(resId);
+    }
+
+    public static String getUidLinkText(long uid,String name){
+        return String.format("<a href=\"uid://%s\">%s<a>",uid, name);
+    }
+
+    public static String getRobotLinkText(long id,String name){
+        return String.format("<a href=\"robot://%s\">%s<a>",id, name);
+    }
+
+    public static void hideKeyboard(EditText editText){
+        int type = WindowInsetsCompat.Type.ime();
+        WindowInsetsControllerCompat controller = ViewCompat.getWindowInsetsController(editText);
+        if (controller != null) {
+            controller.hide(type);
+        }
+    }
+
+    public static void showKeyboard(EditText editText){
+        int type = WindowInsetsCompat.Type.ime();
+        WindowInsetsControllerCompat controller = ViewCompat.getWindowInsetsController(editText);
+        if (controller != null) {
+            controller.show(type);
+            editText.requestFocus();
+        }
+    }
+
+    public static String getLanguage(){
+
+        String language = AppCompatDelegate.getApplicationLocales().toLanguageTags();
+        if (!TextUtils.isEmpty(language)){
+            return language;
+        }
+
+        Configuration configuration = BogoApplication.getInstance().getResources().getConfiguration();
+
+        return configuration.getLocales().get(0).toLanguageTag();
+    }
+
+    public static String getFirstChar(NameSort nameSort){
+        String firstChar = nameSort.getFirstChar();
+        if (firstChar == null){
+            return CharacterParser.getFirstPinYinChar(nameSort.getName());
+        }
+
+        return firstChar;
+    }
+
+    public static void showProgressDialog(Context context,@StringRes int id){
+        if (context instanceof BaseActivity){
+            ((BaseActivity<?>) context).showProgressDialog(id);
+        }
+    }
+
+    public static void hideProgressDialog(Context context){
+        if (context instanceof BaseActivity){
+            ((BaseActivity<?>) context).hideProgressDialog();
+        }
+    }
+
+    public static void switchAudioSpeaker(Context context,boolean on){
+        AudioManager audioManager = ContextCompat.getSystemService(context, AudioManager.class);
+        if (audioManager != null){
+            audioManager.setSpeakerphoneOn(on);
+        }
+    }
+
+
+    public static String getString(EditText editText){
+
+        if (editText == null || editText.getText() == null){
+            return null;
+        }
+        return editText.getText().toString().trim();
+    }
+
+    public static void cancelNotification(Context context,Number id){
+        NotificationManagerCompat.from(context).cancel(id.intValue());
+    }
+}

+ 25 - 0
app/src/main/java/com/bogo/android/common/BindingCompat.java

@@ -0,0 +1,25 @@
+package com.bogo.android.common;
+
+import android.view.View;
+
+public class BindingCompat<Binding> {
+    private final Binding binding;
+    private final View rootView;
+
+    public BindingCompat(Binding binding, View rootView) {
+        this.binding = binding;
+        this.rootView = rootView;
+    }
+
+    public Binding getBinding() {
+        return binding;
+    }
+
+    public View getRootView() {
+        return rootView;
+    }
+
+    public  static <Binding> BindingCompat<Binding> of(Binding binding,View rootView){
+        return new BindingCompat<>(binding,rootView);
+    }
+}

+ 216 - 0
app/src/main/java/com/bogo/android/common/Global.java

@@ -0,0 +1,216 @@
+
+package com.bogo.android.common;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.telephony.TelephonyManager;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.common.database.UserDatabase;
+import com.bogo.android.common.entity.User;
+import com.bogo.android.friend.database.FriendDatabase;
+import com.tencent.bugly.crashreport.CrashReport;
+
+import java.io.File;
+
+public class Global {
+
+    private static final String MODEL_KEY = "CURRENT_USER";
+
+    private static final String KEY_PHOTO_GRAPH_FILE_PATH = "KEY_PHOTO_GRAPH_FILE_PATH";
+
+    private static final String KEY_PRIMARY_DATA_LOAD_FINISHED= "KEY_PRIMARY_DATA_LOAD_FINISHED_%1$s";
+
+    private static final String KEY_SECOND_DATA_LOAD_FINISHED= "KEY_SECOND_DATA_LOAD_FINISHED_%1$s";
+
+    private static final String KEY_ROOM_CALL_MISS_COUNT= "KEY_ROOM_CALL_MISS_COUNT%1$s";
+
+    static final String KEY_WEBRTC_CALL_STATE = "KEY_WEBRTC_CALL_STATE";
+
+
+    private Global() {
+    }
+
+    public static boolean hasLogin(){
+        return hasAccount() && getAccessToken() != null;
+    }
+
+    public static boolean hasAccount(){
+        return UserDatabase.hasAccount();
+    }
+
+    public static User getCurrentUser() {
+        return UserDatabase.findOne();
+    }
+
+    public static void addAccount(User user,String token,Long timestamp) {
+
+        CrashReport.setUserId(String.valueOf(user.id));
+
+        user.timestamp = timestamp;
+
+        user.token = token;
+
+        UserDatabase.add(user);
+    }
+
+    public static void removeToken() {
+        UserDatabase.removeToken();
+    }
+
+    public static String getAccessToken() {
+       return UserDatabase.getToken();
+    }
+
+    /**
+     * 获取当前账号
+     */
+    public static Long getUid() {
+        return UserDatabase.findUid();
+    }
+
+
+    public static String getMotto() {
+        return UserDatabase.findMotto();
+    }
+
+    /**
+     * 获取当前账号
+     */
+    public static String getTelephone() {
+        return UserDatabase.findTelephone();
+    }
+
+    /**
+     * 获取当前账号名称
+     */
+    public static String getName() {
+        return UserDatabase.findName();
+    }
+
+
+    public static void setName(String text) {
+        UserDatabase.setName(text);
+    }
+
+    public static void setMotto(String text) {
+        UserDatabase.setMotto(text);
+    }
+
+    public static void setEmail(String text) {
+        UserDatabase.setEmail(text);
+    }
+
+    public static void setGender(byte gender) {
+        UserDatabase.setGender(gender);
+    }
+
+    /**
+     * 获取当前账号名称
+     */
+    public static String getName(long uid) {
+        String name = FriendDatabase.findName(uid);
+        if (name != null){
+            return name;
+        }
+        return Global.getName();
+    }
+    /**
+     * 获取当前账号
+     */
+    public static String getDatabase() {
+         return getUid() + ".db";
+    }
+
+
+    /**
+     * 获取拍照的照片文件uri
+     */
+    public static Uri getPhotoGraphFileUri() {
+        String filePath = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE)
+                .getString(KEY_PHOTO_GRAPH_FILE_PATH, null);
+        return Uri.fromFile(new File(filePath));
+    }
+
+
+    /**
+     * 设置拍照的照片文件地址
+     */
+    public static void setPhotoGraphFilePath(String path) {
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        sp.edit().putString(KEY_PHOTO_GRAPH_FILE_PATH, path).apply();
+    }
+
+    public static void setPrimaryDataLoadFinished(long id) {
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        sp.edit().putBoolean(String.format(KEY_PRIMARY_DATA_LOAD_FINISHED,id), true).apply();
+    }
+
+    public static void clearPrimaryDataLoadMark(long id) {
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        sp.edit().putBoolean(String.format(KEY_PRIMARY_DATA_LOAD_FINISHED,id), false).apply();
+    }
+
+    public static boolean isPrimaryDataLoadFinished(long id) {
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        return sp.getBoolean(String.format(KEY_PRIMARY_DATA_LOAD_FINISHED,id), false);
+    }
+
+    public static void setSecondDataLoadFinished(long id) {
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        sp.edit().putBoolean(String.format(KEY_SECOND_DATA_LOAD_FINISHED,id), true).apply();
+    }
+
+    public static void clearSecondDataLoadMark(long id) {
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        sp.edit().putBoolean(String.format(KEY_SECOND_DATA_LOAD_FINISHED,id), false).apply();
+    }
+
+    public static boolean isSecondDataLoadFinished(long id) {
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        return sp.getBoolean(String.format(KEY_SECOND_DATA_LOAD_FINISHED,id), false);
+    }
+
+    public static int getPhoneCallState() {
+        boolean hasPhonePermission = App.hasAllPermissions(BogoApplication.getInstance(), Manifest.permission.READ_PHONE_STATE);
+        if (!hasPhonePermission){
+            return TelephonyManager.CALL_STATE_IDLE;
+        }
+        TelephonyManager telephonyManager = (TelephonyManager) BogoApplication.getInstance().getSystemService(Context.TELEPHONY_SERVICE);
+        return telephonyManager.getCallState();
+    }
+
+
+    public static int getIncomingCallState() {
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        return sp.getInt(KEY_WEBRTC_CALL_STATE, TelephonyManager.CALL_STATE_IDLE);
+    }
+
+    public static void setIncomingCallState(int state) {
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        sp.edit().putInt(KEY_WEBRTC_CALL_STATE,state).apply();
+    }
+
+    public static int getGroupCallMissCount() {
+        return BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE)
+                .getInt(KEY_ROOM_CALL_MISS_COUNT, 0);
+    }
+
+    public static void addGroupCallMissCount() {
+
+        int count = getGroupCallMissCount();
+
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        sp.edit().putInt(KEY_ROOM_CALL_MISS_COUNT,count + 1)
+                .apply();
+    }
+
+    public static void clearGroupCallMissCount() {
+        SharedPreferences sp = BogoApplication.getInstance().getSharedPreferences(MODEL_KEY, Context.MODE_PRIVATE);
+        sp.edit().remove(KEY_ROOM_CALL_MISS_COUNT)
+                .apply();
+    }
+
+}

+ 27 - 0
app/src/main/java/com/bogo/android/common/GlobalGlideModule.java

@@ -0,0 +1,27 @@
+
+package com.bogo.android.common;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.bumptech.glide.GlideBuilder;
+import com.bumptech.glide.annotation.GlideModule;
+import com.bumptech.glide.load.engine.cache.ExternalPreferredCacheDiskCacheFactory;
+import com.bumptech.glide.module.AppGlideModule;
+
+@GlideModule
+public class GlobalGlideModule extends AppGlideModule {
+    /**
+     * 500 MB of cache.
+     */
+    private final static int DEFAULT_DISK_CACHE_SIZE = 500 * 1024 * 1024;
+
+    @Override
+    public void applyOptions(@NonNull Context context, GlideBuilder builder) {
+        builder.setLogLevel(Log.ERROR);
+        builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context, "glide", DEFAULT_DISK_CACHE_SIZE));
+    }
+
+}

+ 135 - 0
app/src/main/java/com/bogo/android/common/GlobalVideoCache.java

@@ -0,0 +1,135 @@
+
+package com.bogo.android.common;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.database.StandaloneDatabaseProvider;
+import androidx.media3.datasource.cache.Cache;
+import androidx.media3.datasource.cache.CacheSpan;
+import androidx.media3.datasource.cache.ContentMetadata;
+import androidx.media3.datasource.cache.ContentMetadataMutations;
+import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor;
+import androidx.media3.datasource.cache.SimpleCache;
+
+import com.bogo.android.common.util.FileManager;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.util.NavigableSet;
+import java.util.Set;
+
+@UnstableApi
+public class GlobalVideoCache implements Cache {
+
+    private final SimpleCache target;
+
+    public GlobalVideoCache(Context context) {
+        this.target = new SimpleCache(FileManager.getVideoCacheDir(),new LeastRecentlyUsedCacheEvictor(FileUtils.ONE_GB),new StandaloneDatabaseProvider(context));
+    }
+
+    @Override
+    public long getUid() {
+        return target.getUid();
+    }
+
+    @Override
+    public void release() {
+        target.release();
+    }
+
+    @NonNull
+    @Override
+    public NavigableSet<CacheSpan> addListener(@NonNull String key, @NonNull Listener listener) {
+        return target.addListener(key,listener);
+    }
+
+    @Override
+    public void removeListener(@NonNull String key, @NonNull Listener listener) {
+        target.removeListener(key,listener);
+    }
+
+    @NonNull
+    @Override
+    public NavigableSet<CacheSpan> getCachedSpans(@NonNull String key) {
+        return target.getCachedSpans(key);
+    }
+
+    @NonNull
+    @Override
+    public Set<String> getKeys() {
+        return target.getKeys();
+    }
+
+    @Override
+    public long getCacheSpace() {
+        return target.getCacheSpace();
+    }
+
+    @NonNull
+    @Override
+    public CacheSpan startReadWrite(@NonNull String key, long position, long length) throws InterruptedException, CacheException {
+        return target.startReadWrite(key,position,length);
+    }
+
+    @Nullable
+    @Override
+    public CacheSpan startReadWriteNonBlocking(@NonNull String key, long position, long length) throws CacheException {
+        return target.startReadWriteNonBlocking(key,position,length);
+    }
+
+    @NonNull
+    @Override
+    public File startFile(@NonNull String key, long position, long length) throws CacheException {
+        return target.startFile(key,position,length);
+    }
+
+    @Override
+    public void commitFile(@NonNull File file, long length) throws CacheException {
+        target.commitFile(file,length);
+    }
+
+    @Override
+    public void releaseHoleSpan(@NonNull CacheSpan holeSpan) {
+        target.releaseHoleSpan(holeSpan);
+    }
+
+    @Override
+    public void removeResource(@NonNull String key) {
+        target.removeResource(key);
+    }
+
+    @Override
+    public void removeSpan(@NonNull CacheSpan span) {
+        target.removeSpan(span);
+    }
+
+    @Override
+    public boolean isCached(@NonNull String key, long position, long length) {
+        return target.isCached(key,position,length);
+    }
+
+    @Override
+    public long getCachedLength(@NonNull String key, long position, long length) {
+        return target.getCachedLength(key,position,length);
+    }
+
+    @Override
+    public long getCachedBytes(@NonNull String key, long position, long length) {
+        return target.getCachedBytes(key,position,length);
+    }
+
+    @Override
+    public void applyContentMetadataMutations(@NonNull String key, @NonNull ContentMetadataMutations mutations) throws CacheException {
+        target.applyContentMetadataMutations(key,mutations);
+    }
+
+    @NonNull
+    @Override
+    public ContentMetadata getContentMetadata(@NonNull String key) {
+        return target.getContentMetadata(key);
+    }
+}

+ 76 - 0
app/src/main/java/com/bogo/android/common/adapter/AlbumBucketListAdapter.java

@@ -0,0 +1,76 @@
+
+package com.bogo.android.common.adapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.R;
+import com.bogo.android.common.adapter.holder.AlbumBucketViewHolder;
+import com.bogo.android.common.listener.OnItemClickedListener;
+import com.bogo.android.common.model.Bucket;
+import com.bogo.android.common.util.AppTools;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AlbumBucketListAdapter extends RecyclerView.Adapter<AlbumBucketViewHolder> implements View.OnClickListener {
+
+    private ViewGroup parent;
+    private Bucket mTarget;
+    private final List<Bucket> list  =new ArrayList<>();
+    private OnItemClickedListener<Bucket> onItemClickedListener;
+    @NonNull
+    @Override
+    public AlbumBucketViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        this.parent = parent;
+        return new AlbumBucketViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_album_bucket, parent, false));
+    }
+    @Override
+    public void onBindViewHolder(AlbumBucketViewHolder viewHolder, int position) {
+        Bucket bucket = list.get(position);
+        viewHolder.itemView.setOnClickListener(this);
+        viewHolder.itemView.setTag(bucket);
+        if (bucket.cover != null){
+            viewHolder.imageView.load(bucket.cover,R.drawable.media_background);
+        }
+        viewHolder.name.setText(bucket.name);
+        viewHolder.count.setText(AppTools.getString(R.string.label_album_count,bucket.size));
+        viewHolder.mark.setVisibility(bucket.equals(mTarget) ? View.VISIBLE:View.GONE);
+    }
+
+    public void setOnItemClickedListener(OnItemClickedListener<Bucket> onItemClickedListener) {
+        this.onItemClickedListener = onItemClickedListener;
+    }
+
+    @Override
+    public int getItemCount() {
+        return list.size();
+    }
+
+
+    public void addAll(List<Bucket> list) {
+        this.list.clear();
+        this.list.addAll(list);
+        if (!list.isEmpty() && mTarget == null){
+            mTarget = list.get(0);
+        }
+        super.notifyDataSetChanged();
+    }
+    @Override
+    public void onClick(View v) {
+
+        ViewGroup nextViewGroup = parent.findViewWithTag(mTarget);
+        if (nextViewGroup != null){
+            nextViewGroup.findViewById(R.id.mark).setVisibility(View.GONE);
+        }
+
+        v.findViewById(R.id.mark).setVisibility(View.VISIBLE);
+
+        mTarget = (Bucket) v.getTag();
+        onItemClickedListener.onItemClicked(mTarget,v);
+    }
+}

+ 186 - 0
app/src/main/java/com/bogo/android/common/adapter/AlbumMediaGridAdapter.java

@@ -0,0 +1,186 @@
+
+package com.bogo.android.common.adapter;
+
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.R;
+import com.bogo.android.common.adapter.holder.AlbumItemViewHolder;
+import com.bogo.android.common.constant.IntentAction;
+import com.bogo.android.common.listener.OnItemCheckedListener;
+import com.bogo.android.common.listener.OnItemClickedListener;
+import com.bogo.android.common.model.AlbumMedia;
+import com.bogo.android.common.model.CloudImage;
+import com.bogo.android.common.model.CloudVideo;
+import com.bogo.android.common.util.AppTools;
+
+import org.apache.commons.io.FileUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class AlbumMediaGridAdapter extends RecyclerView.Adapter<AlbumItemViewHolder> implements OnCheckedChangeListener, View.OnClickListener {
+    private final List<AlbumMedia> list = new ArrayList<>();
+    private final ArrayList<AlbumMedia> selectedList = new ArrayList<>();
+    private final String  action;
+    private final int maxCount;
+    private final int itemHeight;
+    private OnItemCheckedListener onItemCheckedListener;
+
+    private OnItemClickedListener<AlbumMedia> onItemClickedListener;
+
+    public AlbumMediaGridAdapter(String  action, int count) {
+        super();
+        this.action = action;
+        this.maxCount = count;
+        DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
+        int width = displayMetrics.widthPixels;
+        itemHeight = ((width - AppTools.dip2px(9)) / 4);
+    }
+
+    public void setOnItemClickedListener(OnItemClickedListener<AlbumMedia> onItemClickedListener) {
+        this.onItemClickedListener = onItemClickedListener;
+    }
+
+    public void setOnItemCheckedListener(OnItemCheckedListener onItemCheckedListener) {
+        this.onItemCheckedListener = onItemCheckedListener;
+    }
+
+    @NonNull
+    @Override
+    public AlbumItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View itemView  = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_album_media, parent, false);
+        itemView.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, itemHeight));
+        return new AlbumItemViewHolder(itemView);
+    }
+
+    @Override
+    public void onBindViewHolder(AlbumItemViewHolder holder, int position) {
+        AlbumMedia item = list.get(position);
+        holder.itemView.setTag(item);
+        holder.itemView.setTag(R.id.holder,holder);
+        holder.checkBox.setOnCheckedChangeListener(null);
+        holder.checkBox.setChecked(selectedList.contains(item));
+        holder.checkBox.setTag(item);
+        holder.checkBox.setVisibility(View.VISIBLE);
+        holder.checkBox.setOnCheckedChangeListener(this);
+        holder.checkBox.setTag(R.id.holder,holder);
+        holder.itemView.setOnClickListener(this);
+
+
+        holder.maskView.setVisibility(holder.checkBox.isChecked() ? View.VISIBLE : View.GONE);
+
+        boolean isCheckable = (Objects.equals(action, IntentAction.ACTION_MULTIPLE_PHOTO_SELECTOR) && !item.isVideo()) || Objects.equals(action, IntentAction.ACTION_MULTIPLE_MEDIA_SELECTOR);
+
+        holder.checkBox.setVisibility(isCheckable ? View.VISIBLE : View.GONE);
+
+        if (item.isVideo()){
+            holder.duration.setVisibility(View.VISIBLE);
+            holder.size.setVisibility(View.VISIBLE);
+            holder.duration.setText(AppTools.toString(item.duration));
+            holder.duration.setText(holder.itemView.getContext().getString(R.string.label_video_extra_format,item.duration / 1000));
+            holder.size.setText(FileUtils.byteCountToDisplaySize(item.size));
+        }else {
+            holder.duration.setVisibility(View.GONE);
+            holder.size.setVisibility(View.GONE);
+
+        }
+
+        holder.imageView.load(item.uri,R.drawable.media_background);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return 0;
+    }
+
+    @Override
+    public int getItemCount() {
+        return list.size();
+    }
+
+    @Override
+    public void onCheckedChanged(CompoundButton compoundbutton, boolean isChecked) {
+
+        if (isChecked) {
+            selectedList.add((AlbumMedia) compoundbutton.getTag());
+        }
+        if (!isChecked || selectedList.size() > maxCount) {
+            compoundbutton.setOnCheckedChangeListener(null);
+            compoundbutton.setChecked(false);
+            selectedList.remove(compoundbutton.getTag());
+            compoundbutton.setOnCheckedChangeListener(this);
+        }
+
+
+        AlbumItemViewHolder holder = (AlbumItemViewHolder) compoundbutton.getTag(R.id.holder);
+
+        holder.maskView.setVisibility(compoundbutton.isChecked() ? View.VISIBLE : View.GONE);
+
+        onItemCheckedListener.onItemChecked(compoundbutton.getTag(), compoundbutton, isChecked);
+    }
+
+    public int getSelectedSize() {
+        return selectedList.size();
+    }
+
+    public ArrayList<AlbumMedia> getSelectedMedias() {
+        return selectedList;
+    }
+
+
+    public void add(AlbumMedia album) {
+        list.add(album);
+    }
+
+    @Override
+    public void onClick(View view) {
+
+        AlbumMedia item  = (AlbumMedia) view.getTag();
+        AlbumItemViewHolder holder = (AlbumItemViewHolder) view.getTag(R.id.holder);
+
+        /*
+         * 图片可被多选时,可以点击预览
+         */
+        if (item.isImage() && isMultipleMode()){
+            CloudImage image = new CloudImage();
+            image.uri = item.uri;
+            BogoApplication.getInstance().startPhotoActivity(view.getContext(),image, holder.imageView);
+            return;
+        }
+
+        /*
+         * 视频可被多选时,可以点击预览
+         */
+        if (item.isVideo() && Objects.equals(action, IntentAction.ACTION_MULTIPLE_MEDIA_SELECTOR)){
+            CloudVideo video = new CloudVideo();
+            video.uri = item.uri;
+            BogoApplication.getInstance().startVideoActivity(view.getContext(),video,holder.imageView);
+            return;
+        }
+        onItemClickedListener.onItemClicked(item, view);
+    }
+
+
+    private boolean isMultipleMode(){
+        return Objects.equals(action, IntentAction.ACTION_MULTIPLE_PHOTO_SELECTOR) || Objects.equals(action, IntentAction.ACTION_MULTIPLE_MEDIA_SELECTOR);
+    }
+
+    public void clear() {
+        list.clear();
+    }
+
+    public void addAll(List<AlbumMedia> list) {
+        this.list.addAll(list);
+    }
+}

+ 56 - 0
app/src/main/java/com/bogo/android/common/adapter/CountrySelectorAdapter.java

@@ -0,0 +1,56 @@
+
+package com.bogo.android.common.adapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.R;
+import com.bogo.android.account.loader.CountryLoader;
+import com.bogo.android.common.adapter.holder.CountryViewHolder;
+import com.bogo.android.common.listener.OnItemClickedListener;
+import com.bogo.android.common.model.Country;
+
+import java.util.List;
+
+public class CountrySelectorAdapter extends RecyclerView.Adapter<CountryViewHolder> implements View.OnClickListener {
+    private final List<Country> countries;
+    private OnItemClickedListener<Country> onItemClickedListener;
+
+    public CountrySelectorAdapter() {
+        countries = CountryLoader.getLoader().getCountries();
+    }
+
+    @NonNull
+    @Override
+    public CountryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        return new CountryViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_county_selector, parent, false));
+    }
+
+    @Override
+    public void onBindViewHolder(CountryViewHolder viewHolder, int position) {
+        Country country = countries.get(position);
+        viewHolder.name.setText(country.name);
+        viewHolder.icon.setImageResource(country.icon);
+        viewHolder.code.setText(country.code);
+        viewHolder.itemView.setTag(country);
+        viewHolder.itemView.setOnClickListener(this);
+    }
+
+    public void setOnItemClickedListener(OnItemClickedListener<Country> onItemClickedListener) {
+        this.onItemClickedListener = onItemClickedListener;
+    }
+
+    @Override
+    public int getItemCount() {
+        return countries.size();
+    }
+
+    @Override
+    public void onClick(View v) {
+        onItemClickedListener.onItemClicked((Country) v.getTag(), v);
+    }
+}

+ 124 - 0
app/src/main/java/com/bogo/android/common/adapter/FileSelectorViewAdapter.java

@@ -0,0 +1,124 @@
+
+package com.bogo.android.common.adapter;
+
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.R;
+import com.bogo.android.common.adapter.holder.FileItemViewHolder;
+import com.bogo.android.common.listener.OnItemClickedListener;
+import com.bogo.android.common.model.SharedFile;
+import com.bogo.android.common.util.AppTools;
+import com.bogo.android.common.util.FileManager;
+import com.bogo.android.common.util.FileViewUtils;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+public class FileSelectorViewAdapter extends RecyclerView.Adapter<FileItemViewHolder> implements View.OnClickListener, Comparator<SharedFile>, Serializable {
+
+    private OnItemClickedListener<SharedFile> onItemCheckedListener;
+    private final List<SharedFile> fileList = new LinkedList<>();
+    private SharedFile target;
+    private ViewGroup parent;
+
+    public FileSelectorViewAdapter() {
+        load();
+    }
+
+    public void setOnItemCheckedListener(OnItemClickedListener<SharedFile> onItemCheckedListener) {
+        this.onItemCheckedListener = onItemCheckedListener;
+    }
+
+    @NonNull
+    @Override
+    public FileItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        this.parent = parent;
+        return new FileItemViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_file_selector, parent, false));
+    }
+
+    @Override
+    public void onBindViewHolder(FileItemViewHolder holder, int position) {
+        SharedFile file = fileList.get(position);
+        holder.name.setText(file.name);
+
+        String size = org.apache.commons.io.FileUtils.byteCountToDisplaySize(file.size);
+        String time = AppTools.getDateTimeString(file.lastModified);
+        holder.description.setText(AppTools.getString(R.string.label_file_desc_format, size, time));
+        holder.radioButton.setVisibility(View.VISIBLE);
+        holder.radioButton.setChecked(Objects.equals(file, target));
+        holder.icon.setImageResource(FileViewUtils.getFileIcon(file.name));
+
+        holder.itemView.setTag(R.id.holder, holder);
+        holder.itemView.setTag(file);
+        holder.itemView.setOnClickListener(this);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return 0;
+    }
+
+    @Override
+    public int getItemCount() {
+        return fileList.size();
+    }
+
+    @Override
+    public void onClick(View v) {
+        SharedFile file = (SharedFile) v.getTag();
+        FileItemViewHolder holder = (FileItemViewHolder) v.getTag(R.id.holder);
+
+        View view = parent.findViewWithTag(target);
+        if (view != null) {
+            FileItemViewHolder preHolder = (FileItemViewHolder) view.getTag(R.id.holder);
+            preHolder.radioButton.setChecked(false);
+        }
+
+        holder.radioButton.setChecked(true);
+        target = file;
+        onItemCheckedListener.onItemClicked(file, v);
+    }
+
+    public void set(SharedFile file) {
+        fileList.clear();
+        fileList.add(file);
+        target = file;
+        super.notifyDataSetChanged();
+    }
+
+    private void load() {
+        for (File file : FileManager.ofDocument().listFiles()) {
+            SharedFile sharedFile = new SharedFile();
+            sharedFile.name = file.getName();
+            sharedFile.size = file.length();
+            sharedFile.lastModified = file.lastModified();
+            sharedFile.uri = Uri.fromFile(file);
+            fileList.add(sharedFile);
+        }
+        fileList.sort(this);
+    }
+
+    public void reload() {
+        fileList.clear();
+        target = null;
+        load();
+        super.notifyDataSetChanged();
+    }
+
+    @Override
+    public int compare(SharedFile f1, SharedFile f2) {
+        return Long.compare(f2.lastModified,f1.lastModified);
+    }
+
+}

+ 82 - 0
app/src/main/java/com/bogo/android/common/adapter/GalleryPhotoViewAdapter.java

@@ -0,0 +1,82 @@
+
+package com.bogo.android.common.adapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.viewpager.widget.PagerAdapter;
+
+import com.bogo.android.R;
+import com.bogo.android.common.listener.CloudImageApplyListener;
+import com.bogo.android.common.model.CloudImage;
+import com.bogo.android.common.widget.ProgressbarPhotoView;
+import com.github.chrisbanes.photoview.OnPhotoTapListener;
+
+import java.util.List;
+
+
+public class GalleryPhotoViewAdapter extends PagerAdapter {
+
+    private final List<CloudImage> list;
+    private CloudImageApplyListener cloudImageApplyListener;
+    private OnPhotoTapListener onPhotoTapListener;
+    private View.OnLongClickListener onLongClickListener;
+    public GalleryPhotoViewAdapter(List<CloudImage> list) {
+        this.list = list;
+    }
+
+    public void setCloudImageApplyListener(CloudImageApplyListener cloudImageApplyListener) {
+        this.cloudImageApplyListener = cloudImageApplyListener;
+    }
+
+
+    public void setOnLongClickListener(View.OnLongClickListener onLongClickListener) {
+        this.onLongClickListener = onLongClickListener;
+    }
+
+    public void setOnPhotoTapListener(OnPhotoTapListener onPhotoTapListener) {
+        this.onPhotoTapListener = onPhotoTapListener;
+    }
+
+    @Override
+    public int getCount() {
+        return list.size();
+    }
+
+    @NonNull
+    @Override
+    public View instantiateItem(ViewGroup container, int position) {
+
+        ProgressbarPhotoView photoView = (ProgressbarPhotoView) LayoutInflater.from(container.getContext()).inflate(R.layout.layout_progressbar_photoview, null);
+
+        CloudImage image = list.get(position);
+        photoView.setTag(position);
+        container.addView(photoView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        photoView.display(image,cloudImageApplyListener);
+        photoView.setOnPhotoTapListener(onPhotoTapListener);
+        photoView.setOnLongClickListener(onLongClickListener);
+        return photoView;
+    }
+
+    @Override
+    public void destroyItem(ViewGroup container, int position, @NonNull Object object) {
+        container.removeView((View) object);
+    }
+
+    @Override
+    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
+        return view == object;
+    }
+
+    public void addAll(List<CloudImage> list) {
+        this.list.clear();
+        this.list.addAll(list);
+        super.notifyDataSetChanged();
+    }
+
+    public CloudImage getItem(int position) {
+       return  list.get(position);
+    }
+}

+ 48 - 0
app/src/main/java/com/bogo/android/common/adapter/LoadMoreFooterAdapter.java

@@ -0,0 +1,48 @@
+
+package com.bogo.android.common.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.common.adapter.holder.FooterViewHolder;
+import com.bogo.android.common.widget.ListFooterView;
+import com.bogo.android.databinding.LayoutListFooterBinding;
+
+public class LoadMoreFooterAdapter extends RecyclerView.Adapter<FooterViewHolder>{
+
+    private final ListFooterView footerView;
+
+    public LoadMoreFooterAdapter(Context context) {
+        this.footerView = LayoutListFooterBinding.inflate(LayoutInflater.from(context)).getRoot();
+    }
+
+
+    @NonNull
+    @Override
+    public FooterViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        return new FooterViewHolder(footerView);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull FooterViewHolder holder, int position) {
+    }
+
+    public ListFooterView getFooterView() {
+        return footerView;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return 0;
+    }
+
+    @Override
+    public int getItemCount() {
+        return 1;
+    }
+
+}

+ 117 - 0
app/src/main/java/com/bogo/android/common/adapter/MapAddressListAdapter.java

@@ -0,0 +1,117 @@
+
+package com.bogo.android.common.adapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.baidu.mapapi.search.core.PoiInfo;
+import com.bogo.android.R;
+import com.bogo.android.common.adapter.holder.MapAddressViewHolder;
+import com.bogo.android.common.listener.OnItemClickedListener;
+import com.bogo.android.common.util.AppTools;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+public class MapAddressListAdapter extends RecyclerView.Adapter<MapAddressViewHolder> implements View.OnClickListener, Comparator<PoiInfo> {
+    private final List<PoiInfo> list = new ArrayList<>();
+    private OnItemClickedListener<PoiInfo> mOnItemClickedListener;
+    private View currMaker;
+    private PoiInfo currPoiInfo;
+    private String keyword;
+
+    public void setOnItemClickedListener(OnItemClickedListener<PoiInfo> onItemClickedListener) {
+        this.mOnItemClickedListener = onItemClickedListener;
+    }
+
+    @NonNull
+    @Override
+    public MapAddressViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        return new MapAddressViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_map_address, parent, false));
+    }
+
+    @Override
+    public void onBindViewHolder(MapAddressViewHolder holder, int position) {
+        PoiInfo target = list.get(position);
+        holder.address.setText(holder.itemView.getContext().getString(R.string.label_map_address_format,(target.getDistance() / 1000F),target.getAddress()));
+        if (keyword == null || !target.name.contains(keyword)){
+            holder.name.setText(target.getName());
+        }else {
+            holder.name.setText(AppTools.highlight(target.getName(),keyword));
+        }
+        holder.itemView.setTag(target);
+        holder.itemView.setId(position);
+        holder.itemView.setOnClickListener(this);
+        holder.marker.setVisibility(View.GONE);
+
+        if (currPoiInfo == target){
+            holder.marker.setVisibility(View.VISIBLE);
+            return;
+        }
+
+        if (position == 0 && currMaker == null){
+            holder.marker.setVisibility(View.VISIBLE);
+            currMaker = holder.marker;
+            mOnItemClickedListener.onItemClicked(target,null);
+        }
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return 0;
+    }
+
+    @Override
+    public int getItemCount() {
+        return list.size();
+    }
+
+    public void addAll(List<PoiInfo> list,String keyword) {
+        this.list.clear();
+        this.list.addAll(list);
+        this.keyword = keyword;
+        currMaker = null;
+        this.list.sort(this);
+        super.notifyDataSetChanged();
+    }
+
+    public boolean isEmpty() {
+        return list.isEmpty();
+    }
+
+    @Override
+    public void onClick(View view) {
+
+        PoiInfo poiInfo = (PoiInfo) view.getTag();
+
+        if (poiInfo == currPoiInfo){
+            return;
+        }
+
+        View marker = view.findViewById(R.id.marker);
+
+        marker.setVisibility(View.VISIBLE);
+
+        mOnItemClickedListener.onItemClicked(poiInfo, view);
+
+        if (currMaker != null){
+            currMaker.setVisibility(View.GONE);
+        }
+
+        currMaker = marker;
+
+        currPoiInfo = poiInfo;
+    }
+
+    @Override
+    public int compare(PoiInfo o1, PoiInfo o2) {
+        double distance0 = o1.distance;
+        double distance1 = o2.distance;
+        return Double.compare(distance0,distance1);
+    }
+}

+ 26 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/AlbumBucketViewHolder.java

@@ -0,0 +1,26 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.R;
+import com.bogo.android.common.widget.WebImageView;
+
+public class AlbumBucketViewHolder extends RecyclerView.ViewHolder {
+    public final WebImageView imageView;
+    public final RadioButton mark;
+    public final TextView name;
+    public final TextView count;
+
+    public AlbumBucketViewHolder(View itemView) {
+        super(itemView);
+        imageView = itemView.findViewById(R.id.image);
+        mark = itemView.findViewById(R.id.mark);
+        name = itemView.findViewById(R.id.name);
+        count = itemView.findViewById(R.id.count);
+    }
+}

+ 29 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/AlbumItemViewHolder.java

@@ -0,0 +1,29 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.R;
+import com.bogo.android.common.widget.WebImageView;
+
+public class AlbumItemViewHolder extends RecyclerView.ViewHolder {
+    public final WebImageView imageView;
+    public final CheckBox checkBox;
+    public final TextView duration;
+    public final TextView size;
+
+    public final View maskView;
+
+    public AlbumItemViewHolder(View itemView) {
+        super(itemView);
+        imageView = itemView.findViewById(R.id.image);
+        checkBox = itemView.findViewById(R.id.checkBox);
+        maskView = itemView.findViewById(R.id.mask);
+        duration = itemView.findViewById(R.id.duration);
+        size = itemView.findViewById(R.id.size);
+    }
+}

+ 17 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/ChipViewHolder.java

@@ -0,0 +1,17 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.chip.Chip;
+
+public class ChipViewHolder extends RecyclerView.ViewHolder {
+    public final Chip chip;
+
+    public ChipViewHolder(View itemView) {
+        super(itemView);
+        chip = (Chip) itemView;
+    }
+}

+ 23 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/CountryViewHolder.java

@@ -0,0 +1,23 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.R;
+
+public class CountryViewHolder extends RecyclerView.ViewHolder {
+    public final TextView name;
+    public final ImageView icon;
+    public final TextView code;
+
+    public CountryViewHolder(View itemView) {
+        super(itemView);
+        icon = itemView.findViewById(R.id.icon);
+        name = itemView.findViewById(R.id.name);
+        code = itemView.findViewById(R.id.code);
+    }
+}

+ 27 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/FileItemViewHolder.java

@@ -0,0 +1,27 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.R;
+
+public class FileItemViewHolder extends RecyclerView.ViewHolder {
+    public final ImageView icon;
+    public final RadioButton radioButton;
+    public final TextView name;
+    public final TextView description;
+
+    public FileItemViewHolder(View itemView) {
+        super(itemView);
+        icon = itemView.findViewById(R.id.icon);
+        radioButton = itemView.findViewById(R.id.radio);
+        name = itemView.findViewById(R.id.name);
+        description = itemView.findViewById(R.id.description);
+
+    }
+}

+ 12 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/FooterViewHolder.java

@@ -0,0 +1,12 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+public class FooterViewHolder extends RecyclerView.ViewHolder {
+    public FooterViewHolder(View itemView) {
+        super(itemView);
+    }
+}

+ 12 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/HeaderViewHolder.java

@@ -0,0 +1,12 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+public class HeaderViewHolder extends RecyclerView.ViewHolder {
+    public HeaderViewHolder(View itemView) {
+        super(itemView);
+    }
+}

+ 17 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/ImageViewHolder.java

@@ -0,0 +1,17 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.common.widget.WebImageView;
+
+public class ImageViewHolder extends RecyclerView.ViewHolder {
+    public final WebImageView imageView;
+
+    public ImageViewHolder(View itemView) {
+        super(itemView);
+        imageView = (WebImageView) itemView;
+    }
+}

+ 25 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/LogoNameViewHolder.java

@@ -0,0 +1,25 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.R;
+import com.bogo.android.common.widget.WebImageView;
+
+public class LogoNameViewHolder extends RecyclerView.ViewHolder {
+    public final TextView name;
+    public final WebImageView icon;
+    public final View badge;
+    public final TextView alias;
+
+    public LogoNameViewHolder(View itemView) {
+        super(itemView);
+        name = itemView.findViewById(R.id.name);
+        icon = itemView.findViewById(R.id.icon);
+        badge = itemView.findViewById(R.id.badge);
+        alias = itemView.findViewById(R.id.alias);
+    }
+}

+ 23 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/MapAddressViewHolder.java

@@ -0,0 +1,23 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bogo.android.R;
+
+
+public class MapAddressViewHolder extends RecyclerView.ViewHolder {
+    public final TextView name;
+    public final TextView address;
+    public final View marker;
+
+    public MapAddressViewHolder(View itemView) {
+        super(itemView);
+        name = itemView.findViewById(R.id.name);
+        address = itemView.findViewById(R.id.address);
+        marker = itemView.findViewById(R.id.marker);
+    }
+}

+ 16 - 0
app/src/main/java/com/bogo/android/common/adapter/holder/TextViewHolder.java

@@ -0,0 +1,16 @@
+
+package com.bogo.android.common.adapter.holder;
+
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+public class TextViewHolder extends RecyclerView.ViewHolder {
+    public final TextView textView;
+
+    public TextViewHolder(View itemView) {
+        super(itemView);
+        textView = (TextView) itemView;
+    }
+}

+ 16 - 0
app/src/main/java/com/bogo/android/common/api/AppVersionServiceManager.java

@@ -0,0 +1,16 @@
+
+package com.bogo.android.common.api;
+
+import com.bogo.android.common.api.service.ConfigService;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.home.model.AppVersion;
+
+public class AppVersionServiceManager extends BaseServiceManager {
+
+    private static final ConfigService configService = createService(ConfigService.class);
+
+    public static void checkVersion(HttpResponseListener<AppVersion> responseListener) {
+        configService.map("android").enqueue(new MainThreadCallback<>(responseListener));
+    }
+
+}

+ 152 - 0
app/src/main/java/com/bogo/android/common/api/BaseServiceManager.java

@@ -0,0 +1,152 @@
+
+package com.bogo.android.common.api;
+
+
+import androidx.annotation.NonNull;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.BuildConfig;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.constant.ResponseCode;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.util.AppTools;
+import com.bogo.android.common.util.ThreadHandler;
+import com.bogo.messaging.PushManager;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.Expose;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class BaseServiceManager {
+
+
+    public static <T> T createService(Class<T> tClass) {
+
+        GsonBuilder builder = new GsonBuilder()
+                .addSerializationExclusionStrategy(new ExclusionStrategy() {
+
+            @Override
+            public boolean shouldSkipField(FieldAttributes fieldAttributes) {
+                Expose expose = fieldAttributes.getAnnotation(Expose.class);
+                return expose != null && !expose.serialize();
+            }
+
+            @Override
+            public boolean shouldSkipClass(Class<?> clazz) {
+                return false;
+            }
+        });
+
+        Retrofit retrofit = new Retrofit.Builder()
+                .baseUrl(BuildConfig.API_SERVER_URL + "/")
+                .client(OkHttpFactory.getOkHttpClient())
+                .addConverterFactory(GsonConverterFactory.create(builder.create()))
+                .build();
+
+        return retrofit.create(tClass);
+
+    }
+
+    public static final Callback<ApiResponse<Void>> VOID_CALLBACK = new Callback<ApiResponse<Void>>() {
+
+        @Override
+        public void onResponse(@NonNull Call<ApiResponse<Void>> call, Response<ApiResponse<Void>> response) {
+            if (ResponseCode.CODE_401 == response.code()) {
+                Global.removeToken();
+                PushManager.stop(BogoApplication.getInstance());
+                BogoApplication.getInstance().restartSelf();
+                return;
+            }
+
+            if (response.isSuccessful()) {
+                return;
+            }
+
+            try {
+                if (response.errorBody() != null){
+                    ApiResponse<?> errorResponse = AppTools.parseData(response.errorBody().string(),ApiResponse.class);
+                    if (errorResponse.message != null){
+                        AppTools.showToastView(BogoApplication.getInstance(),errorResponse.message);
+                    }
+                }
+            }catch (Exception ignored){}
+        }
+
+        @Override
+        public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+        }
+    };
+
+ 
+    public static class MainThreadCallback<T> implements Callback<ApiResponse<T>> {
+
+        protected HttpResponseListener<T> responseListener;
+
+        public MainThreadCallback(HttpResponseListener<T> responseListener) {
+            this.responseListener = responseListener;
+        }
+
+        @Override
+        public void onResponse(@NonNull Call<ApiResponse<T>> call, retrofit2.Response<ApiResponse<T>> response) {
+            if (ResponseCode.CODE_401 == response.code()) {
+                Global.removeToken();
+                PushManager.stop(BogoApplication.getInstance());
+                BogoApplication.getInstance().restartSelf();
+                return;
+            }
+
+            if (response.isSuccessful()) {
+                onHttpResponse(response.body());
+                return;
+            }
+
+            try {
+                if (response.errorBody() != null){
+                    ApiResponse<?> errorResponse = AppTools.parseData(response.errorBody().string(),ApiResponse.class);
+                    if (errorResponse.message != null){
+                        AppTools.showToastView(BogoApplication.getInstance(),errorResponse.message);
+                    }
+                }
+            }catch (Exception ignored){}
+        }
+
+        @Override
+        public void onFailure(@NonNull Call<ApiResponse<T>> call, @NonNull Throwable t) {
+            onHttpException(t);
+        }
+
+        public void onHttpResponse(ApiResponse<T> body) {
+            responseListener.onHttpResponse(body);
+        }
+
+        public void onHttpException(Throwable throwable) {
+            responseListener.onHttpException((Exception) throwable);
+        }
+    }
+
+
+    public static class WorkerThreadCallback<T> extends MainThreadCallback<T> {
+
+        public WorkerThreadCallback(HttpResponseListener<T> responseListener) {
+            super(responseListener);
+        }
+
+        @Override
+        public void onHttpResponse(ApiResponse<T> body) {
+            ThreadHandler.post(() -> responseListener.onHttpResponse(body));
+        }
+
+        public void onHttpException(Throwable throwable) {
+            ThreadHandler.post(() -> responseListener.onHttpException((Exception) throwable));
+        }
+    }
+
+
+}

+ 17 - 0
app/src/main/java/com/bogo/android/common/api/FileServiceManager.java

@@ -0,0 +1,17 @@
+
+package com.bogo.android.common.api;
+
+import com.bogo.android.common.api.service.FileService;
+import com.bogo.android.common.constant.FileBucket;
+import com.bogo.android.common.listener.HttpResponseListener;
+
+public class FileServiceManager extends BaseServiceManager {
+
+    private static final FileService fileService = createService(FileService.class);
+
+
+    public static void delete(FileBucket bucket, String key, HttpResponseListener<Void> responseListener) {
+        fileService.delete(bucket.getName(),key).enqueue(new MainThreadCallback<>(responseListener));
+    }
+
+}

+ 148 - 0
app/src/main/java/com/bogo/android/common/api/OkHttpFactory.java

@@ -0,0 +1,148 @@
+
+package com.bogo.android.common.api;
+
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.bogo.android.BuildConfig;
+import com.bogo.android.common.App;
+import com.bogo.android.common.Global;
+import com.bogo.android.common.constant.Constant;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.logging.HttpLoggingInterceptor;
+
+
+public class OkHttpFactory {
+
+     static X509TrustManager trustManager = new X509TrustManager() {
+        @Override
+        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
+        }
+
+        @Override
+        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
+        }
+
+        @Override
+        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+            return new java.security.cert.X509Certificate[]{};
+        }
+    };
+
+    public static OkHttpClient getOkHttpClient() {
+        try {
+            SSLContext sslContext = SSLContext.getInstance("SSL");
+            sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
+            return new OkHttpClient.Builder()
+                    .connectTimeout(5, TimeUnit.SECONDS)
+                    .readTimeout(10, TimeUnit.SECONDS)
+                    .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
+                    .addInterceptor(new TokenInterceptor())
+                    .addInterceptor(new AppInterceptor())
+                    .addInterceptor(new LoggingInterceptor())
+                    .hostnameVerifier((hostname, session) -> true).build();
+
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static OkHttpClient getUploadOkHttpClient() {
+
+        try {
+            SSLContext sslContext = SSLContext.getInstance("SSL");
+            sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
+            return new OkHttpClient.Builder()
+                    .connectTimeout(5, TimeUnit.SECONDS)
+                    .readTimeout(60, TimeUnit.SECONDS)
+                    .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
+                    .addInterceptor(new TokenInterceptor())
+                    .addInterceptor(new AppInterceptor())
+                    .addInterceptor(new LoggingInterceptor(HttpLoggingInterceptor.Level.BASIC))
+                    .hostnameVerifier((hostname, session) -> true).build();
+
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static class AppInterceptor implements Interceptor{
+
+        @NonNull
+        @Override
+        public Response intercept(Chain chain) throws IOException {
+            Request request = chain.request().newBuilder()
+                    .addHeader(Constant.ATTR_APP_TYPE,String.valueOf(0))
+                    .addHeader(Constant.ATTR_APP_VERSION,String.valueOf(BuildConfig.VERSION_CODE))
+                    .addHeader(Constant.ATTR_APP_LANGUAGE, App.getLanguage())
+                    .build();
+
+            return chain.proceed(request);
+        }
+    }
+
+    private static class TokenInterceptor implements Interceptor{
+
+        @NonNull
+        @Override
+        public Response intercept(@NonNull Chain chain) throws IOException {
+
+            String token = Global.getAccessToken();
+
+            if (token == null){
+                return chain.proceed(chain.request());
+            }
+
+            Request request = chain.request().newBuilder()
+                    .addHeader(Constant.ATTR_HEADER_TOKEN,token)
+                    .build();
+
+            return chain.proceed(request);
+        }
+    }
+
+    private static class LoggingInterceptor implements Interceptor, HttpLoggingInterceptor.Logger {
+
+        private final HttpLoggingInterceptor interceptor;
+
+        private LoggingInterceptor() {
+            this(HttpLoggingInterceptor.Level.BODY);
+        }
+
+        private LoggingInterceptor(HttpLoggingInterceptor.Level level) {
+            this.interceptor = new HttpLoggingInterceptor(this);
+            interceptor.setLevel(level);
+        }
+
+        @NonNull
+        @Override
+        public Response intercept(@NonNull Chain chain) throws IOException {
+
+            if (BuildConfig.DEBUG){
+                return interceptor.intercept(chain);
+            }
+
+            return chain.proceed(chain.request());
+        }
+
+        @Override
+        public void log(@NonNull String message) {
+            Log.i("OkHttp",message);
+        }
+    }
+
+}

+ 96 - 0
app/src/main/java/com/bogo/android/common/api/UserServiceManager.java

@@ -0,0 +1,96 @@
+
+package com.bogo.android.common.api;
+
+import com.bogo.android.account.api.response.UserDTO;
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.api.service.UserService;
+import com.bogo.android.common.listener.HttpResponseListener;
+import com.bogo.android.common.util.DebugLogger;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import retrofit2.Call;
+import retrofit2.Response;
+
+public class UserServiceManager extends BaseServiceManager {
+
+    private static final UserService userService = createService(UserService.class);
+
+
+    public static List<UserDTO> findList(String[] idList) {
+        List<Long> longList = new LinkedList<>();
+        for (String id : idList){
+            longList.add(Long.parseLong(id));
+        }
+        return findList(longList);
+    }
+
+    public static List<UserDTO> findList(List<Long> idList) {
+        return findList(idList.toArray(new Long[0]));
+    }
+
+    public static List<UserDTO> findList(Long[] idList) {
+        if (idList == null || idList.length == 0){
+            return Collections.emptyList();
+        }
+        try {
+            Response<ApiResponse<List<UserDTO>>> response = userService.list(idList).execute();
+            if (response.isSuccessful() && response.body() != null){
+                return response.body().data;
+            }
+        } catch (Exception e) {
+            DebugLogger.e(UserServiceManager.class.getSimpleName(),"获取用户信息失败",e);
+        }
+
+        return Collections.emptyList();
+    }
+
+    public static void findOne(long id, HttpResponseListener<UserDTO> responseListener) {
+
+        Call<ApiResponse<UserDTO>> call = userService.findOne(id);
+
+        call.enqueue(new MainThreadCallback<>(responseListener));
+
+    }
+
+    public static UserDTO findOne(long id) {
+
+        Call<ApiResponse<UserDTO>> call = userService.findOne(id);
+
+        try {
+            Response<ApiResponse<UserDTO>> response = call.execute();
+            if (response.isSuccessful() && response.body() != null){
+                return response.body().data;
+            }
+        } catch (Exception e) {
+            DebugLogger.e(UserServiceManager.class.getSimpleName(),"获取用户信息失败",e);
+        }
+        return null;
+    }
+
+    public static UserDTO getSelf() {
+        Call<ApiResponse<UserDTO>> call = userService.getSelfInfo();
+        try {
+            Response<ApiResponse<UserDTO>> response = call.execute();
+            if (response.isSuccessful() && response.body() != null){
+                return response.body().data;
+            }
+        } catch (Exception e) {
+            DebugLogger.e(UserServiceManager.class.getSimpleName(),"获取自己信息失败",e);
+        }
+        return null;
+    }
+
+
+
+    public static void findOne(String telephone, HttpResponseListener<UserDTO> responseListener) {
+
+        Call<ApiResponse<UserDTO>> call = userService.findOne(telephone);
+
+        call.enqueue(new MainThreadCallback<>(responseListener));
+
+    }
+
+}

+ 60 - 0
app/src/main/java/com/bogo/android/common/api/response/ApiResponse.java

@@ -0,0 +1,60 @@
+
+package com.bogo.android.common.api.response;
+
+import com.bogo.android.common.constant.ResponseCode;
+import com.bogo.android.common.model.Page;
+
+import java.util.Collection;
+
+public class ApiResponse<T> {
+
+    public int code;
+
+    public String message;
+
+    public long timestamp;
+
+    public String token;
+
+    public T data;
+
+    public Page page;
+
+    public Long minId;
+
+    public Long maxId;
+
+    public boolean isSuccess() {
+        return ResponseCode.CODE_200 == (code);
+    }
+
+    public boolean isEmpty(){
+
+        if (data == null){
+            return true;
+        }
+
+        if (data instanceof Collection){
+            return ((Collection<?>) data).isEmpty();
+        }
+
+        return false;
+    }
+
+
+    public boolean isNotEmpty(){
+         return !isEmpty();
+    }
+
+
+    public static <T> ApiResponse<T> make(int code,T data){
+        ApiResponse<T> response = new ApiResponse<>();
+        response.code = code;
+        response.data = data;
+        return response;
+    }
+
+    public static <T> ApiResponse<T> make(T data){
+        return make(ResponseCode.CODE_200,data);
+    }
+}

+ 37 - 0
app/src/main/java/com/bogo/android/common/api/response/UploadSignDTO.java

@@ -0,0 +1,37 @@
+
+package com.bogo.android.common.api.response;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class UploadSignDTO {
+    private String host;
+
+    private String method;
+
+    private Map<String,String> parameters;
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public Map<String, String> getParameters() {
+        return parameters == null ? Collections.emptyMap() : parameters;
+    }
+
+    public void setParameters(Map<String, String> parameters) {
+        this.parameters = parameters;
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+}

+ 15 - 0
app/src/main/java/com/bogo/android/common/api/service/ConfigService.java

@@ -0,0 +1,15 @@
+package com.bogo.android.common.api.service;
+
+
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.home.model.AppVersion;
+
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Path;
+
+public interface ConfigService {
+
+    @GET("config/map/{domain}")
+    Call<ApiResponse<AppVersion>> map(@Path("domain") String domain);
+}

+ 31 - 0
app/src/main/java/com/bogo/android/common/api/service/FileService.java

@@ -0,0 +1,31 @@
+package com.bogo.android.common.api.service;
+
+
+import com.bogo.android.common.api.response.ApiResponse;
+import com.bogo.android.common.api.response.UploadSignDTO;
+
+import okhttp3.RequestBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.PUT;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+import retrofit2.http.Url;
+
+public interface FileService {
+
+    @POST
+    Call<ApiResponse<Void>> postUpload(@Url String url, @Body RequestBody body);
+
+    @PUT
+    Call<ApiResponse<Void>> putUpload(@Url String url, @Body RequestBody body);
+
+    @GET("file/upload/sign")
+    Call<ApiResponse<UploadSignDTO>> getUploadSign(@Query("bucket") String bucket , @Query("name") String name);
+
+    @DELETE("file//{bucket}/{key}")
+    Call<ApiResponse<Void>> delete(@Path("bucket") String bucket , @Path("key") String key);
+}

+ 28 - 0
app/src/main/java/com/bogo/android/common/api/service/UserService.java

@@ -0,0 +1,28 @@
+package com.bogo.android.common.api.service;
+
+
+import com.bogo.android.account.api.response.UserDTO;
+import com.bogo.android.common.api.response.ApiResponse;
+
+import java.util.List;
+
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+public interface UserService {
+
+    @GET("user/{id}")
+    Call<ApiResponse<UserDTO>> findOne(@Path("id") long id);
+
+
+    @GET("user")
+    Call<ApiResponse<UserDTO>> getSelfInfo();
+
+    @GET("user/find/{telephone}")
+    Call<ApiResponse<UserDTO>> findOne(@Path("telephone") String telephone);
+
+    @GET("user/list")
+    Call<ApiResponse<List<UserDTO>>> list(@Query("id") Long[] id);
+}

+ 174 - 0
app/src/main/java/com/bogo/android/common/constant/Constant.java

@@ -0,0 +1,174 @@
+
+package com.bogo.android.common.constant;
+
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.R;
+
+public interface Constant {
+
+    int DEF_PAGE_INDEX = 0;
+
+    long MOMENT_PAGE_SIZE = 20;
+
+    long MESSAGE_PAGE_SIZE = 20;
+
+    long MOMENT_REMIND_COUNT = 3;
+
+    long SYSTEM_ID = 0;
+
+    int EMOTION_FACE_SIZE = 20;
+
+    String ATTR_FROM_OFFLINE = "ATTR_FROM_OFFLINE";
+
+    String ATTR_LOGO = "ATTR_LOGO";
+
+    String ATTR_TAG = "ATTR_TAG";
+
+    String ATTR_PROGRESS = "ATTR_PROGRESS";
+
+    String ATTR_HANDLE_FAILURE = "ATTR_HANDLE_FAILURE";
+
+    String ATTR_CODE = "ATTR_CODE";
+
+    String ATTR_TITLE = "TITLE";
+
+    String ATTR_MESSAGE = "MESSAGE";
+
+    String ATTR_MESSAGE_ID = "ATTR_MESSAGE_ID";
+
+    String ATTR_ROOM_TAG = "ATTR_ROOM_TAG";
+
+    String ATTR_MESSAGE_TIMESTAMP = "ATTR_MESSAGE_TIMESTAMP";
+
+    String ATTR_IMAGE_ONLY = "ATTR_IMAGE_ONLY";
+
+    String ATTR_MAX_COUNT = "ATTR_MAX_COUNT";
+
+    String ATTR_URL = "ATTR_URL";
+
+    String ATTR_SINGE_MODE = "ATTR_SINGE_MODE";
+
+    String ATTR_UID = "ATTR_UID";
+
+    String ATTR_VIDEO_TRACK = "ATTR_VIDEO_TRACK";
+    String ATTR_FILE = "ATTR_FILE";
+
+    String ATTR_MEDIA_LIST = "ATTR_MEDIA_LIST";
+
+    String ATTR_GROUP_ID = "ATTR_GROUP_ID";
+
+    String ATTR_HEADER_TOKEN = "access-token";
+
+    String ATTR_APP_TYPE = "x-app-type";
+
+    String ATTR_APP_VERSION = "x-app-version";
+
+    String ATTR_APP_LANGUAGE = "x-app-language";
+
+    String ATTR_URI = "ATTR_URI";
+
+    String ATTR_LIST = "ATTR_LIST";
+
+    String ATTR_FROM_FORWARD = "ATTR_FROM_FORWARD";
+
+    String ATTR_TYPE = "ATTR_TYPE";
+
+    String ATTR_TARGET_CLASS = "ATTR_TARGET_CLASS";
+
+    String ATTR_DURATION = "ATTR_DURATION";
+
+    String ATTR_STATE = "ATTR_STATE";
+
+    String ATTR_INDEX = "ATTR_INDEX";
+
+    String ATTR_CALLING_ON = "ATTR_CALLING_ON";
+
+    String ATTR_SELECTED_UID_LIST = "ATTR_SELECTED_UID_LIST";
+
+    String ATTR_OPTIONAL_UID_LIST = "ATTR_OPTIONAL_UID_LIST";
+
+    String ATTR_EXCLUDED_UID_LIST = "ATTR_EXCLUDED_UID_LIST";
+
+    String ATTR_CHAT_SESSION = "ATTR_CHAT_SESSION";
+
+    String ATTR_ACTION = "ATTR_ACTION";
+
+    String ATTR_FRIEND = "ATTR_FRIEND";
+
+    String ATTR_KEYWORD = "ATTR_KEYWORD";
+
+    String ATTR_SOURCE = "ATTR_SOURCE";
+
+    String ATTR_BADGE_ADDABLE = "ATTR_BADGE_ADDABLE";
+
+    String ATTR_NOTIFICATION_IGNORED= "ATTR_NOTIFICATION_IGNORED";
+
+    String ATTR_TEXT = "ATTR_TEXT";
+
+    int RESULT_CAMERA = 1920;
+
+    /*
+    对话页面消息时间显示间隔
+     */
+    int CHATTING_TIME_SPACE = 2 * 60 * 1000;
+
+    int MAX_GRID_MEMBER = 15;
+
+    int MAX_MOMENT_PHOTO_SIZE = 9;
+
+    int MAX_SELECT_PHOTO_SIZE = 9;
+
+    long MAX_SEND_FILE_SIZE = 50 * org.apache.commons.io.FileUtils.ONE_MB;
+
+    String NEW_MESSAGE_NTF_CHANNEL_ID = "NEW_MESSAGE_NOTIFICATION";
+
+    String NEW_MESSAGE_NTF_CHANNEL_NAME = BogoApplication.getInstance().getString(R.string.message_channel_name);
+
+    String HIGH_NOTIFICATION_CHANNEL_ID = "IMPORTANCE_HIGH_NOTIFICATION";
+
+    String HIGH_NOTIFICATION_CHANNEL_NAME = BogoApplication.getInstance().getString(R.string.message_channel_name_high);
+
+    String DOWNLOAD_NTF_CHANNEL_ID = "APP_DOWNLOAD_NOTIFICATION";
+
+    String DOWNLOAD_NTF_CHANNEL_NAME = BogoApplication.getInstance().getString(R.string.download_channel_name);
+
+    /*
+    定位时间间隔
+    */
+    int LOCATION_TIME_INTERVAL = 5 * 60 * 1000;
+
+    int UC_MOMENT_IMAGE_SIZE = 5;
+
+    //呼叫超时应答
+    int CALL_TIME_OUT =  30 * 1000;
+
+    String CALLING_NTF_CHANNEL_ID = "LIVING_CALL_NOTIFICATION";
+
+    String CALLING_NTF_CHANNEL_NAME = BogoApplication.getInstance().getString(R.string.title_message_channel_calling);
+
+    int CALLING_KEEP_NTF_ID =  46434342;
+
+    String CALL_INCOMING_NTF_CHANNEL_ID = "REALTIME_CALL_INCOMING_NOTIFICATION";
+
+    String CALL_INCOMING_NTF_CHANNEL_NAME = BogoApplication.getInstance().getString(R.string.title_message_channel_call_coming);
+
+
+    String SCREEN_CAPTURE_NTF_CHANNEL_ID = "SCREEN_CAPTURE_NOTIFICATION";
+
+    String SCREEN_CAPTURE_NTF_CHANNEL_NAME = BogoApplication.getInstance().getString(R.string.title_message_channel_screen_capture);
+
+    int CALL_INCOMING_NTF_NOTIFICATION_ID =  74826113;
+
+    int SYSTEM_IMAGE_REQUEST_CODE = 9;
+
+    int SYSTEM_BANNER_REQUEST_CODE = 10;
+
+    int SYSTEM_BANNER_CROP_REQUEST_CODE = 100;
+
+
+    int CALL_ROOM_MIN_MEMBER = 1;
+
+        String UID_QR_PATH = "QR://uid/";
+
+}

+ 87 - 0
app/src/main/java/com/bogo/android/common/constant/FileBucket.java

@@ -0,0 +1,87 @@
+
+package com.bogo.android.common.constant;
+
+/**
+ * 阿里云 OSS 文件 地址构建
+ */
+public enum FileBucket {
+
+    /*
+    用户头像存储bucket
+     */
+    USER_ICON("user-icon"),
+
+    /*
+    用户海报存储bucket
+     */
+    USER_BANNER("user-banner"),
+
+    /*
+    群头像存储bucket
+     */
+    GROUP_ICON("group-icon"),
+
+    /*
+    公众号logo 存储 bucket
+     */
+    MICRO_SERVER_ICON("micro-server-icon"),
+
+    /*
+    聊天文件图片等其他文件
+     */
+    CHAT_SPACE("chat-space"),
+
+    /*
+    朋友圈图片等其他文件存储bucket
+     */
+    MOMENT_SPACE("moment-space"),
+
+    MOMENT_WALLPAPER("moment-wallpaper"),
+
+
+    /*
+     *机器人头像
+     */
+    ROBOT_LOGO("robot-logo"),
+
+    /*
+    表情包Logo
+     */
+    EMOTICON_LOGO("emoticon-logo"),
+
+    /*
+     *表情包图片
+     */
+    EMOTICON_ITEM("emoticon-item"),
+
+
+    /*
+     *会议房间聊天图片
+     */
+    MEETING("meeting"),
+
+    /*
+     *表情包图片
+     */
+    NOTE("note");
+
+    private final String name;
+
+    FileBucket(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public static FileBucket of(String name){
+        for (FileBucket bucket : values()){
+            if (name.equals(bucket.name)){
+                return bucket;
+            }
+        }
+
+        return null;
+    }
+}

+ 92 - 0
app/src/main/java/com/bogo/android/common/constant/IntentAction.java

@@ -0,0 +1,92 @@
+
+package com.bogo.android.common.constant;
+
+import com.bogo.android.BuildConfig;
+
+public interface IntentAction {
+
+    String ACTION_DELETE_MOMENT = BuildConfig.APPLICATION_ID + ".ACTION_DELETE_MOMENT";
+    String ACTION_REFRESH_MOMENT = BuildConfig.APPLICATION_ID + ".ACTION_REFRESH_MOMENT";
+
+    String ACTION_WINDOW_REFRESH_MESSAGE = BuildConfig.APPLICATION_ID + ".ACTION_WINDOW_REFRESH_MESSAGE";
+    String ACTION_WINDOW_ADD_MESSAGE = BuildConfig.APPLICATION_ID + ".ACTION_WINDOW_ADD_MESSAGE";
+    String ACTION_RECENT_APPEND_CHAT = BuildConfig.APPLICATION_ID + ".ACTION_RECENT_APPEND_CHAT";
+    String ACTION_RECENT_DELETE_CHAT = BuildConfig.APPLICATION_ID + ".ACTION_RECENT_DELETE_CHAT";
+    String ACTION_RECENT_REFRESH_CHAT = BuildConfig.APPLICATION_ID + ".ACTION_RECENT_REFRESH_CHAT";
+    String ACTION_RECENT_REFRESH_SOURCE = BuildConfig.APPLICATION_ID + ".ACTION_RECENT_REFRESH_SOURCE";
+    String ACTION_RECENT_REFRESH_LIST = BuildConfig.APPLICATION_ID + ".ACTION_RECENT_REFRESH_LIST";
+
+    String ACTION_RECENT_ENABLE_LISTENER = BuildConfig.APPLICATION_ID + ".ACTION_RECENT_ENABLE_LISTENER";
+    String ACTION_RECENT_DISABLE_LISTENER = BuildConfig.APPLICATION_ID + ".ACTION_RECENT_DISABLE_LISTENER";
+
+    String ACTION_UPLOAD_PROGRESS = BuildConfig.APPLICATION_ID + ".ACTION_UPLOAD_PROGRESS";
+    String ACTION_LOGO_CHANGED = BuildConfig.APPLICATION_ID + ".ACTION_LOGO_CHANGED";
+    String ACTION_MOMENT_WALLPAPER_CHANGED = BuildConfig.APPLICATION_ID + ".ACTION_MOMENT_WALLPAPER_CHANGED";
+
+    String ACTION_MICRO_SERVER_INVOKED = BuildConfig.APPLICATION_ID + ".ACTION_MICRO_SERVER_INVOKED";
+
+    String ACTION_ICON_LONG_CLICKED = BuildConfig.APPLICATION_ID + ".ACTION_ICON_LONG_CLICKED";
+
+    String ACTION_MULTIPLE_PHOTO_SELECTOR = BuildConfig.APPLICATION_ID + ".ACTION_MULTIPLE_PHOTO_SELECTOR";
+
+    String ACTION_MULTIPLE_MEDIA_SELECTOR = BuildConfig.APPLICATION_ID + ".ACTION_MULTIPLE_MEDIA_SELECTOR";
+
+    String ACTION_FROM_NOTIFICATION = BuildConfig.APPLICATION_ID + ".ACTION_FROM_NOTIFICATION";
+
+    String ACTION_FROM_SPLASH_ACTIVITY = BuildConfig.APPLICATION_ID + ".ACTION_FROM_SPLASH_ACTIVITY";
+
+    String ACTION_FROM_RECORD_VIDEO_PREVIEW = BuildConfig.APPLICATION_ID + ".ACTION_FROM_RECORD_VIDEO_PREVIEW";
+
+    String ACTION_MESSAGE_FORWARD_FINISH = BuildConfig.APPLICATION_ID + ".ACTION_MESSAGE_FORWARD_FINISH";
+    String ACTION_MESSAGE_FORWARD_ERROR = BuildConfig.APPLICATION_ID + ".ACTION_MESSAGE_FORWARD_ERROR";
+    String ACTION_MOMENT_CREATE_ERROR = BuildConfig.APPLICATION_ID + ".ACTION_MOMENT_CREATE_ERROR";
+    String ACTION_MOMENT_CREATE_FINISH = BuildConfig.APPLICATION_ID + ".ACTION_MOMENT_CREATE_FINISH";
+    String ACTION_QUOTE_MESSAGE = BuildConfig.APPLICATION_ID + ".ACTION_QUOTE_MESSAGE";
+    String ACTION_REDO_MESSAGE = BuildConfig.APPLICATION_ID + ".ACTION_REDO_MESSAGE";
+    String ACTION_READ_MESSAGE = BuildConfig.APPLICATION_ID + ".ACTION_READ_MESSAGE";
+    String ACTION_SELECT_ADDRESS = BuildConfig.APPLICATION_ID + ".ACTION_SELECT_ADDRESS";
+    String ACTION_SELECT_CHAT_MAP = BuildConfig.APPLICATION_ID + ".ACTION_SELECT_CHAT_MAP";
+
+    String ACTION_TAKE_VIDEO = BuildConfig.APPLICATION_ID + ".ACTION_TAKE_VIDEO";
+    String ACTION_TAKE_PHOTO = BuildConfig.APPLICATION_ID + ".ACTION_TAKE_PHOTO";
+    String ACTION_FROM_CHATTING = BuildConfig.APPLICATION_ID + ".ACTION_FROM_CHATTING";
+    String ACTION_FROM_MOMENT= BuildConfig.APPLICATION_ID + ".ACTION_FROM_MOMENT";
+    String ACTION_OPEN_GALLERY = BuildConfig.APPLICATION_ID + ".ACTION_OPEN_GALLERY";
+
+
+    String ACTION_INCOMING_CALL_HANG_UP = BuildConfig.APPLICATION_ID + ".ACTION_INCOMING_CALL_HANG_UP";
+    String ACTION_INCOMING_CALL_IGNORE = BuildConfig.APPLICATION_ID + ".ACTION_INCOMING_CALL_IGNORE";
+
+    String ACTION_CALL_TIMEOUT = BuildConfig.APPLICATION_ID + ".ACTION_CALL_TIMEOUT";
+
+    String ACTION_CALL_INCOMING_BUSY = BuildConfig.APPLICATION_ID + ".ACTION_CALL_INCOMING_BUSY";
+
+    String ACTION_CALL_INCOMING_NOTIFY = BuildConfig.APPLICATION_ID + ".ACTION_CALL_INCOMING_NOTIFY";
+
+    String ACTION_APK_DOWNLOAD_PROGRESS = BuildConfig.APPLICATION_ID + ".ACTION_APK_DOWNLOAD_PROGRESS";
+
+    String ACTION_START_SCREEN_CAPTURE = BuildConfig.APPLICATION_ID + ".ACTION_START_SCREEN_CAPTURE";
+
+    String ACTION_SESSION_TYPE_CHANGED = BuildConfig.APPLICATION_ID + ".ACTION_SESSION_TYPE_CHANGED";
+
+    String ACTION_ACCOUNT_SYNC_FINISHED = BuildConfig.APPLICATION_ID + ".ACTION_ACCOUNT_SYNC_FINISHED";
+
+    String ACTION_MEETING_MEMBER_QUITED = BuildConfig.APPLICATION_ID + ".ACTION_MEETING_MEMBER_QUITED";
+
+    String ACTION_MEETING_VIDEO_CLOSED = BuildConfig.APPLICATION_ID + ".ACTION_MEETING_VIDEO_CLOSED";
+
+    String ACTION_MEETING_HANG_UP = BuildConfig.APPLICATION_ID + ".ACTION_MEETING_HANG_UP";
+    String ACTION_MEETING_JOIN = BuildConfig.APPLICATION_ID + ".ACTION_MEETING_JOIN";
+
+    String ACTION_RESET_MOMENT_WALLPAPER = BuildConfig.APPLICATION_ID + ".ACTION_RESET_MOMENT_WALLPAPER";
+
+    String ACTION_CHATTING_BACKGROUND_BLUR = BuildConfig.APPLICATION_ID + ".ACTION_CHATTING_BACKGROUND_BLUR";
+
+    String ACTION_CHATTING_BACKGROUND_NORMAL = BuildConfig.APPLICATION_ID + ".ACTION_CHATTING_BACKGROUND_NORMAL";
+
+    String ACTION_LIVING_SERVICE_WINDOW = BuildConfig.APPLICATION_ID + ".ACTION_LIVING_SERVICE_WINDOW";
+
+    String ACTION_LIVING_SERVICE_WINDOW_HIDE = BuildConfig.APPLICATION_ID + ".ACTION_LIVING_SERVICE_WINDOW_HIDE";
+    String ACTION_ORGANIZATION_RELOAD_DONE = BuildConfig.APPLICATION_ID + ".ACTION_ORGANIZATION_RELOAD_DONE";
+
+}

+ 14 - 0
app/src/main/java/com/bogo/android/common/constant/PermissionCode.java

@@ -0,0 +1,14 @@
+
+package com.bogo.android.common.constant;
+
+public interface PermissionCode {
+
+    int REQ_CODE_AUDIO = 4581;
+    int REQ_CODE_CAMERA = 2768;
+    int REQ_CODE_LOCATION = 8515;
+    int REQ_CODE_STORAGE = 6604;
+
+    int REQ_CODE_BOTH = 7586;
+
+
+}

+ 45 - 0
app/src/main/java/com/bogo/android/common/constant/ResponseCode.java

@@ -0,0 +1,45 @@
+package com.bogo.android.common.constant;
+
+public interface ResponseCode {
+
+    /*
+     * api访问成功
+     */
+    int CODE_200 = 200;
+
+    /*
+     * 访问接口 token无效
+     */
+    int CODE_401 = 401;
+
+    /*
+     * 获得没有权限。或者密码错误
+     */
+    int CODE_403 = 403;
+
+    /*
+     * 获得文件或者数据不存在
+     */
+    int CODE_404 = 404;
+
+    /*
+     * 数据重复或者冲突
+     */
+    int CODE_409 = 409;
+
+    /*
+     * 上传文件过大
+     */
+    int CODE_413 = 413;
+
+    /*
+     * 状态被锁定
+     */
+    int CODE_423 = 423;
+
+    /*
+     * 服务内部错误
+     */
+    int CODE_500 = 500;
+
+}

+ 12 - 0
app/src/main/java/com/bogo/android/common/constant/SessionType.java

@@ -0,0 +1,12 @@
+
+package com.bogo.android.common.constant;
+
+public interface SessionType {
+
+	int TYPE_ALL = 0;
+
+	int TYPE_USER = 1;
+	int TYPE_GROUP = 2;
+	int TYPE_SYSTEM = 3;
+	int TYPE_MICRO_SERVER = 4;
+}

+ 16 - 0
app/src/main/java/com/bogo/android/common/constant/SwitchState.java

@@ -0,0 +1,16 @@
+
+package com.bogo.android.common.constant;
+
+public interface SwitchState {
+
+	/**
+	 * 关闭的
+	 */
+	byte CLOSED = 0;
+
+	/**
+	 * 开启的
+	 */
+	byte OPENED = 1;
+
+}

+ 19 - 0
app/src/main/java/com/bogo/android/common/constant/Visible.java

@@ -0,0 +1,19 @@
+
+package com.bogo.android.common.constant;
+
+public enum Visible {
+
+    GONE(0),
+
+    VISIBLE(1);
+
+    private final int value;
+
+    Visible(int value) {
+        this.value = value;
+    }
+
+    public int getValue() {
+        return value;
+    }
+}

+ 77 - 0
app/src/main/java/com/bogo/android/common/database/ConfigDatabase.java

@@ -0,0 +1,77 @@
+
+package com.bogo.android.common.database;
+
+import com.bogo.android.common.database.base.PublicRoomDatabase;
+import com.bogo.android.common.database.repository.ConfigRepository;
+import com.bogo.android.common.entity.Config;
+
+import java.util.List;
+
+public class ConfigDatabase {
+
+    private static final String APP_DOMAIN = "X-APP-CACHE";
+
+    private static final String TRANSLATE_KEY = "MT:%s";
+
+    private static final String SHOW_GROUP_NAME_KEY = "SN:%s";
+
+    private static final ConfigRepository configRepository;
+
+    static {
+        configRepository =  PublicRoomDatabase.getConfigRepository();
+    }
+
+    public static void add(List<Config> configList) {
+
+        if (configList == null || configList.isEmpty()) {
+            return;
+        }
+
+        configRepository.add(configList);
+
+    }
+
+    public static String findValue(String domain, String name) {
+        return configRepository.findValue(domain, name);
+    }
+
+    public static void saveTranslateResult(long id,String text){
+        Config config = new Config();
+        config.id = System.currentTimeMillis();
+        config.domain = APP_DOMAIN;
+        config.name = String.format(TRANSLATE_KEY,id);
+        config.value = text;
+        configRepository.add(config);
+    }
+
+    public static String getTranslateResult(long id){
+        return findValue(APP_DOMAIN,String.format(TRANSLATE_KEY,id));
+    }
+
+    public static void removeTranslateResult(long id){
+        configRepository.delete(APP_DOMAIN,String.format(TRANSLATE_KEY,id));
+    }
+
+    public static void setShowGroupMemberName(long groupId,boolean isShowName){
+        if (isShowName){
+            configRepository.delete(APP_DOMAIN,String.format(SHOW_GROUP_NAME_KEY,groupId));
+            return;
+        }
+        Config config = new Config();
+        config.id = System.currentTimeMillis();
+        config.domain = APP_DOMAIN;
+        config.name = String.format(SHOW_GROUP_NAME_KEY,groupId);
+        config.value = String.valueOf(false);
+        configRepository.add(config);
+    }
+
+    public static boolean isShowGroupMemberName(long id){
+        String value =  findValue(APP_DOMAIN,String.format(SHOW_GROUP_NAME_KEY,id));
+        if (value == null){
+            return true;
+        }
+        return Boolean.parseBoolean(value);
+    }
+
+
+}

+ 28 - 0
app/src/main/java/com/bogo/android/common/database/GlideVersionDatabase.java

@@ -0,0 +1,28 @@
+
+package com.bogo.android.common.database;
+
+import com.bogo.android.common.database.base.PublicRoomDatabase;
+import com.bogo.android.common.database.repository.GlideVersionRepository;
+import com.bogo.android.common.entity.GlideVersion;
+
+public class GlideVersionDatabase {
+
+    private static final GlideVersionRepository glideVersionRepository;
+
+    static {
+        glideVersionRepository =  PublicRoomDatabase.getGlideVersionRepository();
+    }
+
+    public static void add(String url) {
+        glideVersionRepository.delete(url);
+        GlideVersion version = new GlideVersion();
+        version.url = url;
+        version.version = String.valueOf(System.currentTimeMillis());
+        glideVersionRepository.add(version);
+    }
+
+    public static String getVersion(String url) {
+        return glideVersionRepository.findVersion(url);
+    }
+
+}

+ 91 - 0
app/src/main/java/com/bogo/android/common/database/UserDatabase.java

@@ -0,0 +1,91 @@
+
+package com.bogo.android.common.database;
+
+
+import com.bogo.android.common.database.base.PublicRoomDatabase;
+import com.bogo.android.common.database.repository.UserRepository;
+import com.bogo.android.common.entity.User;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+public class UserDatabase {
+
+    private static final AtomicReference<String> nameCache = new AtomicReference<>();
+
+    private static final UserRepository userRepository;
+
+    static {
+        userRepository =  PublicRoomDatabase.getUserRepository();
+    }
+
+    public static void add(User user) {
+
+        userRepository.deleteAll();
+
+        userRepository.add(user);
+
+        nameCache.set(null);
+
+    }
+
+    public static boolean hasAccount(){
+        return userRepository.count() > 0;
+    }
+
+    public static User findOne() {
+        return userRepository.findOne();
+    }
+
+    public static Long findUid() {
+        return userRepository.findUid();
+    }
+
+    public static String findTelephone() {
+        return userRepository.findTelephone();
+    }
+
+    public static String findName() {
+
+        if (nameCache.get() != null){
+            return nameCache.get();
+        }
+
+        String name = userRepository.findName();
+
+        nameCache.set(name);
+
+        return name;
+    }
+
+    public static String findMotto() {
+        return userRepository.findMotto();
+    }
+
+    public static void setName(String name) {
+        userRepository.updateName(name);
+    }
+
+    public static void setTelephone(String telephone) {
+        userRepository.updateTelephone(telephone);
+    }
+
+    public static void setMotto(String motto) {
+        userRepository.updateMotto(motto);
+    }
+
+    public static void setEmail(String email) {
+        userRepository.updateEmail(email);
+    }
+
+    public static void setGender(byte gender) {
+        userRepository.updateGender(gender);
+    }
+
+    public static String getToken() {
+        return userRepository.getToken();
+    }
+
+    public static void removeToken() {
+        userRepository.removeToken();
+    }
+}

+ 15 - 0
app/src/main/java/com/bogo/android/common/database/base/EmptyMigration.java

@@ -0,0 +1,15 @@
+package com.bogo.android.common.database.base;
+
+import androidx.annotation.NonNull;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+class EmptyMigration extends Migration {
+    public EmptyMigration(int startVersion, int endVersion) {
+        super(startVersion, endVersion);
+    }
+
+    @Override
+    public void migrate(@NonNull SupportSQLiteDatabase database) {
+    }
+}

+ 180 - 0
app/src/main/java/com/bogo/android/common/database/base/PrivateRoomDatabase.java

@@ -0,0 +1,180 @@
+
+package com.bogo.android.common.database.base;
+
+import androidx.annotation.NonNull;
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.BuildConfig;
+import com.bogo.android.emoticon.database.EmoticonDatabase;
+import com.bogo.android.emoticon.database.EmoticonItemDatabase;
+import com.bogo.android.emoticon.database.dao.EmoticonItemRepository;
+import com.bogo.android.emoticon.database.dao.EmoticonRepository;
+import com.bogo.android.emoticon.entity.Emoticon;
+import com.bogo.android.emoticon.entity.EmoticonItem;
+import com.bogo.android.friend.database.FriendDatabase;
+import com.bogo.android.friend.database.FriendRequestDatabase;
+import com.bogo.android.friend.database.dao.FriendRepository;
+import com.bogo.android.friend.database.dao.FriendRequestRepository;
+import com.bogo.android.friend.entity.Friend;
+import com.bogo.android.friend.entity.FriendRequest;
+import com.bogo.android.group.database.GroupDatabase;
+import com.bogo.android.group.database.GroupMemberDatabase;
+import com.bogo.android.group.database.GroupRobotDatabase;
+import com.bogo.android.group.database.dao.GroupMemberRepository;
+import com.bogo.android.group.database.dao.GroupRepository;
+import com.bogo.android.group.database.dao.GroupRobotRepository;
+import com.bogo.android.group.entity.Group;
+import com.bogo.android.group.entity.GroupMember;
+import com.bogo.android.group.entity.GroupRobot;
+import com.bogo.android.message.database.ChatSessionDatabase;
+import com.bogo.android.message.database.MessageDatabase;
+import com.bogo.android.message.database.ReadRecordDatabase;
+import com.bogo.android.message.database.dao.ChatSessionRepository;
+import com.bogo.android.message.database.dao.MessageRepository;
+import com.bogo.android.message.database.dao.ReadRecordRepository;
+import com.bogo.android.message.entity.ChatSession;
+import com.bogo.android.message.entity.Message;
+import com.bogo.android.message.entity.ReadRecord;
+import com.bogo.android.micro.database.MicroServerDatabase;
+import com.bogo.android.micro.database.MicroServerMenuDatabase;
+import com.bogo.android.micro.database.dao.MicroServerMenuRepository;
+import com.bogo.android.micro.database.dao.MicroServerRepository;
+import com.bogo.android.micro.entity.MicroServer;
+import com.bogo.android.micro.entity.MicroServerMenu;
+import com.bogo.android.moment.database.CommentDatabase;
+import com.bogo.android.moment.database.MomentDatabase;
+import com.bogo.android.moment.database.MomentRuleDatabase;
+import com.bogo.android.moment.database.dao.CommentRepository;
+import com.bogo.android.moment.database.dao.MomentRepository;
+import com.bogo.android.moment.database.dao.MomentRuleRepository;
+import com.bogo.android.moment.entity.Comment;
+import com.bogo.android.moment.entity.Moment;
+import com.bogo.android.moment.entity.MomentRule;
+import com.bogo.android.note.database.NoteDatabase;
+import com.bogo.android.note.database.dao.NoteRepository;
+import com.bogo.android.note.entity.Note;
+import com.bogo.android.organization.database.DepartmentDatabase;
+import com.bogo.android.organization.database.DepartmentMemberDatabase;
+import com.bogo.android.organization.database.OrganizationDatabase;
+import com.bogo.android.organization.database.dao.DepartmentMemberRepository;
+import com.bogo.android.organization.database.dao.DepartmentRepository;
+import com.bogo.android.organization.database.dao.OrganizationRepository;
+import com.bogo.android.organization.entity.Department;
+import com.bogo.android.organization.entity.DepartmentMember;
+import com.bogo.android.organization.entity.Organization;
+import com.bogo.android.webrtc.database.GroupMeetingDatabase;
+import com.bogo.android.webrtc.database.dao.GroupMeetingRepository;
+import com.bogo.android.webrtc.entity.GroupMeeting;
+
+
+@Database(entities = {
+        Comment.class,
+        Friend.class,
+        Group.class,
+        GroupMember.class,
+        Message.class,
+        MicroServer.class,
+        MicroServerMenu.class,
+        Moment.class,
+        MomentRule.class,
+        Emoticon.class,
+        EmoticonItem.class,
+        ChatSession.class,
+        GroupRobot.class,
+        Organization.class,
+        Department.class,
+        DepartmentMember.class,
+        ReadRecord.class,
+        Note.class,
+        FriendRequest.class,
+        GroupMeeting.class
+},
+        version = BuildConfig.VERSION_CODE,
+        exportSchema = false)
+public abstract class PrivateRoomDatabase extends RoomDatabase {
+
+    private final static String DATABASE_NAME = "%s.db";
+
+    private static PrivateRoomDatabase DATABASE_INSTANCE;
+
+    abstract CommentRepository commentRepository();
+
+    abstract FriendRepository friendRepository();
+
+    abstract GroupRepository groupRepository();
+
+    abstract GroupMemberRepository groupMemberRepository();
+
+    abstract MomentRepository momentRepository();
+
+    abstract MomentRuleRepository momentRuleRepository();
+
+    abstract MessageRepository messageRepository();
+
+    abstract MicroServerRepository microServerRepository();
+
+    abstract MicroServerMenuRepository microServerMenuRepository();
+
+    abstract EmoticonItemRepository emoticonItemRepository();
+
+    abstract EmoticonRepository emoticonRepository();
+
+    abstract GroupMeetingRepository groupMeetingRepository();
+
+    abstract ChatSessionRepository chatSessionRepository();
+
+    abstract GroupRobotRepository groupRobotRepository();
+
+    abstract OrganizationRepository organizationRepository();
+
+    abstract DepartmentRepository departmentRepository();
+
+    abstract DepartmentMemberRepository departmentMemberRepository();
+
+    abstract ReadRecordRepository readRecordRepository();
+
+    abstract NoteRepository noteRecordRepository();
+
+    abstract FriendRequestRepository friendRequestRepository();
+
+    public static void init(long uid) {
+
+        if (DATABASE_INSTANCE != null) {
+            DATABASE_INSTANCE.close();
+        }
+
+        String database = String.format(DATABASE_NAME, uid);
+
+        DATABASE_INSTANCE = Room.databaseBuilder(BogoApplication.getInstance(), PrivateRoomDatabase.class, database)
+                .allowMainThreadQueries()
+                .addMigrations(new EmptyMigration(100,101))
+                .build();
+
+        CommentDatabase.setCommentRepository(DATABASE_INSTANCE.commentRepository());
+        FriendDatabase.setFriendRepository(DATABASE_INSTANCE.friendRepository());
+        GroupDatabase.setGroupRepository(DATABASE_INSTANCE.groupRepository());
+        GroupMemberDatabase.setGroupMemberRepository(DATABASE_INSTANCE.groupMemberRepository());
+        GroupRobotDatabase.setGroupRobotRepository(DATABASE_INSTANCE.groupRobotRepository());
+        MomentDatabase.setMomentRepository(DATABASE_INSTANCE.momentRepository());
+        MomentRuleDatabase.setMomentRuleRepository(DATABASE_INSTANCE.momentRuleRepository());
+        MicroServerDatabase.setMicroServerRepository(DATABASE_INSTANCE.microServerRepository());
+        MicroServerMenuDatabase.setMicroServerMenuRepository(DATABASE_INSTANCE.microServerMenuRepository());
+        MessageDatabase.setMessageRepository(DATABASE_INSTANCE.messageRepository());
+        EmoticonDatabase.setEmoticonRepository(DATABASE_INSTANCE.emoticonRepository());
+        EmoticonItemDatabase.setEmoticonItemRepository(DATABASE_INSTANCE.emoticonItemRepository());
+        ChatSessionDatabase.setChatSessionRepository(DATABASE_INSTANCE.chatSessionRepository());
+        ReadRecordDatabase.setReadRecordRepository(DATABASE_INSTANCE.readRecordRepository());
+        NoteDatabase.setNoteRepository(DATABASE_INSTANCE.noteRecordRepository());
+        FriendRequestDatabase.setFriendRequestRepository(DATABASE_INSTANCE.friendRequestRepository());
+        OrganizationDatabase.setOrganizationRepository(DATABASE_INSTANCE.organizationRepository());
+        DepartmentDatabase.setDepartmentRepository(DATABASE_INSTANCE.departmentRepository());
+        DepartmentMemberDatabase.setDepartmentMemberRepository(DATABASE_INSTANCE.departmentMemberRepository());
+        GroupMeetingDatabase.setGroupMeetingRepository(DATABASE_INSTANCE.groupMeetingRepository());
+
+    }
+
+}

+ 49 - 0
app/src/main/java/com/bogo/android/common/database/base/PublicRoomDatabase.java

@@ -0,0 +1,49 @@
+
+package com.bogo.android.common.database.base;
+
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import com.bogo.android.BogoApplication;
+import com.bogo.android.BuildConfig;
+import com.bogo.android.common.database.repository.ConfigRepository;
+import com.bogo.android.common.database.repository.GlideVersionRepository;
+import com.bogo.android.common.database.repository.UserRepository;
+import com.bogo.android.common.entity.Config;
+import com.bogo.android.common.entity.GlideVersion;
+import com.bogo.android.common.entity.User;
+
+@Database(entities = {Config.class, GlideVersion.class, User.class}, version = BuildConfig.VERSION_CODE, exportSchema = false)
+public abstract class PublicRoomDatabase extends RoomDatabase {
+
+    private final static String COMMON_DATABASE_NAME = "common.db";
+
+    abstract ConfigRepository configRepository();
+
+    abstract GlideVersionRepository glideVersionRepository();
+
+    abstract UserRepository userRepository();
+
+    private final static PublicRoomDatabase DATABASE_INSTANCE;
+
+    static {
+        DATABASE_INSTANCE = Room.databaseBuilder(BogoApplication.getInstance(), PublicRoomDatabase.class, COMMON_DATABASE_NAME)
+                .allowMainThreadQueries()
+                .addMigrations(new EmptyMigration(100,101))
+                .build();
+    }
+
+    public static ConfigRepository getConfigRepository() {
+        return DATABASE_INSTANCE.configRepository();
+    }
+
+    public static GlideVersionRepository getGlideVersionRepository() {
+        return DATABASE_INSTANCE.glideVersionRepository();
+    }
+
+    public static UserRepository getUserRepository() {
+        return DATABASE_INSTANCE.userRepository();
+    }
+
+}

+ 33 - 0
app/src/main/java/com/bogo/android/common/database/repository/ConfigRepository.java

@@ -0,0 +1,33 @@
+package com.bogo.android.common.database.repository;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+import com.bogo.android.common.entity.Config;
+
+import java.util.List;
+
+@Dao
+public interface ConfigRepository {
+
+    @Query("select * from t_bogo_config")
+    List<Config> findList();
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void add(List<Config> configList);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void add(Config config);
+
+    @Delete
+    void delete(Config config);
+
+    @Query("delete from t_bogo_config where domain = :domain and name = :name")
+    void delete(String domain, String name);
+
+    @Query("select value from t_bogo_config where domain = :domain and name = :name")
+    String findValue(String domain, String name);
+}

+ 20 - 0
app/src/main/java/com/bogo/android/common/database/repository/GlideVersionRepository.java

@@ -0,0 +1,20 @@
+package com.bogo.android.common.database.repository;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+import com.bogo.android.common.entity.GlideVersion;
+
+@Dao
+public interface GlideVersionRepository {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void add(GlideVersion version);
+
+    @Query("delete from t_bogo_glide_version where url = :url")
+    void delete(String url);
+
+    @Query("select version from t_bogo_glide_version where url = :url order by id desc limit 1")
+    String findVersion(String url);
+}

+ 58 - 0
app/src/main/java/com/bogo/android/common/database/repository/UserRepository.java

@@ -0,0 +1,58 @@
+package com.bogo.android.common.database.repository;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+import com.bogo.android.common.entity.User;
+
+@Dao
+public interface UserRepository {
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void add(User user);
+
+    @Query("delete from t_bogo_user")
+    void deleteAll();
+
+    @Query("select * from t_bogo_user limit 1")
+    User findOne();
+
+    @Query("select token from t_bogo_user limit 1")
+    String getToken();
+
+    @Query("update t_bogo_user set token = null")
+    void removeToken();
+
+    @Query("select count(*) from t_bogo_user")
+    long count();
+
+    @Query("select id from t_bogo_user limit 1")
+    Long findUid();
+
+    @Query("select name from t_bogo_user limit 1")
+    String findName();
+
+    @Query("select telephone from t_bogo_user limit 1")
+    String findTelephone();
+
+    @Query("update t_bogo_user set name = :name")
+    void updateName(String name);
+
+    @Query("update t_bogo_user set motto = :motto")
+    void updateMotto(String motto);
+
+    @Query("update t_bogo_user set telephone = :telephone")
+    void updateTelephone(String telephone);
+
+
+    @Query("select motto from t_bogo_user limit 1")
+    String findMotto();
+
+    @Query("update t_bogo_user set email = :email")
+    void updateEmail(String email);
+
+    @Query("update t_bogo_user set gender = :gender")
+    void updateGender(byte gender);
+}

+ 38 - 0
app/src/main/java/com/bogo/android/common/dialog/AlbumBucketWindow.java

@@ -0,0 +1,38 @@
+
+package com.bogo.android.common.dialog;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+
+import androidx.recyclerview.widget.DefaultItemAnimator;
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.adapter.AlbumBucketListAdapter;
+import com.bogo.android.common.listener.OnItemClickedListener;
+import com.bogo.android.common.model.Bucket;
+import com.bogo.android.databinding.LayoutAlbumWindowBinding;
+
+import java.util.List;
+
+public class AlbumBucketWindow extends BlurBottomSheetDialog<LayoutAlbumWindowBinding> {
+    private final AlbumBucketListAdapter adapter;
+    public AlbumBucketWindow(Context context, OnItemClickedListener<Bucket> listener) {
+        super(context);
+        binding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+        binding.recyclerView.setItemAnimator(new DefaultItemAnimator());
+        binding.recyclerView.addItemDecoration(new DividerItemDecoration(context, LinearLayout.VERTICAL));
+        binding.recyclerView.setAdapter(adapter = new AlbumBucketListAdapter());
+        adapter.setOnItemClickedListener(listener);
+    }
+    public void setAlbumBucketList(List<Bucket> list) {
+        adapter.addAll(list);
+    }
+
+    @Override
+    public BindingCompat<LayoutAlbumWindowBinding> getBinding() {
+        LayoutAlbumWindowBinding binding = LayoutAlbumWindowBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+}

+ 73 - 0
app/src/main/java/com/bogo/android/common/dialog/BlurAlterDialog.java

@@ -0,0 +1,73 @@
+
+package com.bogo.android.common.dialog;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.RenderEffect;
+import android.graphics.Shader;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.StyleRes;
+import androidx.appcompat.app.AppCompatDialog;
+
+import com.bogo.android.R;
+import com.bogo.android.common.App;
+import com.bogo.android.common.BindingCompat;
+
+public abstract class BlurAlterDialog<Binding> extends AppCompatDialog {
+
+    private View decorView;
+    protected Binding binding;
+    public BlurAlterDialog(@NonNull Context context) {
+        this(context,R.style.commonDialogStyle);
+    }
+
+
+    public BlurAlterDialog(@NonNull Context context, @StyleRes int styleId) {
+        super(context,styleId);
+        BindingCompat<Binding> bindingCompat = getBinding();
+        this.binding = bindingCompat.getBinding();
+        setContentView(bindingCompat.getRootView());
+
+        if (context instanceof Activity){
+            decorView = App.getActivity(context).getWindow().getDecorView();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        WindowManager.LayoutParams p = getWindow().getAttributes();
+        p.width = (int) (Resources.getSystem().getDisplayMetrics().widthPixels * getWidthRatio());
+        getWindow().setAttributes(p);
+    }
+
+    protected float getWidthRatio(){
+        return 0.85F;
+    }
+
+    public abstract BindingCompat<Binding> getBinding();
+
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && decorView != null) {
+            decorView.setRenderEffect(RenderEffect.createBlurEffect(25F,25F, Shader.TileMode.CLAMP));
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && decorView != null) {
+            decorView.setRenderEffect(null);
+        }
+    }
+
+}

+ 50 - 0
app/src/main/java/com/bogo/android/common/dialog/BlurBottomSheetDialog.java

@@ -0,0 +1,50 @@
+
+package com.bogo.android.common.dialog;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.RenderEffect;
+import android.graphics.Shader;
+import android.os.Build;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.bogo.android.common.BindingCompat;
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+
+public abstract class BlurBottomSheetDialog<Binding> extends BottomSheetDialog {
+    private View decorView;
+    protected Binding binding;
+    public BlurBottomSheetDialog(@NonNull Context context) {
+        super(context);
+
+        BindingCompat<Binding> bindingCompat = getBinding();
+        this.binding = bindingCompat.getBinding();
+        setContentView(bindingCompat.getRootView());
+
+        if (context instanceof Activity){
+            decorView = ((Activity) context).getWindow().getDecorView();
+        }
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && decorView != null) {
+            decorView.setRenderEffect(RenderEffect.createBlurEffect(25F,25F, Shader.TileMode.CLAMP));
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && decorView != null) {
+            decorView.setRenderEffect(null);
+        }
+    }
+
+    public abstract BindingCompat<Binding> getBinding();
+
+
+}

+ 46 - 0
app/src/main/java/com/bogo/android/common/dialog/CancelDialog.java

@@ -0,0 +1,46 @@
+
+package com.bogo.android.common.dialog;
+
+
+import android.content.Context;
+import android.view.View;
+
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.databinding.DialogCustomCancelBinding;
+
+
+public class CancelDialog extends BlurAlterDialog<DialogCustomCancelBinding> implements View.OnClickListener{
+
+
+    public CancelDialog(Context context) {
+        super(context);
+        binding.button.setOnClickListener(this);
+        setCanceledOnTouchOutside(false);
+        setCancelable(false);
+    }
+
+    @Override
+    public BindingCompat<DialogCustomCancelBinding> getBinding() {
+        DialogCustomCancelBinding binding = DialogCustomCancelBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+
+    public void setMessage(int sid) {
+        binding.message.setText(sid);
+    }
+
+    public void setMessage(CharSequence message) {
+        binding.message.setText(message);
+    }
+
+    public void setButtonText(CharSequence left) {
+        binding.button.setText(left);
+    }
+
+
+    @Override
+    public void onClick(View view) {
+        dismiss();
+    }
+}

+ 104 - 0
app/src/main/java/com/bogo/android/common/dialog/CustomDialog.java

@@ -0,0 +1,104 @@
+
+package com.bogo.android.common.dialog;
+
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.StringRes;
+
+import com.bogo.android.R;
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.listener.OnDialogButtonClickListener;
+import com.bogo.android.databinding.DialogCustomConfirmBinding;
+
+
+public class CustomDialog extends BlurAlterDialog<DialogCustomConfirmBinding> implements View.OnClickListener{
+
+    private OnDialogButtonClickListener onDialogButtonClickListener;
+
+    private Object tag;
+
+    private boolean autoDismiss;
+
+
+    public CustomDialog(Context context) {
+        super(context);
+        binding.footer.leftButton.setOnClickListener(this);
+        binding.footer.rightButton.setOnClickListener(this);
+        setCanceledOnTouchOutside(false);
+    }
+
+    @Override
+    public BindingCompat<DialogCustomConfirmBinding> getBinding() {
+        DialogCustomConfirmBinding binding = DialogCustomConfirmBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    public void setOnDialogButtonClickListener(OnDialogButtonClickListener onDialogButtonClickListener) {
+        this.onDialogButtonClickListener = onDialogButtonClickListener;
+    }
+
+    public Object getTag() {
+        return tag;
+    }
+
+    public boolean isAutoDismiss() {
+        return autoDismiss;
+    }
+
+    public void setAutoDismiss(boolean autoDismiss) {
+        this.autoDismiss = autoDismiss;
+    }
+
+    public void setTag(Object tag) {
+       this.tag = tag;
+    }
+
+    public void setMessage(int sid) {
+       binding.message.setText(sid);
+    }
+
+    public void setMessage(CharSequence message) {
+       binding.message.setText(message);
+    }
+
+    public void hideCancelButton() {
+       binding.footer.leftButton.setEnabled(false);
+    }
+
+    public void setButtonsText(CharSequence left, CharSequence right) {
+       binding.footer.leftButton.setText(left);
+       binding.footer.rightButton.setText(right);
+    }
+    public void setRightButtonsText(@StringRes int right) {
+       binding.footer.rightButton.setText(right);
+    }
+
+    public void setButtonsText(@StringRes int left, @StringRes int right) {
+       binding.footer.leftButton.setText(left);
+       binding.footer.rightButton.setText(right);
+    }
+
+
+    @Override
+    public void onClick(View view) {
+        if (onDialogButtonClickListener==null){
+            dismiss();
+            return;
+        }
+        if (view.getId() == R.id.leftButton) {
+            if (isAutoDismiss()){
+                dismiss();
+            }
+            onDialogButtonClickListener.onLeftButtonClicked();
+        }
+        if (view.getId() == R.id.rightButton) {
+            if (isAutoDismiss()){
+                dismiss();
+            }
+            onDialogButtonClickListener.onRightButtonClicked();
+        }
+    }
+
+}

+ 39 - 0
app/src/main/java/com/bogo/android/common/dialog/CustomProgressDialog.java

@@ -0,0 +1,39 @@
+
+package com.bogo.android.common.dialog;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatDialog;
+
+import com.bogo.android.R;
+
+public class CustomProgressDialog extends AppCompatDialog {
+
+    private final TextView messageView;
+
+    public CustomProgressDialog(Context context) {
+        super(context,R.style.commonDialogStyle);
+        setContentView(R.layout.layout_progress_dialog);
+        messageView = findViewById(R.id.title);
+    }
+
+    @Override
+    protected void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        WindowManager.LayoutParams p = getWindow().getAttributes();
+        p.width = (int) (Resources.getSystem().getDisplayMetrics().widthPixels * 0.8);
+        getWindow().setAttributes(p);
+    }
+    public void setMessage(int resId) {
+        messageView.setText(resId);
+    }
+
+    public void setMessage(String content) {
+        messageView.setText(content);
+    }
+
+}

+ 38 - 0
app/src/main/java/com/bogo/android/common/dialog/DownloadProgressDialog.java

@@ -0,0 +1,38 @@
+
+package com.bogo.android.common.dialog;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.databinding.DialogDownloadProgressBinding;
+
+public class DownloadProgressDialog extends BlurAlterDialog<DialogDownloadProgressBinding> {
+
+    public DownloadProgressDialog(Context context) {
+        super(context);
+        setCancelable(false);
+        setCanceledOnTouchOutside(false);
+    }
+
+    @Override
+    public BindingCompat<DialogDownloadProgressBinding> getBinding() {
+        DialogDownloadProgressBinding binding = DialogDownloadProgressBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    @Override
+    protected void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        WindowManager.LayoutParams p = getWindow().getAttributes();
+        p.width = (int) (Resources.getSystem().getDisplayMetrics().widthPixels * 0.75);
+        getWindow().setAttributes(p);
+    }
+
+    public void setProgress(int progress) {
+        binding.progressBar.setProgress(progress);
+    }
+
+}

+ 42 - 0
app/src/main/java/com/bogo/android/common/dialog/MediaMenuWindow.java

@@ -0,0 +1,42 @@
+
+package com.bogo.android.common.dialog;
+
+import android.content.Context;
+import android.view.View;
+
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.common.listener.OnMenuClickedListener;
+import com.bogo.android.common.util.AppTools;
+import com.bogo.android.databinding.DialogMediaMenuBinding;
+
+public class MediaMenuWindow extends BlurBottomSheetDialog<DialogMediaMenuBinding> implements View.OnClickListener {
+    private OnMenuClickedListener<Integer> onMenuClickedListener;
+    public MediaMenuWindow(Context context) {
+        super(context);
+        binding.menuShared.setOnClickListener(this);
+        binding.menuSave.setOnClickListener(this);
+        binding.menuCollect.setOnClickListener(this);
+    }
+
+    public void setOnMenuClickedListener(OnMenuClickedListener<Integer> onMenuClickedListener) {
+        this.onMenuClickedListener = onMenuClickedListener;
+    }
+
+    @Override
+    public BindingCompat<DialogMediaMenuBinding> getBinding() {
+        DialogMediaMenuBinding binding = DialogMediaMenuBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+    @Override
+    public void show() {
+        super.show();
+        AppTools.vibrate(15);
+    }
+
+    @Override
+    public void onClick(View v) {
+        dismiss();
+        onMenuClickedListener.onMenuClicked(v.getId());
+    }
+}

+ 46 - 0
app/src/main/java/com/bogo/android/common/dialog/ShowTimeDialog.java

@@ -0,0 +1,46 @@
+
+package com.bogo.android.common.dialog;
+
+
+import android.content.Context;
+import android.view.View;
+
+import com.bogo.android.common.BindingCompat;
+import com.bogo.android.databinding.DialogShowTimeBinding;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+
+public class ShowTimeDialog extends BlurAlterDialog<DialogShowTimeBinding> implements View.OnClickListener{
+    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
+
+
+    public ShowTimeDialog(Context context) {
+        super(context);
+        binding.button.setOnClickListener(this);
+        setCanceledOnTouchOutside(false);
+        setCancelable(false);
+    }
+
+    @Override
+    public BindingCompat<DialogShowTimeBinding> getBinding() {
+        DialogShowTimeBinding binding = DialogShowTimeBinding.inflate(getLayoutInflater());
+        return BindingCompat.of(binding,binding.getRoot());
+    }
+
+
+    public void show(long time) {
+        binding.time.setText(FORMAT.format(new Date(time)));
+        show();
+    }
+
+
+
+
+    @Override
+    public void onClick(View view) {
+        dismiss();
+    }
+}

+ 31 - 0
app/src/main/java/com/bogo/android/common/entity/Config.java

@@ -0,0 +1,31 @@
+
+package com.bogo.android.common.entity;
+
+
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import java.io.Serializable;
+
+@Entity(tableName = "t_bogo_config")
+public class Config implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @PrimaryKey
+    public long id;
+
+    @ColumnInfo
+    public String name;
+
+    @ColumnInfo
+    public String value;
+
+    @ColumnInfo
+    public String domain;
+
+    @ColumnInfo
+    public String description;
+
+}

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.