提交 0958760f authored 作者: ganzhe's avatar ganzhe

Initial commit

上级
流水线 #267 已失败 于阶段
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
# Default ignored files
/shelf/
/workspace.xml
Chatx
\ No newline at end of file
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>
\ No newline at end of file
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="C:\Users\Admin\.android\avd\Pixel_5_API_27_2.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2022-06-22T03:22:22.779243500Z" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$USER_HOME$/.gradle/wrapper/dists/gradle-7.3.3-bin/6a41zxkdtcxs8rphpq6y0069z/gradle-7.3.3" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/keeplivelib" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
</component>
</project>
\ No newline at end of file
差异被折叠。
# Chatx介绍
===================================================================
## 本项目是再mobileSDK上改造的android端聊天项目,如需了解前因后果,可以github搜索mobileSDK
<br>
<br>
## 1.环境搭建
java8 <br>
android studio <br>
kotlin <br>
## 2.框架介绍
基于google官方的mvvm架构 、kotlin语言 、coroutines 、okhttp 、retrofit2 brvah 、SmartRefresh 、Room操作数据库 、MMKV文件存储缓存 、轻量级依赖注入koin
* activity 、fragment只处理界面显示和简单的逻辑
* viewmodel处理业务逻辑 、包括本地数据的操作和网络数据处理
* dataSource有LocalDataSource RemoteDataSource 、主要是本地数据的处理 和远程网络数据的请求,给viewmodel调用,viewmodel通过livedata响应给activity和fragment 、重要一点是所有可观察数据都放在LocalDataSource中的livedata或者flow
<br>
<br>
## 3.文件目录介绍
<br>
<br>
## 4.流程介绍
\ No newline at end of file
/build
\ No newline at end of file
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
}
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
// buildToolsVersion rootProject.ext.android["buildToolsVersion"]
defaultConfig {
applicationId "com.renrenliao.chat"
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
multiDexEnabled true
//noinspection HighAppVersionCode
versionCode 2022050601
versionName "2.1.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
// sourceSets {
// main {
// java.srcDirs = ['src']
// }
// test {
// java.srcDirs = ['test']
// }
// }
signingConfigs {
release {
storeFile file('../keystore.jks')
storePassword "jony5422"
keyAlias 'test'
keyPassword "jony5422"
}
}
buildTypes {
debug{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release //指定签名文件
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release //指定签名文件
}
}
}
dependencies {
implementation fileTree(include: ['*.jar',"*.aar"], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation rootProject.ext.supportx["core_ktx"]
implementation rootProject.ext.supportx["appcompat"]
implementation rootProject.ext.supportx["material"]
implementation rootProject.ext.supportx["constraintlayout"]
testImplementation rootProject.ext.supportx["junit"]
androidTestImplementation rootProject.ext.supportx["test_ext_junit"]
androidTestImplementation rootProject.ext.supportx["espresso_core"]
implementation rootProject.ext.dependencies["multidex"]
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1'
implementation project(':keeplivelib')
implementation files('libs/pinyin4j-2.5.0.jar')
implementation files('libs/commons-lang3-3.12.0.jar')
//koin
implementation "io.insert-koin:koin-android:$koin_version"
//retrofit + okHttp4
testImplementation("com.squareup.okhttp3:mockwebserver:4.9.0")
implementation rootProject.ext.dependencies["okhttp"]
implementation rootProject.ext.dependencies["okhttp_logging_interceptor"]
implementation rootProject.ext.dependencies["retrofit"]
implementation rootProject.ext.dependencies["converter_gson"]
//协程
implementation rootProject.ext.dependencies["coroutines_core"]
implementation rootProject.ext.dependencies["coroutines_android"]
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1'
//rxjava
implementation rootProject.ext.dependencies["rxandroid"]
implementation rootProject.ext.dependencies["rxjava"]
implementation rootProject.ext.dependencies["lifecycle_runtime_ktx"]
implementation rootProject.ext.dependencies["lifecycle_viewmodel_ktx"]
implementation rootProject.ext.dependencies["lifecycle_livedata_ktx"]
kapt rootProject.ext.dependencies["room_compiler"]
implementation rootProject.ext.dependencies["room_runtime"]
implementation rootProject.ext.dependencies["room_ktx"]
// implementation rootProject.ext.dependencies["room_rxjava"]
//tencent mmkv 基于 mmap 内存映射的 key-value 存储组件
implementation rootProject.ext.dependencies["mmkv"]
//glide
implementation rootProject.ext.dependencies["glide"]
kapt rootProject.ext.dependencies["glide_compiler"]
implementation 'jp.wasabeef:glide-transformations:4.3.0'
// 基础依赖包,必须要依赖
implementation 'com.geyifeng.immersionbar:immersionbar:3.2.2'
// kotlin扩展(可选)
implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.2'
implementation rootProject.ext.dependencies["assent"]
//implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
//implementation 'com.alibaba:fastjson:1.2.76'
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.6'
implementation 'io.github.scwang90:refresh-layout-kernel:2.0.5' //核心必须依赖
implementation 'io.github.scwang90:refresh-header-classics:2.0.5' //经典刷新头
implementation 'io.github.scwang90:refresh-header-material:2.0.5'
// implementation 'io.github.scwang90:refresh-footer-classics:2.0.5' //经典加载
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.renrenliao.im
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.renrenliao.im", appContext.packageName)
}
}
\ No newline at end of file
package com.renrenliao.im.utils;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class DigestUtilsTest {
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void md5() {
String s = "XSUGINROBD4BEQGGY9ARDMX666U7Y08S"+"1654756025944";//+"100005"+"4e82812adcbb4b469da2b8f10d4a52d2";
String a = DigestUtils.md5(s);
System.out.println(a);
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.renrenliao.im">
<!-- 以下为需要的基本权限,需要自行添加至您的AndroidManifest文件中 start -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_ADDED" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_CHANGED" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_INSTALL" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_REPLACED" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 这个权限用于访问GPS定位 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- ===================================【通用模块】商城 END====================================== -->
<!-- ===================================【通用模块】音视频聊天 START==================================== -->
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <!-- 百度定位 -->
<uses-permission
android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- Android 9.0中需要此权限的添加,不然崩溃 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".MyApp"
android:allowBackup="true"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="n">
<!--适配华为(huawei)刘海屏-->
<meta-data
android:name="android.notch_support"
android:value="true"/>
<!--适配小米(xiaomi)刘海屏-->
<meta-data
android:name="notch.config"
android:value="portrait|landscape" />
<provider
android:name=".provider.ClarityPotion"
android:authorities="${applicationId}.claritypotion"
android:exported="false"
android:multiprocess="true" />
<receiver android:name=".utils.ntp.BootCompletedBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<service android:name=".im.OffLineMsgService"/>
<activity
android:name=".ui.activity.SplashActivity"
android:configChanges="screenSize|keyboardHidden|orientation"
android:theme="@style/AppTheme.Splash"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.activity.LoginActivity"
android:launchMode="singleTask"
/>
<activity android:name=".ui.activity.MainActivity"
android:launchMode="singleTask"/>
<activity android:name=".ui.activity.SettingActivity"/>
<activity android:name=".ui.activity.UserInfoActivity"/>
<activity android:name=".ui.activity.BigImageActivity"/>
<activity android:name=".ui.activity.GroupListActivity"/>
<activity android:name=".ui.activity.GroupCreateActivity"
android:windowSoftInputMode="adjustPan"/>
<activity android:name=".ui.activity.FriendSearchActivity"/>
<activity android:name=".ui.activity.FriendInfoActivity"/>
<activity android:name=".ui.activity.FriendAddActivity"/>
<activity android:name=".ui.activity.FriendRemarkActivity"/>
<activity android:name=".ui.activity.FriendCircleActivity"/>
<activity android:name=".ui.activity.AccountSafeActivity"/>
<activity android:name=".ui.activity.VerificationActivity"/>
<activity android:name=".ui.activity.ProcessAddFriendActivity"/>
<activity android:name=".ui.activity.AddFriendSureActivity"/>
</application>
</manifest>
\ No newline at end of file
package com.renrenliao.im
import android.content.Context
import androidx.multidex.MultiDexApplication
import com.renrenliao.im.di.appModule
import com.renrenliao.im.im.IMClientManager
import com.renrenliao.im.utils.LogUtils
import com.renrenliao.im.utils.ntp.NtpSync
import com.scwang.smart.refresh.header.ClassicsHeader
import com.scwang.smart.refresh.layout.SmartRefreshLayout
import com.tencent.mmkv.MMKV
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidFileProperties
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.koin.core.logger.Level
import kotlin.properties.Delegates
/**
**@ClassName MyApp
**@Description TODO
**@Author ganzhe
**@Date 2022/5/20 11:51
**/
class MyApp : MultiDexApplication() {
companion object {
var CONTEXT: Context by Delegates.notNull()
//类似java静态代码块
init {
SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, layout ->
layout.setPrimaryColorsId(R.color.transparent, R.color.common_title_bar_slave) //全局设置主题颜色
ClassicsHeader(context)
}
}
}
override fun onCreate() {
super.onCreate()
CONTEXT = applicationContext
initMMKV()
initKoin()
//时间同步
NtpSync.getInstance().initNtp(this)
NtpSync.getInstance().start(true)
//初始化IM
IMClientManager.getInstance(this)
//设置全局的Header构建器
// SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, layout ->
// layout.setPrimaryColorsId(R.color.transparent, R.color.white) //全局设置主题颜色
// ClassicsHeader(context)
// }
}
private fun initMMKV() {
val rootDir = MMKV.initialize(this)
LogUtils.info("mmkv root: $rootDir")
}
private fun initKoin() {
startKoin {
androidLogger(Level.DEBUG)
androidContext(CONTEXT)
androidFileProperties()
modules(appModule)
}
}
}
\ No newline at end of file
package com.renrenliao.im.base
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
/**
*Author:ganZhe
*时间:2020/10/21 7:30 PM
*描述:This is BaseActivity
*/
abstract class BaseActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getLayoutResId())
initActivity()
}
protected open fun initActivity() {
initView()
initData()
}
// override fun attachBaseContext(newBase: Context) {
// //获取我们存储的语言环境 比如 "en","zh","hi",等等
// // val language: String = LanguageUtil.getDefaultLanguage()
//
// // super.attachBaseContext(LanguageUtil.attachBaseContext(newBase,language))
//
// }
/*override fun onResume() {
super.onResume()
val language: String = LanguageUtil.getDefaultLanguage()
LanguageUtil.updateResources(this, language)
}*/
abstract fun getLayoutResId(): Int
abstract fun initView()
abstract fun initData()
}
\ No newline at end of file
package com.renrenliao.im.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
/**
*Author:ganzhe
*时间:2020/10/24 13:36
*描述:This is BaseFragment
*/
abstract class BaseFragment:Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(getLayoutResId(), container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initFragment()
super.onViewCreated(view, savedInstanceState)
}
protected open fun initFragment(){
initView()
initData()
}
abstract fun getLayoutResId(): Int
abstract fun initView()
abstract fun initData()
}
\ No newline at end of file
package com.renrenliao.im.base
import com.renrenliao.im.utils.DialogProgressUtils
/**
*Author:ganZhe
*时间:2020/10/21 5:12 PM
*描述:This is BaseActivity abstract 抽象类或者方法默认是带着的open
* open关键字 与java 中的 final相反:它允许别的类继承这个类。默认情形下,kotlin 中所有的类都是 final ,用来表示他可以被继承。
* 若子类要重写父类中的方法,则需在父类的方法前面加open关键字,然后在子类重写的方法前加override关键字
* lateinit var延迟初始化 只能用来修饰类属性,不能用来修饰局部变量,并且只能用来修饰对象,不能用来修饰基本类型(因为基本类型的属性在类加载后的准备阶段都会被初始化为默认值)
* by lazy要求属性声明为val,即不可变变量,在java中相当于被final修饰。这意味着该变量一旦初始化后就不允许再被修改值了(基本类型是值不能被修改,对象类型是引用不能被修改)。{}内的操作就是返回唯一一次初始化的结果。
* by lazy可以使用于类属性或者局部变量。
*/
abstract class BaseVMActivity<VM : BaseViewModel> : BaseActivity() {
lateinit var mViewModel: VM
// val loadService: LoadService<*> by lazy {
// LoadSir.getDefault().register(this) {
// reLoad()
// }
// }
override fun initActivity() {
mViewModel = initVM()
startObserve()
super.initActivity()
// mViewModel.loadState.observe(this, observer)
mViewModel.dialogLoadingState.observe(this) {
if (it) { // 显示dialogLoading
// DialogLoaingUtils.showDialogLoading()
DialogProgressUtils.getInstance().showProgressDialog(this)
} else {// 隐藏dialogLoading
//DialogLoaingUtils.hideDialogLoading()
DialogProgressUtils.getInstance().dismissProgressDialog()
}
}
}
abstract fun initVM(): VM
abstract fun startObserve()
/**
* 重试加载页面
* 一般重新请求页面初始化数据
*/
open fun reLoad() {}
/**
* 分发应用状态
*/
// private val observer by lazy {
// Observer<LoadState> {
// it?.let {
// when (it.code) {
// LoadStateType.SUCCESS -> showSuccess()
// LoadStateType.LOADING -> showLoading()
// LoadStateType.ERROR -> showError()
// LoadStateType.EMPTY -> showEmpty()
// }
// }
// }
// }
// open fun showLoading() {
// loadService.showCallback(LoadingCallBack::class.java)
// }
//
// open fun showSuccess() {
// loadService.showCallback(SuccessCallback::class.java)
// }
//
// open fun showError() {
// loadService.showCallback(
// ErrorPageCallBack::class.java
// )
// }
//
// open fun showEmpty() {
// loadService.showCallback(EmptyPageCallBack::class.java)
// }
}
\ No newline at end of file
package com.renrenliao.im.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.renrenliao.im.utils.DialogLoaingUtils
/**
*Author:ganzhe
*时间:2020/10/24 14:06
*描述:This is BaseVMFragment
*/
abstract class BaseVMFragment<VM : BaseViewModel> : BaseFragment() {
lateinit var mViewModel: VM
// private lateinit var loadService: LoadService<*>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// mBinding = DataBindingUtil.inflate(inflater, getLayoutResId(), container, false)
val rootView = inflater.inflate(getLayoutResId(), container, false)
// loadService = LoadSir.getDefault().register(rootView) { reLoad() }
return rootView//loadService.loadLayout
}
override fun initFragment() {
mViewModel = initVM()
startObserve()
// showSuccess()//TODO fragment 这里需要初始化页面显示success 否则页面会默认空白
super.initFragment()
// mViewModel.loadState.observe(this, observer)
mViewModel.dialogLoadingState.observe(this) {
it?.let {
if (it) { // 显示dialogLoading
DialogLoaingUtils.showDialogLoading()
} else {// 隐藏dialogLoading
DialogLoaingUtils.hideDialogLoading()
}
}
}
}
abstract fun initVM(): VM
abstract fun startObserve()
/**
* 重试加载页面
* 一般重新请求页面初始化数据
*/
open fun reLoad() {}
/**
* 分发应用状态
*/
// private val observer by lazy {
// Observer<LoadState> {
// it?.let {
// when (it.code) {
// LoadStateType.SUCCESS -> showSuccess()
// LoadStateType.LOADING -> showLoading()
// LoadStateType.ERROR -> showError()
// LoadStateType.EMPTY -> showEmpty()
// }
// }
// }
// }
//
//
// open fun showLoading() {
// loadService.showCallback(LoadingCallBack::class.java)
// }
//
// open fun showSuccess() {
// loadService.showCallback(SuccessCallback::class.java)
// }
//
// open fun showError() {
// loadService.showCallback(
// ErrorPageCallBack::class.java
// )
// }
//
// open fun showEmpty() {
// loadService.showCallback(EmptyPageCallBack::class.java)
// }
}
\ No newline at end of file
package com.renrenliao.im.base
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.renrenliao.im.ext.handleException
import com.renrenliao.im.ext.toast
import com.renrenliao.im.utils.LogUtils
import com.renrenliao.im.utils.RingToast
import com.tencent.mmkv.MMKV
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
abstract class BaseViewModel : ViewModel() {
val kv = MMKV.defaultMMKV()
val dialogLoadingState by lazy { MutableLiveData<Boolean>() }
/**
* 一般用于 同步 请求 并发多个请求 请单独更具自定义的业务写
*/
fun launchUI(
block: suspend CoroutineScope.() -> Unit,
isShowToast: Boolean = false,
isShowDiaLoading:Boolean = false,
) {
viewModelScope.launch {
//各种状态开始
if (isShowDiaLoading){
dialogLoadingState.value = true
}
runCatching {
block()
}.onSuccess {
}.onFailure {
val exception = handleException(it)
LogUtils.error("${exception.errCode} ${exception.errorMsg}")
if (isShowToast) {
// toast(exception.errorMsg)
RingToast.show(exception.errorMsg)
}
}
//各种状态结束
if (isShowDiaLoading){
dialogLoadingState.value = false
}
}
}
}
\ No newline at end of file
package com.renrenliao.im.bean
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import com.renrenliao.im.config.Const
import kotlinx.android.parcel.Parcelize
/**
**@ClassName AppConfigEntity
**@Description
**@Author gan
**@Date 2022/5/24 16:00
**/
@Entity(tableName = "student")
data class StudentEntity(
@PrimaryKey
val id: Int,
val name: String,
val age: Int,
val sex: Boolean
)
@Parcelize
data class LineEntity(
val name: String,
val server: List<LineItem>,
val version: Int
):Parcelable
@Parcelize
data class LineItem(
val apiUrl: String,
val imAddr: String,
val imPort: String,
val name: String,
val uploadUrl: String,
var select:Boolean = false
):Parcelable
/**
* {"withdrawMinAmount":"100","androidAddress":"https://rrim.oss-cn-beijing.aliyuncs.com/renrenliao.apk","useMobileRegister":"0","versionStatus":1,"iosAtt1":"抖一抖","redPackBlessing":"恭喜發財","iosAtt2":"https://apimg.alipay.com/","ob":"1","iosAtt3":"https://ccdcapi.alipay.com/","apiUrl":"http://47.108.176.248","inviteSwitch":"0","imPort":"9903","payAddress":"http://api.renrenim.com/defray/#/?payKey=138625c0fc6e4b28934f3e85fd02526e&vipUserId=10013016","aliyunoss":"https://renrenim.seelinok.com/avatar/","withdrawRate":"0.1","iosAddress":"https://tf.kekouyun.vip/app/1578110577","iosForceUpdateVersion":"100","iosUpdateContent":"当前最新版本:1.0.1。#@1.UI改版#@2.针对部分机型修复了bug#@3.优化消息收发","avatarVersion":"1589530066000","userTerms":"https://im.177.me/legal.html","iosVersion":"100","androidTitle":"系统升级","openAuth":"0","androidUpdateContent":"当前最新版本:1.8.0。#@1.UI改版#@2.针对部分机型修复了bug#@3.优化消息收发","uploadUrl":"http://josn.renrenim.net:100","iosTitle":"人人聊升级","headImgRefreshTime":"86400000","payJump":"0","imAddr":"47.108.176.248"}
**/
@Parcelize
data class AppConfigEntity(
val aliyunoss: String?,
val androidAddress: String,
val androidTitle: String,
val androidUpdateContent: String,
val apiUrl: String,
val avatarVersion: String,
val headImgRefreshTime: String,
val imAddr: String,
val imPort: String,
val inviteSwitch: String,
val iosAddress: String,
val iosAtt1: String,
val iosAtt2: String,
val iosAtt3: String,
val iosForceUpdateVersion: String,
val iosTitle: String,
val iosUpdateContent: String,
val iosVersion: String,
val ob: String,
val openAuth: String,
val payAddress: String,
val payJump: String,
val redPackBlessing: String,
val uploadUrl: String,
val useMobileRegister: String,
val userTerms: String,
val versionStatus: Int,
val withdrawMinAmount: String,
val withdrawRate: String
) : Parcelable
@Parcelize
data class AuthInfo(
val authed_info: UserInfo
) : Parcelable
@Entity(
//表名
tableName = "friend",
//索引
indices = [Index(value = ["account_uid", "userUid"], unique = true)]
)
@Parcelize
data class UserInfo(
@PrimaryKey(autoGenerate = true)
var id: Int ,
var account_uid: String? ="", //自己的uid
var accessToken: String? ="",
var areaCode: String? ="", //+86
var authStatus: Int =0,
var balance: Double =0.0, //1.28
var chatid: String? ="", //test_PCfRB0J 用户聊天的id 后台自动生成
var expiresIn: Int =0, //0
var friendVerification: Int =0, //1
var grade: Int =0, //0
var gId: String? = "",
var invitationCode: String? ="", // 邀请码
var isInsider: Int =0, //0
var isOnline: Int =0, //0
var latestLoginIp: String? ="", //45.195.40.2
var latestLoginTime: String? ="", //2022-05-30 17:52:55
var liveStatus: Int =0, //0
var logoutTime: String? ="", //2022-05-28 20:37:07
var maxFriend: Int =0, //20
var mobile: String? ="", //ganzhe
var nickname: String? ="", //gzc
var payPassword: String? ="",
var registerIp: String ?="", //45.195.40.2
var registerTime: String? ="", //2022-05-13 13:16:57
var token: String? ="",
var totalOnlineSecond: String? ="", //152837995
var userAvatarFileName: String? ="", //https://renrenim.oss-accelerate.aliyuncs.com/avatar/user/10026290/10026290.png"
var userRegieon: String? ="", //香港
var userSex: Int =0, //3
var userStatus: Int =0, //1
var userType: Int =0, //0
var userUid: String ="", //3
var whatsUp: String? ="", //说点什么吧......
var userDesc: String? ="", //你以为还能说啥......
var buddyRemark:String? ="", //备注
var ex1:String? = "",
var ex10:String? = "",
var avatarVersion:Long = 0
) : Parcelable{
@Ignore
constructor():this(id = 0)
@Ignore
var ext11:Boolean = false /** @see com.renrenliao.im.ui.adapter.GroupMemberAdapter 扩展字段*/
fun getRemark():String{
return if (buddyRemark?.isBlank()==true){
nickname.toString()
}else{
buddyRemark.toString()
}
}
fun getAvatarUrl():String{
return Const.COMMON_IMG_USER_FILE + avatarVersion+"/"+avatarVersion+".png"
}
}
data class LoginExtra(
val accessToken: String,
val modleType: String = "android"
) {
constructor(accessToken: String) : this(accessToken, "android")
}
data class BankCardEntity(
val addTime: Long,
val bankAccountAddress: String,
val bankCardAvatar: String,
val bankCardNumber: String,
val bankCardTailNumber: String,
val bankCode: String,
val bankName: String,
val id: Int,
val isNotDel: Int,
val updateTime: Long,
val userName: String,
val userUid: Int
)
//@Entity(
// //表名
// tableName = "avatarTag",
// //索引
// indices = [Index(value = ["account_uid", "uid"], unique = true)]
//)
//data class AvatarTAG(
// @PrimaryKey(autoGenerate = true)
// var id: Int =0 ,
// var account_uid: String="", //自己的uid
// var uid:String="" ,//用户uid
// var version:Long = 0, //头像版本
//){
// @Ignore
// constructor():this(id = 0)
//}
package com.renrenliao.im.bean
class ResultException (var errCode: Int, var errorMsg: String?) : Exception(errorMsg)
package com.renrenliao.im.config;
import com.renrenliao.im.BuildConfig;
import com.renrenliao.im.im.ImMsgBean;
import com.renrenliao.im.model.repository.LocalDataSource;
/**
* *@ClassName Const
* *@Description
* *@Author ganzhe
* *@Date 2022/5/19 16:37
**/
public class Const {
// public static String BASE_URL = "http://8.134.82.56/";
public static String BASE_URL = "http://10.3.3.8/";
// public static String BASE_URL = "http://10.3.0.18/";
public final static String APICODE = "XSUGINROBD4BEQGGY9ARDMX666U7Y08S";//BuildConfig.APIKEY;//api版本标识
public static String BASE_UP_URL = "http://api.renrenim.com:100";
public static String SERVER_LINES = "https://myimconf.oss-cn-shanghai.aliyuncs.com/test/servernode.json";
public static String BASE_IMG_HEAD_URL = "https://renreni.oss-accelerate.aliyuncs.com/avatar/";
public static String COMMON_IMG_USER_FILE = BASE_IMG_HEAD_URL + "user/";//用户头像
public static String COMMON_IMG_GROUP_FILE = BASE_IMG_HEAD_URL + "group/";//群组头像
public static int useMobileRegister = 0;//1 用手机号注册,0不限制
public static String IM_IP = "8.134.82.56";
public static int IM_PORT = 9905;
}
package com.renrenliao.im.db
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.renrenliao.im.bean.UserInfo
import com.renrenliao.im.im.AlarmEntity
import com.renrenliao.im.im.ImMsgBean
import kotlinx.coroutines.flow.Flow
/**
**@ClassName AlarmDao
**@Description
**@Author gan
**@Date 2022/6/17 15:39
**/
@Dao
interface AlarmDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAlarm(alarmEntity: AlarmEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAlarms(alarms:List<AlarmEntity>)
@Query("SELECT * FROM alarm WHERE account_uid = :account_uid")
fun getAlarmsLiveData(account_uid:String): LiveData<List<AlarmEntity>>
@Query("SELECT * FROM alarm WHERE account_uid = :account_uid")
fun getAlarms(account_uid:String): List<AlarmEntity>
@Query("SELECT * FROM alarm WHERE account_uid = :account_uid AND gId = :gId AND msgType = :msgType")
fun getAlarm(account_uid:String,gId:String,msgType:Int): AlarmEntity
@Query("SELECT * FROM alarm WHERE account_uid = :account_uid AND msgType = :msgType")
fun getAlarmListByTypeLiveData(account_uid:String,msgType:Int): LiveData<List<AlarmEntity>>
@Query("delete FROM alarm")
fun deleteAllAlarm()
@Query("delete FROM alarm WHERE account_uid != :account_uid")
fun deleteAlarmsNotCurrentUid(account_uid:String)
@Query("delete FROM alarm WHERE account_uid = :account_uid AND gId = :gId AND msgType = :msgType")
fun deleteAlarm(account_uid:String,gId: String,msgType:Int)
@Query("delete FROM alarm WHERE account_uid = :account_uid AND gId = :gId")
fun deleteAlarm(account_uid:String,gId: String)
//更新置顶状态
@Query("UPDATE alarm SET isTop = :top WHERE account_uid = :account_uid AND tid = :uid AND msgType = :msgType")
fun updateIsTop(account_uid: String,uid: String,msgType:Int,top:Boolean)
//更新已读未读状态
@Query("UPDATE alarm SET num = :num WHERE account_uid = :account_uid AND tid = :uid AND msgType = :msgType")
fun updateReadStatus(account_uid: String,uid: String,msgType:Int,num:Int)
//统计未读消息
@Query("SELECT SUM(num) FROM alarm WHERE account_uid = :account_uid ")
fun countUnRead(account_uid:String):LiveData<Int>
//获取通知列表
@Query("SELECT * FROM alarm WHERE account_uid = :account_uid AND tid = :tid")
fun getAlarms(account_uid:String,tid:String): List<AlarmEntity>
//更新头像版本
@Query("UPDATE alarm SET avatarVersion = :newVersion WHERE account_uid = :account_uid AND tid = :uid")
fun updateAvatarVersion(account_uid: String,uid: String,newVersion:Long)
}
\ No newline at end of file
package com.renrenliao.im.db
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
//import com.renrenliao.im.bean.AvatarTAG
import com.renrenliao.im.bean.UserInfo
import com.renrenliao.im.im.AlarmEntity
import com.renrenliao.im.im.GroupEntity
import com.renrenliao.im.im.ImMsgBean
/**
*Author:ganzhe
*时间:2020/11/9 16:05
*描述:This is AppDatabase
*/
@Database(entities = [UserInfo::class,GroupEntity::class, ImMsgBean::class,AlarmEntity::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
// abstract fun studentDao(): StudentDao
abstract fun friendDao(): FriendDao
abstract fun groupDao(): GroupDao
abstract fun msgDao(): ImMsgDao
abstract fun alarmDao(): AlarmDao
//abstract fun avatarTagDao(): AvatarTagDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase =/*{
if (INSTANCE ==null){
synchronized(this){
if (INSTANCE == null){
INSTANCE = buildDatabase(context)
}
}
}
return INSTANCE!!
}*/
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context, AppDatabase::class.java, "chat.db")
// .allowMainThreadQueries()
.build()
}
}
\ No newline at end of file
package com.renrenliao.im.db
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.renrenliao.im.bean.UserInfo
import com.renrenliao.im.im.AlarmEntity
import com.renrenliao.im.im.ImMsgBean
import kotlinx.coroutines.flow.Flow
/**
**@ClassName AlarmDao
**@Description
**@Author gan
**@Date 2022/6/17 15:39
**/
//@Dao
//interface AvatarTagDao {
// @Insert(onConflict = OnConflictStrategy.REPLACE)
// fun insertAvatarTag(avatarTag: AvatarTAG)
//
// @Insert(onConflict = OnConflictStrategy.REPLACE)
// fun insertAvatarTags(avatarTags: List<AvatarTAG>)
//
// @Query("SELECT * FROM avatarTag WHERE account_uid = :account_uid")
// fun getAvatarsLiveData(account_uid:String): LiveData<List<AvatarTAG>>
//
// @Query("SELECT * FROM avatarTag WHERE account_uid = :account_uid AND uid = :uid")
// fun getAvatar(account_uid:String,uid:String): AvatarTAG
//
// @Query("UPDATE avatarTag SET version = :newVersion WHERE account_uid = :account_uid AND uid = :uid")
// fun updataAvatarVersion(account_uid:String,uid:String,newVersion:Long)
//
// @Query("delete FROM avatarTag")
// fun deleteAllAvatar()
//
//}
\ No newline at end of file
package com.renrenliao.im.db
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.renrenliao.im.bean.UserInfo
import kotlinx.coroutines.flow.Flow
/**
**@ClassName TestDao
**@Description
**@Author gan
**@Date 2022/5/25 14:41
**/
@Dao
interface FriendDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertFriend(friendEntity: UserInfo)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertFriends(friends: List<UserInfo>)
@Query("SELECT * FROM friend WHERE account_uid = :account_uid AND userUid = :uid")
fun getFriend(account_uid:String,uid: String):UserInfo
@Query("SELECT * FROM friend WHERE account_uid = :account_uid")
fun getFriends(account_uid:String):List<UserInfo>
@Query("SELECT * FROM friend WHERE account_uid = :account_uid AND nickname like '%' ||:key|| '%'")
fun getFriendsByKey(account_uid:String,key:String):List<UserInfo>
@Query("SELECT * FROM friend WHERE account_uid = :account_uid")
fun getFriendsLiveData(account_uid:String): Flow<List<UserInfo>>
@Query("UPDATE friend SET userAvatarFileName = :avatarUrl where account_uid = :account_uid and userUid = :userUid")
fun updateFriendAvatar(account_uid:String,userUid:Int,avatarUrl:String)
@Query("SELECT count(*) FROM friend WHERE account_uid = :account_uid AND userUid = :uid")
fun countByUid(account_uid:String,uid:String):Int
@Query("DELETE FROM friend WHERE account_uid = :account_uid AND userUid = :uid")
fun deleteByUid(account_uid:String,uid:String)
//更新备注
@Query("UPDATE friend SET buddyRemark = :buddyRemark where account_uid = :account_uid and userUid = :userUid")
fun updateFriendRemark(account_uid:String,userUid:String,buddyRemark:String)
//设置所有好友离线
@Query("UPDATE friend SET liveStatus = 0 where account_uid = :account_uid")
fun setAllFriendsOffline(account_uid: String)
//设置好友在线离线状态
@Query("UPDATE friend SET liveStatus = :liveStatus where userUid = :uid AND account_uid = :account_uid")
fun setFriendLiveStatus(liveStatus:Int,uid: String,account_uid: String)
//更新头像版本
@Query("UPDATE friend SET avatarVersion = :newVersion WHERE account_uid = :account_uid AND userUid = :uid")
fun updateAvatarVersion(account_uid: String,uid: String,newVersion:Long)
}
\ No newline at end of file
package com.renrenliao.im.db
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.renrenliao.im.im.GroupEntity
import kotlinx.coroutines.flow.Flow
/**
**@ClassName TestDao
**@Description
**@Author gan
**@Date 2022/5/25 14:41
**/
@Dao
interface GroupDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertGroup(group: GroupEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertGroups(groups: List<GroupEntity>)
@Query("SELECT * FROM groups WHERE account_uid = :account_uid")
fun getGroups(account_uid: String): List<GroupEntity>
@Query("SELECT * FROM groups WHERE account_uid = :account_uid")
fun getGroupsLiveData(account_uid: String): Flow<List<GroupEntity>>
// @Query("UPDATE friend SET userAvatarFileName = :avatarUrl where userUid = :userUid")
// fun updateFriendAvatar(userUid:Int,avatarUrl:String)
@Query("delete FROM groups")
fun deleteAllGroup()
}
\ No newline at end of file
package com.renrenliao.im.db
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.renrenliao.im.bean.UserInfo
import com.renrenliao.im.im.ImMsgBean
import kotlinx.coroutines.flow.Flow
/**
**@ClassName ImMsgDao
**@Description
**@Author gan
**@Date 2022/6/17 12:28
**/
@Dao
interface ImMsgDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertMsg(imMsgBean: ImMsgBean)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertMsgs(imMsgs: List<ImMsgBean>)
//更新消息发送状态
@Query("UPDATE message SET sendStatus = :sendStatus where fp = :fp AND account_uid = :account_uid")
fun updateSendStatus(sendStatus:Int,fp: String,account_uid: String)
}
\ No newline at end of file
//package com.renrenliao.im.db
//
//import androidx.room.Dao
//import androidx.room.Insert
//import androidx.room.OnConflictStrategy
//import androidx.room.Query
//import com.renrenliao.im.bean.StudentEntity
//
///**
// **@ClassName TestDao
// **@Description
// **@Author gan
// **@Date 2022/5/25 14:41
// **/
//
//@Dao
//interface StudentDao {
//
// @Insert(onConflict = OnConflictStrategy.REPLACE)
// fun insertStudent(studentEntity: StudentEntity)
//
//
// @Query("SELECT * FROM student")
// fun getStudents():List<StudentEntity>
//
//
////
//// @Query("DELETE FROM student")
//// suspend fun deleteAll()
////
////
//// @Query("UPDATE student SET age = :age WHERE name = :name")
//// suspend fun updateStudent(age: Int,name:String)
//}
\ No newline at end of file
package com.renrenliao.im.di
import com.renrenliao.im.model.repository.LocalDataSource
import com.renrenliao.im.model.repository.RemoteDataSource
import com.renrenliao.im.utils.RingToast
import com.renrenliao.im.viewmodel.*
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val commonModule = module {
// single {
// RetrofitClient.createApi(ApiService::class.java, BASE_URL)
// }
}
val viewModelModule = module {
viewModel{ SplashViewModel(get(),get()) }
viewModel{ LoginViewModel(get(),get()) }
viewModel{ MainViewModel(get(),get())}
viewModel{ AlarmViewModel(get(),get())}
viewModel{ SettingViewModel(get(),get())}
viewModel{ MineViewModel(get())}
viewModel{ UserInfoViewModel(get(),get())}
viewModel{ RrlFriendViewModel(get(),get())}
viewModel{ GroupListViewModel(get(),get())}
viewModel{ GroupMemberViewModel(get(),get())}
viewModel{ FriendSearchViewModel(get())}
viewModel{ FriendInfoViewModel(get(),get())}
viewModel{ FriendAddViewModel(get(),get())}
viewModel{ FriendRemarkViewModel(get(),get())}
viewModel { FriendCircleViewModel(get()) }
viewModel { VerificationViewModel(get(),get()) }
viewModel { ProcessAddFriendViewModel(get(),get()) }
viewModel { AddFriendSureViewModel(get(),get()) }
}
val repositoryModule = module {
single { RemoteDataSource() }
single { LocalDataSource() }
}
val appModule = listOf(commonModule, viewModelModule, repositoryModule)
package com.renrenliao.im.ext
import android.view.View
import com.renrenliao.im.bean.UserInfo
/**
**@ClassName ComExt
**@Description
**@Author gan
**@Date 2022/5/24 17:50
**/
fun View.click(block: () -> Unit) {
setOnClickListener {
block()
}
}
fun View.visible() {
visibility = View.VISIBLE
}
fun View.invisible() {
visibility = View.INVISIBLE
}
fun View.gone() {
visibility = View.GONE
}
fun isMan(userInfo: UserInfo):Boolean{
return userInfo.userSex == 1
}
\ No newline at end of file
package com.renrenliao.im.ext
import com.renrenliao.im.bean.ResultException
import com.renrenliao.im.http.BaseResult
import com.renrenliao.im.http.SealedResult
import com.renrenliao.im.http.StatusCode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import org.json.JSONException
import retrofit2.HttpException
import java.net.ConnectException
import java.net.SocketException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.net.ssl.SSLException
fun handleException(throwable: Throwable): ResultException {
var errorCode: Int
var errorMessage = ""
when (throwable) {
is ConnectException -> {
errorCode = StatusCode.Unknown.code
errorMessage = "连接错误"
}
is SocketException -> {
errorCode = StatusCode.Unknown.code
errorMessage = "网络连接错误"
}
is SocketTimeoutException -> {
errorCode = StatusCode.Unknown.code
errorMessage = "网络超时"
}
is HttpException -> {
errorCode = throwable.code()
when (throwable.code()) {
300, 301, 302, 303, 304, 305, 306, 307 -> "资源重定向"
400 -> errorMessage = "请求语法错误"
401 -> errorMessage = "认证过期"
403 -> errorMessage = "服务器拒绝"
404 -> errorMessage = "未发现资源"
405 -> errorMessage = "405"
500 -> errorMessage = "服务器内部错误"
501 -> errorMessage = "服务器不支持"
502 -> errorMessage = "网关错误"
503 -> errorMessage = "服务器超负载"
504 -> errorMessage = "网关超时"
505 -> errorMessage = "不支持协议"
else -> throwable.message()
}
}
is JSONException -> {
errorCode = StatusCode.Unknown.code
errorMessage = "解析错误"
}
is SSLException -> {
errorCode = StatusCode.Unknown.code
errorMessage = "ssl错误"
}
is UnknownHostException -> {
errorCode = StatusCode.Unknown.code
errorMessage = "网关错误"
}
else -> {
errorCode = StatusCode.Unknown.code
errorMessage = throwable.message.toString()
}
}
return ResultException(errorCode, errorMessage)
}
suspend fun <T : Any> callRequest(call: suspend () -> SealedResult<T>): SealedResult<T> {
return try {
call()
} catch (e: Exception) {
//这里统一处理异常
e.printStackTrace()
SealedResult.Error(handleException(e))
}
}
suspend fun <T : Any> handleResponse(
response: BaseResult<T>,
successBlock: (suspend CoroutineScope.() -> Unit)? = null,
errorBlock: (suspend CoroutineScope.() -> Unit)? = null
): SealedResult<T> {
return coroutineScope {
if (response.code == StatusCode.Ok.code) {
successBlock?.let { it() }
SealedResult.Success (response.data)
} else {
errorBlock?.let { it() }
SealedResult.Error(ResultException(response.code, response.msg))
}
}
}
package com.renrenliao.im.ext
import android.content.Context
import android.widget.Toast
import androidx.annotation.StringRes
import com.renrenliao.im.provider.ClarityPotion
fun Any.toast(content: String?, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(ClarityPotion.clarityPotion, "", duration).apply {
setText(content?:"")
show()
}
}
fun Context.toast(content: String, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(ClarityPotion.clarityPotion,"", duration).apply {
setText(content)
show()
}
}
fun Context.toast(@StringRes id: Int, duration: Int = Toast.LENGTH_SHORT) {
toast(getString(id), duration)
}
fun Context.longToast(content: String) {
toast(content, Toast.LENGTH_LONG)
}
fun Context.longToast(@StringRes id: Int) {
toast(id, Toast.LENGTH_LONG)
}
fun Any.toast(context: Context, content: String, duration: Int = Toast.LENGTH_SHORT) {
context.toast(content, duration)
}
fun Any.toast(context: Context, @StringRes id: Int, duration: Int=Toast.LENGTH_SHORT) {
context.toast(id, duration)
}
fun Any.longToast(context: Context, content: String) {
context.longToast(content)
}
fun Any.longToast(context: Context, @StringRes id: Int) {
context.longToast(id)
}
package com.renrenliao.im.http
import com.google.gson.annotations.SerializedName
class BaseResult<T> (
@SerializedName("status")
val code: Int,
val msg:String?,
var data:T?,
var oK:Boolean?
)
package com.renrenliao.im.http;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.OkHttpClient;
/**
* Author:ganzhe
* 时间:2021/2/20 00:59
* 描述:This is CertUtil
*/
public class CertUtil {
public static X509TrustManager getX509(){
X509TrustManager x509TrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] x509Certificates = new X509Certificate[0];
return x509Certificates;
}
};
return x509TrustManager;
}
public static SSLSocketFactory getSSlSocketFactory() {
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{getX509()}, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return sslContext.getSocketFactory();
}
public static HostnameVerifier getVertifer() {
HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
return DO_NOT_VERIFY;
}
/**
* 设置https 访问的时候对所有证书都进行信任
*
* @throws Exception
*/
public static OkHttpClient getSSLOkHttpClient() throws Exception {
final X509TrustManager trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
return new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})
.build();
}
}
package com.renrenliao.im.http
import com.renrenliao.im.utils.DataCacheUtils
import com.renrenliao.im.utils.MDateUtils
import com.renrenliao.im.utils.MapUtils
import okhttp3.Interceptor
import okhttp3.Response
class HeaderInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val orignrequest = chain.request()
val builder = orignrequest.newBuilder()
val time = MDateUtils.currentTimMillis().toString()//System.currentTimeMillis().toString()
builder.header("Accept-Language", "zh-CN")
builder.header("time", time)
builder.header("accessToken",DataCacheUtils.getToken())
builder.header("userUid", DataCacheUtils.getUid())
builder.header("secret", DataCacheUtils.getSecret(time))
builder.header("deviceId", DataCacheUtils.getDeviceId())
//添加固定头部
MapUtils.getHttpHeader().entries.forEach {
builder.header(it.key,it.value)
}
// for (Map.Entry<String, String> entry : MapU.entrySet()) {
// builder.header(entry.getKey(), entry.getValue());
// }
//todo 添加头部header 一般情况下 if isLogin 从kv中获取token .addHeader("token",kv.decodeString(KEY_TOKEN,""))
//从kv读取cookie cookie存储的方案二
/* val cookies = kv.decodeStringSet(KEY_COOKIE, emptySet())
for (cookie in cookies) {
builder.addHeader("Cookie", cookie)
}*/
val build = builder.build()
return chain.proceed(build)
}
}
\ No newline at end of file
package com.renrenliao.im.http
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import okio.Buffer
import java.nio.charset.Charset
class HttpLogInterceptor : Interceptor {
companion object {
private val UTF8 = Charset.forName("UTF-8")
}
// private val kv: MMKV = MMKV.defaultMMKV()
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val requestBody = request.body
val responseBody = response.body
val responseHeader = response.headers
val responseBodyString = responseBody?.string()
var requestMessage: String = request.method + ' '.toString() + request.url
if (requestBody != null) {
val buffer = Buffer()
requestBody.writeTo(buffer)
requestMessage += "?\n" + buffer.readString(UTF8)
}
/**
* 存储返回的token
*/
/* val token = responseHeader["token"]
if (!TextUtils.isEmpty(token)){
kv.encode(KEY_TOKEN,token)
}*/
//存储cookie 方案二
/*if (response.headers("Set-Cookie").isNotEmpty()) {
val cookies = HashSet<String>()
for (header in response.headers("Set-Cookie")) {
cookies.add(header)
}
kv.encode(Constant.KEY_COOKIE, cookies)
}*/
println("RequestLog >> $requestMessage"+"\n"+request.headers)
println("ResponseLog << "+ request.method + ' '.toString() + request.url + ' '.toString() + responseHeader + ' '.toString() + responseBodyString)
// val entity = GsonHelper.convertEntity(responseBodyString)
// if (responseBodyString!=null){
// val baseBean = GsonUtils.convertBaseBean(responseBodyString)
// if (baseBean.code==200){
// val desData = Des3Util.decode(baseBean.data)
// println("httplog<<$desData")
// }
// }
return response.newBuilder()
.body(responseBodyString?.toByteArray()?.toResponseBody(responseBody.contentType()))
.build()
}
}
\ No newline at end of file
package com.renrenliao.im.http;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
/**
* *@ClassName MyGsonResponseBodyConverter
* *@Description
* *@Author gan
* *@Date 2022/5/25 11:23
**/
public class MyGsonConverterFactory extends Converter.Factory{
public static MyGsonConverterFactory create() {
return create(new Gson());
}
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static MyGsonConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new MyGsonConverterFactory(gson);
}
private final Gson gson;
private MyGsonConverterFactory(Gson gson) {
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new MyGsonResponseBodyConverter(gson,adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(
Type type,
Annotation[] parameterAnnotations,
Annotation[] methodAnnotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new MyGsonRequestBodyConverter<>(gson, adapter);
}
}
package com.renrenliao.im.http;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.Buffer;
import retrofit2.Converter;
/**
* *@ClassName MyGsonRequestBodyConverter
* *@Description
* *@Author gan
* *@Date 2022/5/25 16:38
**/
public class MyGsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
MyGsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
\ No newline at end of file
package com.renrenliao.im.http;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.TypeAdapter;
import com.renrenliao.im.bean.AppConfigEntity;
import com.renrenliao.im.bean.LineEntity;
import com.renrenliao.im.provider.ClarityPotion;
import com.renrenliao.im.ui.activity.SplashActivity;
import com.renrenliao.im.utils.DataCacheUtils;
import com.renrenliao.im.utils.Des3Util;
import com.renrenliao.im.utils.RouteUtils;
import com.renrenliao.im.utils.StringUtils;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import org.apache.commons.lang3.StringEscapeUtils;
import org.json.JSONException;
import org.json.JSONObject;
/**
* *@ClassName MyGsonResponseBodyConverter
* *@Description
* *@Author gan
* *@Date 2022/5/25 11:39
**/
public class MyGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private static final String TAG = MyGsonResponseBodyConverter.class.getSimpleName();
private final Gson gson;
private final TypeAdapter<T> adapter;
MyGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody responseBody) throws IOException {
String strResponse = responseBody.string();
Log.i(TAG, "返回的response:" + strResponse);
BaseResult<String> baseResult = gson.fromJson(strResponse, BaseResult.class);
BaseResult<Object> bO = new BaseResult<Object>(baseResult.getCode(), baseResult.getMsg(),null,baseResult.getOK());
// Log.i(TAG, "返回的baseResult:" + gson.toJson(baseResult));
//5001 登录信息已过期,请重新登录 500 参数校验不通过
if (baseResult.getCode() == 5001){
DataCacheUtils.isLogin = false;
DataCacheUtils.INSTANCE.clearUserInfo();
RouteUtils.INSTANCE.exitAndLogin();
}
//解密操作
String decryptString;
if (baseResult.getData() != null) {
try {
decryptString = Des3Util.decode(baseResult.getData());
Log.i(TAG, "解密data: " + decryptString);
baseResult.setData(decryptString);
Object object = gson.fromJson(decryptString,Object.class);
Log.i(TAG, "替换的baseResult:" + gson.toJson(baseResult));
bO.setData(object);
// bO = new BaseResult<Object>(baseResult.getCode(), baseResult.getMsg(), object,baseResult.getOK());
// Log.i(TAG, "替换的baseObject:" + gson.toJson(bO));
} catch (Exception e) {
e.printStackTrace();
}
}
try{
//判断是否是线路配置文件返回
if (baseResult.getMsg()==null&&baseResult.getData()==null&&baseResult.getOK()==null){
LineEntity lineEntity = gson.fromJson(strResponse,LineEntity.class);
if (!StringUtils.isEmpty(lineEntity.getName())){
return adapter.fromJson(strResponse);
}
}
//判断是否是配置entity 因为 "iosUpdateContent":"[修复]1、修复闪退#@2、修复部分手机流畅度", 不号过滤
if (ClarityPotion.Companion.currentActivity() instanceof SplashActivity){
AppConfigEntity appConfigEntity = gson.fromJson(baseResult.getData(),AppConfigEntity.class);
if (!StringUtils.isEmpty(appConfigEntity.getWithdrawMinAmount())){
return adapter.fromJson(gson.toJson(bO));
}
}
String json = gson.toJson(baseResult);
json = StringEscapeUtils.unescapeJava(json);
json = json.replace("\"{","{");
json = json.replace("}\"","}");
json = json.replace("\"[{","[{");
json = json.replace("}]\"","}]");
json = json.replace("\"[","[");
json = json.replace("]\"","]");
Log.i(TAG, "xx:" + json);
return adapter.fromJson(json);
// return adapter.fromJson(gson.toJson(bO));
}finally {
responseBody.close();
}
}
// /**
// * String转Reader
// *
// * @param json
// * @return
// */
// private Reader StringToReader(String json) {
// Reader reader = new StringReader(json);
// return reader;
// }
}
\ No newline at end of file
package com.renrenliao.im.http
//import com.combodia.httplib.ext.CertUtil
//import com.combodia.httplib.interceptor.HeaderInterceptor
//import com.combodia.httplib.interceptor.HttpLogInterceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.net.Proxy
import java.util.concurrent.TimeUnit
class RetrofitClient {
private val mOkClient: OkHttpClient = OkHttpClient.Builder()
/* .connectionSpecs(
listOf(
ConnectionSpec.MODERN_TLS,
ConnectionSpec.COMPATIBLE_TLS,
ConnectionSpec.CLEARTEXT
)
)*/
/* .certificatePinner(
CertificatePinner.Builder()
.add("wananzhuo.com", "sha256/ldOdVOULMNoOhvk8HQr5iJB7N0d8fHYw2dR8=")
.build())*/
// .sslSocketFactory(CertUtil.getSSlSocketFactory(), CertUtil.getX509())
// .hostnameVerifier(CertUtil.getVertifer())
.addInterceptor(HeaderInterceptor())
.addInterceptor(HttpLogInterceptor())
.readTimeout(READ_TIMEOUT.toLong(), TimeUnit.SECONDS)//设置读取超时时间
.writeTimeout(WRITE_TIMEOUT.toLong(), TimeUnit.SECONDS)//设置写的超时时间
.proxy(Proxy.NO_PROXY)
.connectTimeout(CONNECT_TIMEOUT.toLong(), TimeUnit.SECONDS)//设置连接超时时间
// .retryOnConnectionFailure(false) //连接失败不重试
// .cache(cache)
// .cookieJar(CookieManger(clarityPotion))
.build()
init {
// Subject wanandroid.com
// Fingerprint SHA256: f25a78babca3ce0c9b3636e598c4384c1329ff2cf5b219f71721ec4ef81cec3d
// Pin SHA256: a2bAjA/ldOdVOULMNoOhvk8HQr5iJB7N0d8fHYw2dR8=
/* val httpCacheDirectory =
File(clarityPotion.externalCacheDir, "NetworkCache")
val cacheSize = 50 * 1024 * 1024 // 50 MB
val cache = Cache(httpCacheDirectory, cacheSize.toLong())*/
}
private fun <T> createApi(tClass: Class<T>, baseUrl: String): T {
return Retrofit.Builder()
.client(mOkClient)
.baseUrl(baseUrl)
// .addConverterFactory(NullOnEmptyConverterFactory())
.addConverterFactory(MyGsonConverterFactory.create())
// .addConverterFactory(GsonConverterFactory.create())
.build()
.create(tClass)
}
companion object {
private const val CONNECT_TIMEOUT = 10
private const val READ_TIMEOUT = 30
private const val WRITE_TIMEOUT = 30
private val INSTANT: RetrofitClient by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { RetrofitClient() }
fun get(): RetrofitClient {
return INSTANT
}
fun <T> createApi(tClass: Class<T>, baseUrl: String): T {
return lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
get().createApi(tClass, baseUrl)
}.value
}
}
}
\ No newline at end of file
package com.renrenliao.im.http
import com.renrenliao.im.bean.ResultException
sealed class SealedResult<out T : Any> {
data class Success<out T : Any>(val data: T?) : SealedResult<T>()
data class Error(val exception: ResultException) : SealedResult<Nothing>()
}
inline fun <T : Any> SealedResult<T>.checkSuccess(success: (T) -> Unit) {
if (this is SealedResult.Success) {
success(data!!)
}
}
inline fun <T:Any>SealedResult<T>.checkError(error: (ResultException) -> Unit) {
if (this is SealedResult.Error) {
error(exception)
}
}
inline fun <T:Any> BaseResult<T>.checkSuccess(success: (T) -> Unit){
if (this.code == StatusCode.Ok.code){
success(data!!)
}
}
inline fun <T:Any> BaseResult<T>.checkError(error: (ResultException) -> Unit) {
if (this.code != StatusCode.Ok.code) {
error(ResultException(code,msg))
}
}
package com.renrenliao.im.http
enum class StatusCode(val code: Int) {
Unknown(0),
Ok(200),
NotFound(404),
BadGateWay(502)
}
\ No newline at end of file
package com.renrenliao.im.im
import com.renrenliao.im.bean.UserInfo
import com.renrenliao.im.utils.DataCacheUtils
import com.renrenliao.im.utils.LogUtils
import com.renrenliao.im.utils.MDateUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
object AlarmHelper {
fun createAddFriendAlarm(friend: UserInfo):AlarmEntity{
val alarmEntity = AlarmEntity()
alarmEntity.account_uid = DataCacheUtils.getUid()
alarmEntity.msgType = AlarmType.ADDFRIENDREQUEST.value
alarmEntity.name = "确认提醒"
alarmEntity.msg = "${friend.getRemark()} 邀请您成为好友"
var reqTime = if (friend.ex10 ==null || friend.ex10?.isBlank() == true){
MDateUtils.currentTimMillis()
}else{
friend.ex10?.toLong()!!
}
alarmEntity.time = reqTime
alarmEntity.num = 1
alarmEntity.setSingleChatInfo(friend)
return alarmEntity
}
fun createSingleAlarm(friend: UserInfo):AlarmEntity{
val alarmEntity = AlarmEntity()
alarmEntity.edit = ""
alarmEntity.account_uid = DataCacheUtils.getUid()
alarmEntity.msgType = AlarmType.REVICEMESSAGE.value
alarmEntity.name = friend.getRemark()
alarmEntity.msg = friend.getRemark()+"已是你的好友了,点击开始聊天吧..."
alarmEntity.time = MDateUtils.currentTimMillis()
alarmEntity.num = 0
alarmEntity.setSingleChatInfo(friend)
return alarmEntity
}
}
\ No newline at end of file
/*
* Copyright (C) 2021 即时通讯网(52im.net) & Jack Jiang.
* The MobileIMSDK_UDP (MobileIMSDK v6.x UDP版) Project.
* All rights reserved.
*
* > Github地址:https://github.com/JackJiang2011/MobileIMSDK
* > 文档地址: http://www.52im.net/forum-89-1.html
* > 技术社区: http://www.52im.net/
* > 技术交流群:215477170 (http://www.52im.net/topic-qqgroup.html)
* > 作者公众号:“即时通讯技术圈】”,欢迎关注!
* > 联系作者: http://www.52im.net/thread-2792-1-1.html
*
* "即时通讯网(52im.net) - 即时通讯开发者社区!" 推荐开源工程。
*
* IMClientManager.java at 2021-7-2 23:04:01, code by Jack Jiang.
*/
package com.renrenliao.im.im;
import android.content.Context;
import net.openmob.mobileimsdk.android.ClientCoreSDK;
/**
* MobileIMSDK的管理类。
* 正式的APP项目中,建议在Application中管理本类,确保SDK的生命周期同步于整个APP的生命周期。
*
* @author Jack Jiang(http://www.52im.net/thread-2792-1-1.html)
*/
public class IMClientManager {
private static String TAG = IMClientManager.class.getSimpleName();
private static IMClientManager instance = null;
private Context context = null;
/**
* MobileIMSDK的基础通信消息的回调事件实现类(回调事件可以是:登陆成功事件 通知、掉线事件通知等)。
* <p>
* 通过 ClientCoreSDK.setChatBaseEvent(..) 方法设置之,可实现回调事件的通知和处理。
*/
private IMEventListener imEventListener = null;
/**
* MobileIMSDK的通用数据通信消息的回调事件接口(回调事件可以是:收到聊天数据事件 通知、服务端返回的
* 错误信息事件通知等)。
* <p>
* 通过 ClientCoreSDK.setChatTransDataEvent(..) 方法设置之,可实现回调事件的通知和处理。
*/
// private ChatTransDataEventListener transDataListener = null;
/**
* MobileIMSDK的QoS质量保证机制的回调事件实现类。
* <p>
* 通过 ClientCoreSDK.setMessageQoSEvent(..) 方法设置之,可实现消息已被收到或未成功送出的通知和处理。
*/
// private ChatQoSEventListener chatQoSEventListener = null;
/* MobileIMSDK是否已被初始化. true表示已初化完成,否则未初始化. */
// private boolean _init = false;
public static IMClientManager getInstance(Context context) {
if (instance == null)
synchronized (IMClientManager.class) {
if (instance == null) {
instance = new IMClientManager(context);
}
}
return instance;
}
private IMClientManager(Context context) {
this.context = context;
initMobileIMSDK();
}
/**
* MobileIMSDK的初始化方法。正式的APP项目中,建议本方法在Application的子类中调用。
*/
public void initMobileIMSDK() {
// if (!this._init) {
// 设置AppKey
// ConfigEntity.appKey = "5418023dfd98c579b6001741";
// 设置服务器ip和服务器端口
// IMConfigEntity.serverIP = Const.IM_IP;
// IMConfigEntity.serverPort = Const.IM_PORT;
// MobileIMSDK核心IM框架的敏感度模式设置
// ConfigEntity.setSenseMode(SenseMode.MODE_10S);
// 开启/关闭DEBUG信息输出
// ClientCoreSDK.DEBUG = false;
// 【特别注意】请确保首先进行核心库的初始化(这是不同于iOS和Java端的地方)
imEventListener = new IMEventListener();
// transDataListener = new ChatTransDataEventListener(context);
// chatQoSEventListener = new ChatQoSEventListener(context);
ClientCoreSDK.getInstance().init(this.context);
ClientCoreSDK.getInstance().setImEvent(imEventListener);
// ClientCoreSDK.getInstance().setChatTransDataEvent(transDataListener);
// ClientCoreSDK.getInstance().setMessageQoSEvent(chatQoSEventListener);
// 设置事件回调
// chatMessageListener = new ChatMessageEventImpl();
// messageQoSListener = new MessageQoSEventImpl();
// ClientCoreSDK.getInstance().setChatBaseEvent(chatBaseListener);
// ClientCoreSDK.getInstance().setChatMessageEvent(chatMessageListener);
// ClientCoreSDK.getInstance().setMessageQoSEvent(messageQoSListener);
// }
}
public void release() {
ClientCoreSDK.getInstance().release();
}
/**
* 重置init标识。
* <p>
* <b>重要说明:</b>不退出APP的情况下,重新登陆时记得调用一下本方法,不然再
* 次调用 {@link #initMobileIMSDK()} 时也不会重新初始化MobileIMSDK(
* 详见 {@link #initMobileIMSDK()}代码)而报 code=203错误!
*/
// public void resetInitFlag() {
// _init = false;
// }
}
package com.renrenliao.im.im
import com.renrenliao.im.bean.UserInfo
import com.renrenliao.im.http.checkSuccess
import com.renrenliao.im.model.repository.LocalDataSource
import com.renrenliao.im.model.repository.RemoteDataSource
import com.renrenliao.im.utils.DataCacheUtils
import com.renrenliao.im.utils.LogUtils
import com.renrenliao.im.utils.StringUtils
import kotlinx.coroutines.*
import net.openmob.mobileimsdk.android.ClientCoreSDK
import net.openmob.mobileimsdk.android.event.IMEvent
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
/**
**@ClassName ChatBaseEventListener
**@Description
**@Author gan
**@Date 2022/6/16 14:37
* 与IM服务器的底层连接事件在此ChatBaseEvent子类中实现即可。
* <p>
* 本类是MobileIMSDK的基础通信消息的回调事件接口实现类(将接收如:登陆成功事件 通知、掉线事件通知等)。
* 数据接收都在子线程 UDP_DATA_RECIVER_TASK中
**/
class IMEventListener : IMEvent, KoinComponent {
//注入RemoteDataSource单例
val remoteDataSource by inject<RemoteDataSource>()
//LocalDataSource单例
val localDataSource by inject<LocalDataSource>()
/**
* * 与MobileIMSDK框架服务端的通信断开的回调事件通知。
* <p>
* 该消息只有在客户端连接服务器成功之后网络异常中断之时触发。 导致与与服务端的通信断开的原
* 因有(但不限于):无线网络信号不稳定、WiFi与2G/3G/4G等同开情 况下的网络切换、手机系统的省电策略等。
*
*@param dwErrorCode 本回调参数表示表示连接断开的原因,目前错误码没有太多意义,仅作保留字段,目前通常为-1
*/
@OptIn(DelicateCoroutinesApi::class)
override fun onLinkClose(dwErrorCode: Int) {
LogUtils.info("IM服务器连接已断开,error:$dwErrorCode")
// 通知全局网络状态livedata 离线
LocalDataSource.networkStatusLiveData.postValue(ClientCoreSDK.IM_STATE_OFFLINE)
//设置所有好友为离线状态
GlobalScope.launch(Dispatchers.IO) {
localDataSource.setAllFriendsOffline()
}
}
/**
* 连接中
*/
override fun onLinKing(paramInt: Int) {
LogUtils.error("连接中... $paramInt")
// 通知全局网络状态livedata 连接中
LocalDataSource.networkStatusLiveData.postValue(ClientCoreSDK.IM_STATE_CONNECTING)
}
/**
* 本地用户的MobileIMSDK框架登陆/重连结果回调事件通知。
*
* @param dwErrorCode 服务端反馈的登录结果:0 表示登陆/重连成功,否则为服务端自定义的出错代码(按照约定通常为>=1025的数)
*/
//num 置 1 防止重连后 一直加1
@OptIn(DelicateCoroutinesApi::class)
override fun onLoginResponse(dwErrorCode: Int, fp: String?) {
LogUtils.error("IM服务器连接成功!服务端响应码=$dwErrorCode fp= $fp")
// 通知全局网络状态livedata 已连接
LocalDataSource.networkStatusLiveData.postValue(ClientCoreSDK.IM_STATE_ONLINE)
//获取未读加好友请求数(包括好友发请求时我不在线的情况),并尝试在首页“消息”里放入一条验证消息的表格item
GlobalScope.launch(Dispatchers.Main) {
val result = withContext(Dispatchers.IO) {
remoteDataSource.getOfflineFriend()
}
result.checkSuccess {users ->
LogUtils.error("好友请求加好友 $users")
users.forEach {
withContext(Dispatchers.IO){
val alarmEntity = AlarmHelper.createAddFriendAlarm(it)
//保存在数据库中
localDataSource.insertAlarm(alarmEntity)
//更新头像版本
val newVersion = StringUtils.getAvatarVersion(it.userAvatarFileName)
localDataSource.updateAvatarVersion(account_uid = DataCacheUtils.getUid(), uid = it.userUid,newVersion=newVersion)
}
}
}
}
}
/**
* 消息未送达的回调事件
* 发生场景:比如用户刚发完消息但网络已经断掉了的情况下,表现形式:就像手机qq或微信一样
* 消息气泡边上会出现红色图标以示没有发送成功).
* @param lostMessages
*/
@OptIn(DelicateCoroutinesApi::class)
override fun messagesLost(lostMessages: List<ImMsgBean>) {
LogUtils.error("收到系统的未实时送达消息通知,当前共有 ${lostMessages.size} 个消息未发送" )
//播放一个消息丢失的声音
//更新消息状态为发送失败
GlobalScope.launch(Dispatchers.IO) {
lostMessages.forEach {
localDataSource.updateSendStatus(
sendStatus = ImMsgBean.SendStatus.sendFaild,
fp = it.fp
)
}
}
}
/**
*消息已被对方收到的回调事件
* @param fingerPrint
*/
@OptIn(DelicateCoroutinesApi::class)
override fun messagesReceived(fingerPrint: String) {
LogUtils.error("消息已经被用户收到--------$fingerPrint")
//将数据库中的消息发送状态更新为 已接收
GlobalScope.launch(Dispatchers.IO) {
localDataSource.updateSendStatus(
sendStatus = ImMsgBean.SendStatus.beReceived,
fp = fingerPrint
)
}
}
/**
* 好友上线通知事件
* @param uid
*/
@OptIn(DelicateCoroutinesApi::class)
override fun FriendOnline(uid: String) {
LogUtils.error("好友上线:$uid")
//设置好友数据库中的状态为上线 1
GlobalScope.launch(Dispatchers.IO) {
localDataSource.setFriendLiveStatus(liveStatus = 1, uid = uid)
}
}
/**
* 好友下线通知事件
* @param uid
*/
@OptIn(DelicateCoroutinesApi::class)
override fun FriendOffline(uid: String) {
LogUtils.error("好友下线:$uid")
//设置好友数据库中的状态为上线 0
GlobalScope.launch(Dispatchers.IO) {
localDataSource.setFriendLiveStatus(liveStatus = 0, uid = uid)
}
}
//num + 1 累加
@OptIn(DelicateCoroutinesApi::class)
override fun FriendAddReq(imMsgBean: ImMsgBean) {
LogUtils.error("收到好友添加请求 $imMsgBean")
//展示一个通知 fid 发起好友请求那个人的userId
GlobalScope.launch(Dispatchers.IO){
//首页的表中添加或者更新
val userInfo = UserInfo()
userInfo.userUid = imMsgBean.fid.toString()
userInfo.buddyRemark = imMsgBean.name
userInfo.gId = imMsgBean.gId
//本地alarm
val localAlarm = localDataSource.getAlarm(userInfo.gId.toString(),AlarmType.ADDFRIENDREQUEST.value)
val alarmEntity = AlarmHelper.createAddFriendAlarm(userInfo)
if (localAlarm !=null){
alarmEntity.num = localAlarm.num + 1
}
//保存在数据库中
localDataSource.insertAlarm(alarmEntity)
//更新头像
if (imMsgBean.avatar !=null){
val account_uid = DataCacheUtils.getUid()
val uid = imMsgBean.fid.toString()
val newVersion = imMsgBean.avatar?.toLong()
localDataSource.updateAvatarVersion(account_uid,uid,newVersion!!)
}
}
}
}
\ No newline at end of file
package com.renrenliao.im.im
/**
**@ClassName ImEnumType
**@Description
**@Author gan
**@Date 2022/6/17 14:41
**/
enum class MsgType(val value: Int,val mark: String){
SIGNIN(0,"用户登陆"),
ONLINE_NOTIFY(1,"用户上线通知"),
OFFLINE_NOTIFY(2,"用户下线通知"),
ACK_NOTIFY(3,"收到回执"),
KEEPALIVE(4,"心跳"),
TEXT(5,"文本消息"),
IMAGE(6,"图片消息"),
REDPACKET(7,"红包"),
LOGOUT(13,"退出登录"),
RECALL(14,"撤回消息")
}
enum class AlarmType(val value:Int,val mark:String){
UNDEFINE(0,"undefine"),
ADDFRIENDREQUEST(1,"添加好友请求"),
ADDFRIENDBEREJECT(2,"加好友被拒绝"),
REVICEMESSAGE(4,"收到的好友聊天消息"),
SYSTEMDEVTEAM(6,"Help"),
SYSTEMQNA(7,"Q&A"),
TEMPCHATMESSAGE(8,"临时聊天"),
GROUPCHATMESSAGE(9,"群聊")
}
enum class RouteType(val value: Int,val mark: String){
SIGNIN(0,"登录"),
KEEPALIVE(1,"心跳"),
NORMAL(2,"普通消息"),
LOGOUT(3,"退出登录"),
ACK(4,"qos应答")
}
enum class SignInType(val value: Int,val mark:String){
SUCCEED(200,"成功"),
ERROR(400,"失败"),
REPEAT(401,"重复登陆"),
KICKOFF(402,"踢下线")
}
enum class ChatType(val value: Int,val mark: String){
SINGLE(0,"单聊"),
TEMP(1,"临时聊天"),
GROUP(2,"群聊"),
}
\ No newline at end of file
package com.renrenliao.im.im;
import android.util.Log;
import com.google.gson.Gson;
import com.renrenliao.im.model.repository.LocalDataSource;
import com.renrenliao.im.utils.CharsetUtils;
import com.renrenliao.im.utils.DataCacheUtils;
import com.renrenliao.im.utils.MDateUtils;
import net.openmob.mobileimsdk.server.s.PErrorResponse;
public class ImMsgFactory {
private static String create(Object c) {
return (new Gson()).toJson(c);
}
public static <T> T parse(byte[] fullProtocalJASOnBytes, int len, Class<T> clazz) {
return parse(CharsetUtils.getString(fullProtocalJASOnBytes, len), clazz);
}
public static <T> T parse(String dataContentOfProtocal, Class<T> clazz) {
return (new Gson()).fromJson(dataContentOfProtocal, clazz);
}
public static ImMsgBean parse(byte[] fullProtocalJASOnBytes, int len) {
return (ImMsgBean) parse(fullProtocalJASOnBytes, len, ImMsgBean.class);
}
public static ImMsgBean createRecivedBack(String from_user_id, String to_user_id, String recievedMessageFingerPrint) {
ImMsgBean p = new ImMsgBean();
// p.setFid(from_user_id);
p.setTid(to_user_id);
// p.setBot(false);
p.setAck(recievedMessageFingerPrint);
p.setRoute(RouteType.ACK.getValue() + "");
return p;
}
// public static ImMsgBean createCommonData(String dataContent, String from_user_id, String to_user_id, boolean QoS, String fingerPrint, int typeu) {
// ImMsgBean p = new ImMsgBean();
// p.setFid(from_user_id);
// p.setTid(to_user_id);
// p.setFp(fingerPrint);
// p.setMsgType(typeu);
// p.setTxt(dataContent);
// return p;
// }
public static ImMsgBean createLoginMessage() {
ImMsgBean message = new ImMsgBean();
message.setFid("" + DataCacheUtils.INSTANCE.getUid());
message.setDevice("android");
message.setToken(DataCacheUtils.INSTANCE.getToken());
message.setTid("0");
message.setRoute(RouteType.SIGNIN.getValue() + "");
return message;
}
public static ImMsgBean createPLogoutInfo(String user_id) {
ImMsgBean p = new ImMsgBean();
p.setFid(user_id);
p.setTid("0");
p.setTime(MDateUtils.currentTimMillis());
//退出登录type
p.setMsgType(MsgType.LOGOUT.getValue());
p.setRoute(RouteType.LOGOUT.getValue()+"");
return p;
}
public static ImMsgBean createPKeepAlive(String from_user_id) {
ImMsgBean p = new ImMsgBean();
p.setRoute(RouteType.KEEPALIVE.getValue() + "");
p.setTid("0");
Log.i("sendKeepAlive", " " + p.toJSONString());
return p;
}
public static PErrorResponse parsePErrorResponse(String dataContentOfProtocal) {
return (PErrorResponse) parse(dataContentOfProtocal, PErrorResponse.class);
}
public static ImMsgBean createTextMessage(String userName, String avatar, String tid, String msg, String fp, long time) {
ImMsgBean p = new ImMsgBean();
p.setFid(DataCacheUtils.INSTANCE.getUid());
p.setName(userName);
p.setBot(false);
p.setAvatar(avatar);
p.setTid(tid);
p.setRoute(RouteType.NORMAL.getValue() + "");
p.setTxt(msg);
p.setFp(fp);
p.setTime(time);
p.setMsgType(MsgType.TEXT.getValue());
return p;
}
}
package com.renrenliao.im.im
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.renrenliao.im.http.checkSuccess
import com.renrenliao.im.model.repository.RemoteDataSource
import com.renrenliao.im.utils.LogUtils
import com.renrenliao.im.utils.MapUtils
import com.renrenliao.im.utils.RxTimerUtils
import kotlinx.coroutines.*
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
/**
**@ClassName OffLineService
**@Description
**@Author gan
**@Date 2022/6/7 14:57
**/
class OffLineMsgService :Service(),KoinComponent{
val remoteDataSource by inject<RemoteDataSource>()
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
LogUtils.error("OffLineMsgService onCreate")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
//return super.onStartCommand(intent, flags, startId)
RxTimerUtils.interval(20000){
getOffLineMsg()
}
return START_STICKY
}
@OptIn(DelicateCoroutinesApi::class)
private fun getOffLineMsg()
{
val map = MapUtils.getComParam(HashMap<String,String>())
GlobalScope.launch{
//请求离线消息
val result = withContext(Dispatchers.IO){
// remoteDataSource.getFriendList(map)
}
LogUtils.error("请求离线消息 $result")
//写入数据库
withContext(Dispatchers.IO){
}
}
}
override fun onDestroy() {
LogUtils.error("OffLineMsgService onDestroy")
RxTimerUtils.cancel()
super.onDestroy()
}
}
\ No newline at end of file
package com.renrenliao.im.model.api
import com.renrenliao.im.bean.*
import com.renrenliao.im.http.BaseResult
import com.renrenliao.im.im.GroupEntity
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Url
/**
**@ClassName ApiService
**@Description
**@Author ganzhe
**@Date 2022/5/15 13:56
**/
interface ApiService {
@POST("api/config/appConfig")
suspend fun test(@Body map: Map<String,String>):BaseResult<String>
@GET
suspend fun test1(@Url url:String):Any
//获取线路配置
@GET
suspend fun getLines(@Url url: String): LineEntity
//app通用配置
@POST("api/config/appConfig")
suspend fun appConfig(@Body map: Map<String, String>):BaseResult<AppConfigEntity>
//登陆
@POST("api/auth/login")
suspend fun login(@Body map: Map<String, String>): BaseResult<AuthInfo>
//开屏广告
@POST("api/config/openSAdv")
suspend fun getAd(@Body map: Map<String, String>):BaseResult<Any>
//查询银行卡
@POST("api/bankCard/selectBankCard")
suspend fun getBankCardList(@Body map: Map<String, String>):BaseResult<List<BankCardEntity>>
//注销HTTP服务器的登陆
@POST("api/auth/logout")
suspend fun logOut(@Body map: Map<String, String>):BaseResult<Any>
//修改用户信息
@POST("api/auth/updateUser")
suspend fun updateUserInfo(@Body map: Map<String, String>):BaseResult<UserInfo>
//获取好友
@POST("api/auth/getFriendsList")
suspend fun getFriendList(@Body map: Map<String, String>):BaseResult<List<UserInfo>>
//群主列表
@POST("api/auth/getGroupInfoList")
suspend fun getGroupList(@Body map: Map<String, String>):BaseResult<List<GroupEntity>>
//创建群组
@POST("api/group/createGroupChat")
suspend fun createGroup(@Body map: Map<String, String>):BaseResult<GroupEntity>
//查询好友
@POST("api/friend/find")
suspend fun friendFind(@Body map: Map<String, String>):BaseResult<UserInfo>
//删除好友
@POST("api/friend/deleteFriend")
suspend fun deleteFriend(@Body map: Map<String, String>):BaseResult<Any>
//添加好友
@POST("api/friend/addFriend")
suspend fun addFriend(@Body map: Map<String, String>):BaseResult<Any>
//备注好友
@POST("api/friend/friendRemark")
suspend fun friendRemark(@Body map: Map<String, String>):BaseResult<Any>
//朋友圈数据
@POST("moment/release/releaseList")
suspend fun getFriendCircle(@Body map: Map<String, String>):BaseResult<Any>
//离线好友请求
@POST("api/message/getOfflineFriend")
suspend fun getOfflineFriend(@Body map: Map<String, String>):BaseResult<List<UserInfo>>
//拒绝添加好友
@POST("api/friend/refuseAddFriend")
suspend fun rejectAddFriend(@Body map: Map<String, String>):BaseResult<Any>
//同意添加好友
@POST("api/friend/agreeAddFriend")
suspend fun agreeAddFriend(@Body map: Map<String, String>):BaseResult<UserInfo>
}
\ No newline at end of file
package com.renrenliao.im.model.repository
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.room.Query
import com.renrenliao.im.MyApp
//import com.renrenliao.im.bean.AvatarTAG
import com.renrenliao.im.bean.UserInfo
import com.renrenliao.im.db.AppDatabase
import com.renrenliao.im.im.AlarmEntity
import com.renrenliao.im.im.GroupEntity
import com.renrenliao.im.utils.DataCacheUtils
import com.renrenliao.im.utils.LogUtils
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
*Author:ganzhe
*时间:2020/12/28 12:39
*描述:This is LocalDataSource
*/
class LocalDataSource {
//创建database
private val appDatabase = AppDatabase.getInstance(MyApp.CONTEXT)
companion object {
//个人信息stateflow 响应一对多
private val userInfo = UserInfo()//DataCacheUtils.mUserInfo
private val userFlow = MutableStateFlow(userInfo)
val readOnlyUserFlow = userFlow.asStateFlow()
//全局网络状态livedata
val networkStatusLiveData = MutableLiveData<Int>()
//全局头像版本liveData
//val avatarLiveData = AppDatabase.getInstance(MyApp.CONTEXT).avatarTagDao().getAvatarsLiveData(DataCacheUtils.getUid())
}
suspend fun updateUserInfo(userInfo: UserInfo) {
DataCacheUtils.setUserInfo(userInfo)
userFlow.emit(userInfo)
}
// fun getAvatarsLiveData(): LiveData<List<AvatarTAG>> {
// return appDatabase.avatarTagDao().getAvatarsLiveData(DataCacheUtils.getUid())
// }
fun insertFriend(friend: UserInfo) {
friend.account_uid = DataCacheUtils.getUid()
appDatabase.friendDao().insertFriend(friend)
}
fun insertFriends(friends: List<UserInfo>) {
friends.forEach {
it.account_uid = DataCacheUtils.getUid()
}
appDatabase.friendDao().insertFriends(friends)
}
fun getFriendsLiveData(): Flow<List<UserInfo>> {
return appDatabase.friendDao().getFriendsLiveData(DataCacheUtils.getUid())
}
fun getFriends(): List<UserInfo> {
return appDatabase.friendDao().getFriends(DataCacheUtils.getUid())
}
fun getFriendsByKey(key: String): List<UserInfo> {
return appDatabase.friendDao().getFriendsByKey(DataCacheUtils.getUid(), key)
}
//查询是否是好友
fun isFriend(account_uid: String, uid: String): Boolean {
return appDatabase.friendDao().countByUid(account_uid, uid) > 0
}
//删除好友
fun deleteByUid(account_uid: String, uid: String) {
return appDatabase.friendDao().deleteByUid(account_uid, uid)
}
//更新好友备注
fun updateFriendRemark(account_uid: String, userUid: String, buddyRemark: String) {
return appDatabase.friendDao().updateFriendRemark(account_uid, userUid, buddyRemark)
}
//设置所有好友离线 此方法的应用场景目前是在网络掉线(准确地说是与服务端断开连接)时
fun setAllFriendsOffline() {
return appDatabase.friendDao().setAllFriendsOffline(DataCacheUtils.getUid())
}
//设置好友状态
fun setFriendLiveStatus(liveStatus: Int, uid: String) {
return appDatabase.friendDao().setFriendLiveStatus(liveStatus, uid, DataCacheUtils.getUid())
}
fun insertGroup(group: GroupEntity) {
group.account_uid = DataCacheUtils.getUid()
appDatabase.groupDao().insertGroup(group)
}
fun insertGroups(groups: List<GroupEntity>) {
groups.forEach {
it.account_uid = DataCacheUtils.getUid()
}
appDatabase.groupDao().insertGroups(groups)
}
fun getGroupsLiveData(): Flow<List<GroupEntity>> {
return appDatabase.groupDao().getGroupsLiveData(DataCacheUtils.getUid())
}
//============================== msgDao============================
//更新消息发送状态
fun updateSendStatus(sendStatus: Int, fp: String) {
return appDatabase.msgDao().updateSendStatus(sendStatus, fp, DataCacheUtils.getUid())
}
//====================================alarmDao=================================
//统计全局未读数
fun countUnRead(): LiveData<Int> {
return appDatabase.alarmDao().countUnRead(DataCacheUtils.getUid())
}
//删除 alarm 不是当前userId的
fun deleteAlarmNotCurrentUid() {
return appDatabase.alarmDao().deleteAlarmsNotCurrentUid(DataCacheUtils.getUid())
}
//插入alarm
fun insertAlarm(alarmEntity: AlarmEntity) {
return appDatabase.alarmDao().insertAlarm(alarmEntity)
}
//插入alarms
fun insertAlarms(alarms: List<AlarmEntity>) {
return appDatabase.alarmDao().insertAlarms(alarms)
}
//查询所有alarm的livedata
fun getAlarmsLiveData(): LiveData<List<AlarmEntity>> {
return appDatabase.alarmDao().getAlarmsLiveData(DataCacheUtils.getUid())
}
//查询所有alarm的list
fun getAlarms(): List<AlarmEntity> {
return appDatabase.alarmDao().getAlarms(DataCacheUtils.getUid())
}
//查询alarm
fun getAlarm(gId: String, msgType: Int): AlarmEntity {
return appDatabase.alarmDao().getAlarm(DataCacheUtils.getUid(), gId, msgType)
}
//删除alarm
fun deleteAlarm(account_uid: String, gId: String, msgType: Int) {
appDatabase.alarmDao().deleteAlarm(account_uid, gId, msgType)
}
fun deleteAlarm(account_uid: String, gId: String) {
appDatabase.alarmDao().deleteAlarm(account_uid, gId)
}
//查询AlarmLiveData
fun getAlarmListByTypeLiveData(account_uid: String, msgType: Int): LiveData<List<AlarmEntity>> {
return appDatabase.alarmDao().getAlarmListByTypeLiveData(account_uid, msgType)
}
//更新置顶状态
fun updateIsTop(account_uid: String,uid: String,msgType:Int,top:Boolean){
appDatabase.alarmDao().updateIsTop(account_uid, uid, msgType, top)
}
//更新已读未读状态
fun updateReadStatus(account_uid: String,uid: String,msgType:Int,num:Int){
appDatabase.alarmDao().updateReadStatus(account_uid,uid,msgType,num)
}
//=====================================头像============================================
//更新avatarVersion 先查询 没有 不管 有 判断version 是否相等 不相等 更新 相等不更新
fun updateAvatarVersion(account_uid: String,uid: String,newVersion:Long) {
val alarms = appDatabase.alarmDao().getAlarms(account_uid= DataCacheUtils.getUid(), tid= uid)
if (alarms !=null){
alarms.forEach {
if (it.avatarVersion != newVersion){
appDatabase.alarmDao().updateAvatarVersion(account_uid,uid,newVersion)
}
}
}
val friend = appDatabase.friendDao().getFriend(account_uid= DataCacheUtils.getUid(),uid= uid)
if (friend !=null){
if (friend.avatarVersion != newVersion){
appDatabase.friendDao().updateAvatarVersion(account_uid,uid,newVersion)
}
}
}
}
\ No newline at end of file
package com.renrenliao.im.model.repository
import com.renrenliao.im.bean.*
import com.renrenliao.im.config.Const
import com.renrenliao.im.ext.callRequest
import com.renrenliao.im.ext.handleResponse
import com.renrenliao.im.http.BaseResult
import com.renrenliao.im.http.RetrofitClient
import com.renrenliao.im.http.SealedResult
import com.renrenliao.im.im.GroupEntity
import com.renrenliao.im.model.api.ApiService
import com.renrenliao.im.utils.MapUtils
import retrofit2.http.Body
/**
**@ClassName RemoteDataSource
**@Description
**@Author ganzhe
**@Date 2022/5/15 14:50
**/
class RemoteDataSource {
companion object{
var apiService = RetrofitClient.createApi(ApiService::class.java, Const.BASE_URL)
}
//获取线路配置
suspend fun getLines(): LineEntity {
return apiService.getLines(Const.SERVER_LINES)
}
//版本配置
private suspend fun reqAppConfig(map: Map<String,String>) =
handleResponse(apiService.appConfig(map))
suspend fun getAppConfig(map: Map<String,String>): SealedResult<AppConfigEntity> =
callRequest { reqAppConfig(map) }
//登陆
private suspend fun reqLogin(map: Map<String,String>) =
handleResponse(apiService.login(map))
suspend fun login(map: Map<String,String>): SealedResult<AuthInfo> =
callRequest { reqLogin(map) }
//获取广告
suspend fun getAd(map: Map<String,String>): BaseResult<Any> {
return apiService.getAd(map)
}
//银行卡
suspend fun getBankCardList(map: Map<String, String>):BaseResult<List<BankCardEntity>>{
val body = MapUtils.map2JsonRequestBody(map)
return apiService.getBankCardList(map)
}
//获取广告 异步用的 单独判断异常
private suspend fun reqAds(map: Map<String,String>) =
handleResponse(apiService.getAd(map))
suspend fun getAds(map: Map<String,String>): SealedResult<Any> =
callRequest { reqAds(map) }
//银行卡 异步用的 单独判断异常
private suspend fun reqBankCard(map: Map<String,String>) =
handleResponse(apiService.getBankCardList(map))
suspend fun getBankCards(map: Map<String,String>): SealedResult<List<BankCardEntity>> =
callRequest { reqBankCard(map) }
//获取在线好友
suspend fun getFriendList(map: Map<String, String>):SealedResult<List<UserInfo>> =
callRequest { reqFriendList(map) }
private suspend fun reqFriendList(map: Map<String, String>) =
handleResponse(apiService.getFriendList(map))
//获取群主列表
suspend fun getGroupList(map: Map<String, String>):SealedResult<List<GroupEntity>> =
callRequest { reqGroupList(map) }
private suspend fun reqGroupList(map: Map<String, String>) =
handleResponse(apiService.getGroupList(map))
//退出登陆
suspend fun logout(map: Map<String, String>):BaseResult<Any>{
return apiService.logOut(map)
}
//更新用户信息
suspend fun updateUserInfo(map: Map<String, String>):BaseResult<UserInfo>{
return apiService.updateUserInfo(map)
}
//创建群组
suspend fun createGroup(map: Map<String, String>):BaseResult<GroupEntity>{
return apiService.createGroup(map)
}
//查询好友
suspend fun friendFind(map: HashMap<String, String>):BaseResult<UserInfo>{
val map = MapUtils.getComParam(map)
return apiService.friendFind(map)
}
//删除好友
suspend fun deleteFriend(map: Map<String, String>):BaseResult<Any>{
return apiService.deleteFriend(map)
}
//添加好友
suspend fun addFriend(map: Map<String, String>):BaseResult<Any>{
return apiService.addFriend(map)
}
//拒绝添加好友
suspend fun rejectAddFriend(map: Map<String, String>):BaseResult<Any>{
return apiService.rejectAddFriend(map)
}
//备注好友
suspend fun friendRemark(map: Map<String, String>):BaseResult<Any>{
return apiService.friendRemark(map)
}
//朋友圈数据
suspend fun getFriendCircle( map: Map<String, String>):BaseResult<Any>{
return apiService.getFriendCircle(map)
}
//获取未读加好友请求数
suspend fun getOfflineFriend():SealedResult<List<UserInfo>> =
callRequest { reqOfflineFriend(MapUtils.getComParam(HashMap())) }
private suspend fun reqOfflineFriend(map: Map<String, String>) =
handleResponse(apiService.getOfflineFriend(map))
//同意添加好友
suspend fun agreeAddFriend(map: Map<String, String>):BaseResult<UserInfo>{
return apiService.agreeAddFriend(map)
}
}
\ No newline at end of file
package com.renrenliao.im.provider
import android.app.Activity
import android.app.Application
import android.os.Bundle
/**
**@ClassName ActivityLifecycleCallbacksAdapter
**@Description TODO
**@Author ganzhe
**@Date 2022/5/19 15:19
**/
abstract class ActivityLifecycleCallbacksAdapter: Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
}
\ No newline at end of file
package com.renrenliao.im.provider
import android.app.Activity
import android.app.Application
import android.content.Intent
import android.os.Bundle
import android.os.Looper
import java.lang.ref.WeakReference
/**
**@ClassName ActivityTracker
**@Description TODO
**@Author ganzhe
**@Date 2022/5/19 15:18
**/
class ActivityTracker {
private val activities = mutableListOf<WeakReference<Activity>>()
private val lifecycleCallbacks = object : ActivityLifecycleCallbacksAdapter() {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
add(activity)
}
override fun onActivityDestroyed(activity: Activity) {
remove(activity)
}
}
fun beginTracking(application: Application) {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
fun endTracking(application: Application) {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
fun tryGetCurrentActivity(): Activity? {
activities.reversed().forEach {
val activity = it.get()
if (activity != null) {
return activity
}
}
return null
}
private fun add(activity: Activity) {
if (Looper.myLooper() == Looper.getMainLooper()) {
activities.add(WeakReference(activity))
} else {
throw IllegalStateException("Must in Main Thread!")
}
}
private fun remove(activity: Activity) {
if (Looper.myLooper() == Looper.getMainLooper()) {
removeFromWeakList(activities, activity)
} else {
throw IllegalStateException("Must in Main Thread!")
}
}
private fun <T> removeFromWeakList(
list: MutableList<WeakReference<T>>, needle: T
) {
val find = list.find { it.get() === needle }
list.remove(find)
}
fun finishAllActivity(){
activities.forEach {
it.get()?.finish()
}
activities.clear()
}
/**
* 退出 app 时调用
*/
fun exitApp() {
// try {
// finishAllActivity()
// Process.killProcess(Process.myPid())
// exitProcess(0)
// } catch (e: Exception) {
// e.printStackTrace()
// }
val intent =Intent(Intent.ACTION_MAIN)
intent.flags = (Intent.FLAG_ACTIVITY_NEW_TASK
or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
intent.addCategory(Intent.CATEGORY_HOME)
ClarityPotion.healingSalve.startActivity(intent)
}
}
\ No newline at end of file
package com.renrenliao.im.provider
import android.annotation.SuppressLint
import android.app.Application
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri
/**
**@ClassName ClarityPotion
**@Description
**@Author ganzhe
**@Date 2022/5/19 15:15
**/
@SuppressLint("StaticFieldLeak")
class ClarityPotion : ContentProvider() {
companion object {
/**
* Get Context at anywhere
*/
lateinit var clarityPotion: Context
/**
* Get Application at anywhere
*/
lateinit var healingSalve: Application
private val activityTracker = ActivityTracker()
/**
* Get current Activity at anywhere, Maybe "null" if there no activity.
*/
fun currentActivity() = activityTracker.tryGetCurrentActivity()
fun finishAllActivity() = activityTracker.finishAllActivity()
fun exitApp() = activityTracker.exitApp()
}
override fun onCreate(): Boolean {
clarityPotion = context!!
healingSalve = clarityPotion.applicationContext as Application
activityTracker.beginTracking(healingSalve)
return true
}
override fun query(
uri: Uri, strings: Array<String>?, s: String?, strings1: Array<String>?,
s1: String?
): Cursor? {
return null
}
override fun getType(uri: Uri): String? {
return null
}
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
return null
}
override fun delete(uri: Uri, s: String?, strings: Array<String>?): Int {
return 0
}
override fun update(
uri: Uri,
contentValues: ContentValues?,
s: String?,
strings: Array<String>?
): Int {
return 0
}
}
\ No newline at end of file
package com.renrenliao.im.ui.activity
import androidx.lifecycle.lifecycleScope
import com.renrenliao.im.R
import com.renrenliao.im.base.BaseActivity
import com.renrenliao.im.model.repository.LocalDataSource
import kotlinx.android.synthetic.main.activity_accountsafe.*
import kotlinx.coroutines.flow.collect
/**
**@ClassName AccountSafaActivity
**@Description
**@Author gan
**@Date 2022/6/20 11:52
**/
class AccountSafeActivity:BaseActivity() {
override fun getLayoutResId(): Int = R.layout.activity_accountsafe
override fun initView() {
tool_bar.setOnBackListener { onBackPressed() }
tv_appName.text = getString(R.string.app_name)
}
override fun initData() {
lifecycleScope.launchWhenResumed {
LocalDataSource.readOnlyUserFlow.collect {
tv_account.text = it.chatid
tv_name.text = it.nickname
}
}
}
}
\ No newline at end of file
package com.renrenliao.im.ui.activity
import com.renrenliao.im.R
import com.renrenliao.im.base.BaseVMActivity
import com.renrenliao.im.ext.click
import com.renrenliao.im.im.AlarmEntity
import com.renrenliao.im.viewmodel.AddFriendSureViewModel
import kotlinx.android.synthetic.main.activity_addfriendsure.*
import org.koin.androidx.viewmodel.ext.android.getViewModel
/**
**@ClassName AddFriendSureActivity
**@Description
**@Author gan
**@Date 2022/6/21 12:43
**/
class AddFriendSureActivity:BaseVMActivity<AddFriendSureViewModel>() {
var alarmEntity :AlarmEntity? = null
override fun getLayoutResId(): Int = R.layout.activity_addfriendsure
override fun initVM(): AddFriendSureViewModel = getViewModel()
override fun initView() {
alarmEntity = intent.getParcelableExtra<AlarmEntity>("alarm")
tool_bar.setOnBackListener { onBackPressed() }
submit.click {
agreeAddFriend()
}
}
private fun agreeAddFriend(){
val buddyRemark = et_bz.text.toString().trim()
if (alarmEntity!=null){
mViewModel.agreeAddFriend(buddyRemark,alarmEntity!!)
}
}
override fun initData() {
}
override fun startObserve() {
mViewModel.agreeLiveData.observe(this){
if (it){
intent.putExtra("data","agree")
setResult(RESULT_OK,intent)
finish()
}
}
}
}
\ No newline at end of file
package com.renrenliao.im.ui.activity
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.renrenliao.im.R
import com.renrenliao.im.base.BaseActivity
import kotlinx.android.synthetic.main.activity_bigimage.*
/**
**@ClassName BigImageActivity
**@Description
**@Author gan
**@Date 2022/6/8 19:00
**/
class BigImageActivity:BaseActivity() {
var url = ""
override fun getLayoutResId(): Int = R.layout.activity_bigimage
override fun initView() {
url = intent.getStringExtra("url")
val option = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.override(Target.SIZE_ORIGINAL,Target.SIZE_ORIGINAL)
.format(DecodeFormat.PREFER_RGB_565)
Glide.with(this)
.setDefaultRequestOptions(option)
.load(url)
.into(iv)
tool_bar.setOnBackListener {
onBackPressed()
}
}
override fun initData() {
}
}
\ No newline at end of file
package com.renrenliao.im.ui.activity
import android.content.Intent
import com.renrenliao.im.R
import com.renrenliao.im.base.BaseVMActivity
import com.renrenliao.im.bean.UserInfo
import com.renrenliao.im.viewmodel.FriendAddViewModel
import kotlinx.android.synthetic.main.activity_friendadd.*
import org.koin.androidx.viewmodel.ext.android.getViewModel
/**
**@ClassName FriendAddActivity
**@Description
**@Author gan
**@Date 2022/6/14 18:05
**/
class FriendAddActivity:BaseVMActivity<FriendAddViewModel>() {
override fun initVM(): FriendAddViewModel = getViewModel()
override fun getLayoutResId(): Int = R.layout.activity_friendadd
override fun initView() {
val userInfo = intent.getParcelableExtra<UserInfo>("userinfo")
tool_bar.setOnBackListener { onBackPressed() }
submit.setOnClickListener {
addFriend(userInfo.userUid)
}
}
private fun addFriend(friendId:String){
val say = et_say.text.toString().trim()
val beizhu = et_beizhu.text.toString().trim()
mViewModel.addFriend(friendId,say,beizhu)
}
override fun initData() {
}
override fun startObserve() {
mViewModel.addFriendLiveData.observe(this){
if (it){
val intent = Intent()
intent.putExtra("data","add")
setResult(RESULT_OK,intent)
finish()
}
}
}
}
\ No newline at end of file
package com.renrenliao.im.ui.activity
import android.view.LayoutInflater
import android.view.MenuItem
import android.widget.ImageView
import androidx.recyclerview.widget.LinearLayoutManager
import com.gyf.immersionbar.ktx.immersionBar
import com.gyf.immersionbar.ktx.statusBarHeight
import com.renrenliao.im.R
import com.renrenliao.im.base.BaseVMActivity
import com.renrenliao.im.ext.click
import com.renrenliao.im.ui.adapter.FriendCircleAdapter
import com.renrenliao.im.utils.DeviceUtils
import com.renrenliao.im.utils.GlideUtils
import com.renrenliao.im.viewmodel.FriendCircleViewModel
import kotlinx.android.synthetic.main.activity_friendcircle.*
import org.koin.androidx.viewmodel.ext.android.getViewModel
/**
**@ClassName FriendCircleActivity
**@Description
**@Author gan
**@Date 2022/6/15 15:14
**/
class FriendCircleActivity : BaseVMActivity<FriendCircleViewModel>() {
private val friendCircleAdapter by lazy { FriendCircleAdapter() }
val list = listOf<String>("1", "2", "3", "4", "5", "6", "7")
var pageIndex = 1
val pageSize = 20
override fun initVM(): FriendCircleViewModel = getViewModel()
override fun getLayoutResId(): Int = R.layout.activity_friendcircle
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if (item?.itemId == android.R.id.home) {
finish()
}
return super.onOptionsItemSelected(item);
}
override fun initView() {
immersionBar {
statusBarColor(R.color.transparent)
navigationBarColor(R.color.white)
}
val param = tool_bar.layoutParams
param.height = DeviceUtils.dip2px(this, 45f).toInt() + statusBarHeight
tool_bar.setPadding(0, statusBarHeight, 0, 0)
setSupportActionBar(tool_bar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
val url_cover = "https://cdn.pixabay.com/photo/2016/12/23/12/40/night-1927265_960_720.jpg"
val url_avatar =
"https://img.iplaysoft.com/wp-content/uploads/2019/free-images/free_stock_photo.jpg"
GlideUtils.loadComImage(iv_cover, url_cover)
GlideUtils.loadUserImage(iv_avatar, url_avatar)
recy.run {
adapter = friendCircleAdapter
layoutManager = LinearLayoutManager(this@FriendCircleActivity)
}
friendCircleAdapter.setList(list)
}
override fun initData() {
getFriendCircleData()
}
private fun getFriendCircleData() {
mViewModel.getCircleData(pageIndex, pageSize)
pageIndex++
}
override fun startObserve() {
}
}
\ No newline at end of file
package com.renrenliao.im.ui.activity
import android.content.Intent
import androidx.activity.result.contract.ActivityResultContracts
import com.renrenliao.im.R
import com.renrenliao.im.base.BaseVMActivity
import com.renrenliao.im.bean.UserInfo
import com.renrenliao.im.ext.*
import com.renrenliao.im.utils.DataCacheUtils
import com.renrenliao.im.utils.GlideUtils
import com.renrenliao.im.viewmodel.FriendInfoViewModel
import com.renrenliao.im.widget.dialog.ExplainDialog
import kotlinx.android.synthetic.main.activity_friendinfo.*
import org.koin.androidx.viewmodel.ext.android.getViewModel
/**
**@ClassName FriendInfoActivity
**@Description
**@Author gan
**@Date 2022/6/13 18:03
**/
class FriendInfoActivity : BaseVMActivity<FriendInfoViewModel>() {
var friendInfo: UserInfo? = null
override fun initVM(): FriendInfoViewModel = getViewModel()
override fun getLayoutResId(): Int = R.layout.activity_friendinfo
override fun initView() {
tool_bar.setOnBackListener { onBackPressed() }
friendInfo = intent.getParcelableExtra<UserInfo>("userinfo")
if (friendInfo != null) {
showInfo(friendInfo!!)
}
//删除好友
rl_delete.click {
ExplainDialog.Builder()
.setTitle("删除联系人")
.setDesc("同时删除与该联系人的聊天记录")
.setNo("删除")
.setNoListener {
mViewModel.deleteFriend(friendInfo?.userUid.toString())
}
.create()
.show()
}
//添加好友
rl_add.click {
if (friendInfo?.userUid == DataCacheUtils.getUid()) {
toast("不能添加自己哦")
return@click
}
//RouteUtils.gotoFriendAdd(this,userInfo)
val intent = Intent(this, FriendAddActivity::class.java)
intent.putExtra("userinfo", friendInfo)
requestDataLauncher.launch(intent)
}
//修改备注
rl_remark.click {
val intent = Intent(this, FriendRemarkActivity::class.java)
intent.putExtra("friendId", friendInfo?.userUid)
intent.putExtra("friendRemark", friendInfo?.buddyRemark)
requestDataLauncher.launch(intent)
}
}
override fun initData() {
//判断是否是好友
mViewModel.isFriend(friendInfo?.userUid.toString())
}
// override fun onResume() {
// super.onResume()
// }
override fun startObserve() {
mViewModel.run {
//判断是否是好友展示不一样的视图
isFriendLiveData.observe(this@FriendInfoActivity) {
if (it) {
rl_delete.visible()
rl_add.gone()
tv_flag.gone()
} else {
rl_delete.gone()
rl_add.visible()
tv_flag.visible()
}
}
//删除成功退出activity
deleteLiveData.observe(this@FriendInfoActivity) {
if (it) {
finish()
}
}
}
}
private val requestDataLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val data = result.data?.getStringExtra("data")
// Handle data from FriendAddActivity
if (data == "add") {
finish()
}
if (data == "remark") {
val buddyRemark = result.data?.getStringExtra("buddyRemark")
tv_mark.text = buddyRemark
}
}
}
private fun showInfo(userInfo: UserInfo) {
tv_name.text = userInfo.nickname
tv_whatsup.text = userInfo.whatsUp
if (userInfo.liveStatus == 1) {
tv_status.text = "在线"
} else {
tv_status.text = "离线"
}
tv_mark.text = userInfo.buddyRemark
tv_ID.text = userInfo.chatid
tv_regtime.text = userInfo.registerTime
tv_lastlogin.text = userInfo.latestLoginTime
switch_button.isChecked = DataCacheUtils.getMsgSwitch(userInfo.userUid)
//显示头像
GlideUtils.loadUserImage(iv_avatar, userInfo.userAvatarFileName.toString())
if (isMan(userInfo)) {
iv_sex.setImageResource(R.drawable.sns_friend_list_form_item_male_img)
} else {
iv_sex.setImageResource(R.drawable.sns_friend_list_form_item_female_img)
}
//管理账号 1
if (userInfo.userType == 1) {
iv_cert.visible()
} else {
iv_cert.gone()
}
}
}
\ No newline at end of file
package com.renrenliao.im.ui.activity
import com.renrenliao.im.R
import com.renrenliao.im.base.BaseVMActivity
import com.renrenliao.im.ext.click
import com.renrenliao.im.ext.toast
import com.renrenliao.im.viewmodel.FriendRemarkViewModel
import kotlinx.android.synthetic.main.activity_friendremark.*
import org.koin.androidx.viewmodel.ext.android.getViewModel
/**
**@ClassName FriendRFemarkActivity
**@Description
**@Author gan
**@Date 2022/6/15 13:13
**/
class FriendRemarkActivity :BaseVMActivity<FriendRemarkViewModel>(){
override fun initVM(): FriendRemarkViewModel = getViewModel()
override fun getLayoutResId(): Int = R.layout.activity_friendremark
override fun initView() {
val friendId = intent.getStringExtra("friendId")
val friendRemark = intent.getStringExtra("friendRemark")
//先设置传递过来的remark
et.setText(friendRemark)
tool_bar.setOnBackListener { onBackPressed() }
//点击确定提交修改备注
submit.click {
val remark = et.text.toString().trim()
if (remark.isBlank()){
toast("请输入备注信息")
return@click
}
mViewModel.remark(friendId,remark)
}
}
override fun initData() {
}
override fun startObserve() {
mViewModel.remarkLiveData.observe(this){
if (it){
intent.putExtra("data","remark")
intent.putExtra("buddyRemark",et.text.toString().trim())
setResult(RESULT_OK,intent)
finish()
}
}
}
}
\ No newline at end of file
package com.renrenliao.im.ui.activity
import com.renrenliao.im.R
import com.renrenliao.im.base.BaseVMActivity
import com.renrenliao.im.ext.click
import com.renrenliao.im.utils.DataCacheUtils
import com.renrenliao.im.utils.LogUtils
import com.renrenliao.im.utils.RouteUtils
import com.renrenliao.im.viewmodel.FriendSearchViewModel
import kotlinx.android.synthetic.main.activity_friendsearch.*
import org.koin.androidx.viewmodel.ext.android.getViewModel
/**
**@ClassName FriendSearchActivity
**@Description
**@Author gan
**@Date 2022/6/13 16:30
**/
class FriendSearchActivity:BaseVMActivity<FriendSearchViewModel>() {
override fun initVM(): FriendSearchViewModel = getViewModel()
override fun getLayoutResId(): Int = R.layout.activity_friendsearch
override fun initView() {
tool_bar.setOnBackListener { onBackPressed() }
search_btn.click {
val key = keyword_edit.text.toString().trim()
if (key.isBlank()){
return@click
}
mViewModel.searchFriend(key)
}
}
override fun initData() {
}
override fun startObserve() {
mViewModel.searchLiveData.observe(this){
RouteUtils.gotoFriendInfo(this,it)
}
}
}
\ No newline at end of file
package com.renrenliao.im.ui.activity
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.renrenliao.im.R
import com.renrenliao.im.base.BaseVMActivity
import com.renrenliao.im.ext.click
import com.renrenliao.im.ui.adapter.GroupListAdapter
import com.renrenliao.im.utils.DeviceUtils
import com.renrenliao.im.utils.LogUtils
import com.renrenliao.im.utils.RouteUtils
import com.renrenliao.im.viewmodel.GroupListViewModel
import com.renrenliao.im.widget.view.VerticalDecoration
import kotlinx.android.synthetic.main.activity_grouplist.*
import org.koin.androidx.viewmodel.ext.android.getViewModel
/**
**@ClassName GroupListActivity
**@Description
**@Author gan
**@Date 2022/6/10 16:00
**/
class GroupListActivity:BaseVMActivity<GroupListViewModel>() {
private var emptyView:View? = null
private val groupAdapter by lazy { GroupListAdapter() }
override fun initVM(): GroupListViewModel = getViewModel()
override fun getLayoutResId(): Int = R.layout.activity_grouplist
override fun initView() {
tool_bar.setOnBackListener { onBackPressed() }
val ivMore = tool_bar.findViewById<ImageView>(R.id.iv_right_more)
ivMore.setImageResource(R.drawable.ic_grouplist_creategroup_btn_normal)
//右上角点击
ivMore.click {
RouteUtils.gotoGroupMember(ivMore.context)
}
//没有群点击空试图创建群
emptyView = LayoutInflater.from(this).inflate(R.layout.layout_empty_grouplist,null)
val ivAddGroup = emptyView?.findViewById<ImageView>(R.id.iv_add_group)
ivAddGroup?.click {
LogUtils.error("创建群")
}
val mDivider = ContextCompat.getDrawable(this, R.drawable.divider_left_63)
recy_group.addItemDecoration(VerticalDecoration(this,mDivider,
DeviceUtils.dip2px(this, 0.6f).toInt()
))
recy_group.run {
adapter = groupAdapter
layoutManager = LinearLayoutManager(recy_group.context)
}
groupAdapter.setOnItemClickListener { _, _, position ->
LogUtils.error("点击群 $position")
}
}
override fun initData() {
}
override fun startObserve() {
mViewModel.run {
grouplistLiveData.observe(this@GroupListActivity) { list ->
// list as MutableList
// list.clear()
if (list.isEmpty()){
emptyView?.let { groupAdapter.setEmptyView(it) }
}
groupAdapter.setList(list)
}
}
}
}
\ No newline at end of file
package com.renrenliao.im.ui.adapter
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import com.renrenliao.im.R
/**
**@ClassName FriendCircleAdapter
**@Description
**@Author gan
**@Date 2022/6/15 18:32
**/
class FriendCircleAdapter:BaseQuickAdapter<String,BaseViewHolder>(R.layout.item_friendcircle) {
override fun convert(holder: BaseViewHolder, item: String) {
holder.setText(R.id.tv,item)
}
}
\ No newline at end of file
package com.renrenliao.im.ui.adapter
import android.widget.ImageView
import android.widget.TextView
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import com.renrenliao.im.R
import com.renrenliao.im.bean.UserInfo
import com.renrenliao.im.utils.GlideUtils
/**
**@ClassName FriendSortAdapter
**@Description
**@Author gan
**@Date 2022/5/27 18:52
**/
class FriendSortAdapter:BaseQuickAdapter<UserInfo,BaseViewHolder>(R.layout.item_sort_friend) {
override fun convert(holder: BaseViewHolder, item: UserInfo) {
//holder.setText(R.id.title_name,item.nickname)
val tvName = holder.getView<TextView>(R.id.tv_name)
val tvStatus = holder.getView<TextView>(R.id.tv_status)
val ivAvatar = holder.getView<ImageView>(R.id.iv_avatar)
tvName.text = item.nickname
if (item.liveStatus == 1){
tvStatus.text = "在线"
}else{
tvStatus.text = "离线"
}
GlideUtils.loadUserImage(ivAvatar,item.getAvatarUrl())
}
}
\ No newline at end of file
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论