Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。
Kotlin高仿微信-项目实践58篇,点击查看详情
效果图:
实现代码:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/login_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/wc_base_bg">
<include
layout="@layout/wc_base_top_title"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/login_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="120dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_root_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/wc_logo3"/>
<TextView
android:id="@+id/login_account_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="60dp"
android:text="@string/user_account"
android:textSize="16sp"
android:textColor="@color/color_171717"
android:layout_marginBottom="@dimen/distance_40"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_icon"/>
<EditText
android:id="@+id/login_account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginStart="70dp"
android:paddingLeft="10dp"
android:paddingVertical="8dp"
android:inputType="textEmailAddress"
app:layout_constraintEnd_toStartOf="@+id/login_account_tv"
app:layout_constraintTop_toTopOf="@+id/login_account_tv"
app:layout_constraintBottom_toBottomOf="@+id/login_account_tv"
android:background="@drawable/base_edittext_selector"
android:hint="@string/login_user_tip"
android:textColorHint="@color/gray"
android:textSize="18sp"
android:text=""
android:textColor="@color/gray_text" />
<TextView
android:id="@+id/login_password_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="50dp"
android:text="@string/login_password"
android:textSize="16sp"
android:textColor="@color/black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_account_tv" />
<EditText
android:id="@+id/login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginStart="70dp"
android:paddingLeft="10dp"
android:paddingVertical="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/login_password_tv"
app:layout_constraintTop_toTopOf="@+id/login_password_tv"
app:layout_constraintBottom_toBottomOf="@+id/login_password_tv"
android:hint="@string/login_password_tip"
android:textColorHint="@color/gray"
android:textSize="16sp"
android:inputType="textPassword"
android:background="@drawable/base_edittext_selector"
android:text=""
android:textColor="@color/gray_text" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_register"
android:layout_width="140dp"
android:layout_height="wrap_content"
android:background="@drawable/wc_base_green_selector"
android:layout_marginTop="80dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_login"
app:layout_constraintTop_toBottomOf="@+id/login_password_tv"
android:text="@string/register"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_login"
android:layout_width="140dp"
android:layout_height="wrap_content"
android:background="@drawable/wc_base_green_selector"
app:layout_constraintTop_toTopOf="@+id/btn_register"
app:layout_constraintBottom_toBottomOf="@+id/btn_register"
app:layout_constraintStart_toEndOf="@+id/btn_register"
app:layout_constraintEnd_toEndOf="parent"
android:textColor="@color/white"
android:text="@string/login"
android:textSize="18sp"
android:textStyle="bold"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
/**
* Author : wangning
* Email : maoning20080809@163.com
* Date : 2022/4/19 14:41
* Description : 登录页面
*/
class LoginFragment : BaseDataBindingFragment<WcMainLoginBinding>() , ServiceConnection {
override fun getLayoutRes() = R.layout.wc_main_login
private val userViewModel : UserViewModel by viewModels()
private var account: String = ""
private var password: String = ""
private var xmppService: XmppService? = null
private var navController : NavController? = null
//是否在注册成功页面,注册成功返回
private var isRegisterBack:Boolean = false
override fun onAttach(context: Context) {
super.onAttach(context)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}
override fun onStart() {
super.onStart()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if(!hidden){
getFocus();
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews()
}
private fun initViews() {
super.builder().setTitleContent(R.string.wc_base_top_login)
base_top_back.setOnClickListener {
System.exit(1)
}
navController = Navigation.findNavController(btn_register)
navController?.previousBackStackEntry?.savedStateHandle?.getLiveData<Boolean>(CommonUtils.User.IS_REGISTER_BACK)?.observe(viewLifecycleOwner){
isRegisterBack = it
}
var intent = Intent(requireActivity(), XmppService::class.java)
requireActivity().bindService(intent, this , BIND_AUTO_CREATE)
btn_register.setOnClickListener {
Navigation.findNavController(it).navigate(R.id.action_register)
}
btn_login.setOnClickListener { login() }
var accountTemp = DataStoreUtils.get(DataStoreParams.User.DS_ACCOUNT, "") as String
var passwordTemp = DataStoreUtils.get(DataStoreParams.User.DS_PASSWORD, "") as String
CoroutineScope(Dispatchers.Main).launch {
delay(200)
login_account.setText(accountTemp)
login_password.setText(passwordTemp)
}
login_root_layout.setOnClickListener {
TagUtils.d("点击根节点 Click ")
SoftInputUtils.hideSoftInput(login_account)
}
userViewModel.insertUserData.observe(viewLifecycleOwner){
//如果同步成功,则重新登录
if(it){
DataStoreUtils.put(DataStoreParams.User.DS_OPENFILE_REGISTER, true)
login()
} else {
ToastUtils.makeText(requireActivity(), BaseUtils.getString(R.string.wc_login_failure))
dismissLoadingDialog()
}
}
}
private var loadingUtils : BaseDialogUtils? = null
//显示加载对话框
private fun showLoadingDialog(){
loadingUtils = BaseDialogUtils(requireActivity())
loadingUtils!!.builder()
.hideCancel()
.hideConfirm()
.setCancelable(true)
.setOnLoadingClick(object : BaseDialogUtils.OnLoadingClick{
override fun onClickCancel() {
ToastUtils.makeText(requireActivity(), "对话框取消按钮")
}
override fun onClickConfirm() {
ToastUtils.makeText(requireActivity(), "对话框确定按钮")
}
})
loadingUtils?.show()
}
//隐藏加载对话框
private fun dismissLoadingDialog(){
loadingUtils?.dismiss()
}
//登录
private fun login() {
var isRegisterSuccess = DataStoreUtils.get(DataStoreParams.User.DS_OPENFILE_REGISTER, false) as Boolean
TagUtils.d("login 开始 ${isRegisterSuccess}")
account = login_account.text.toString()
password = login_password.getText().toString()
if (account?.length == 0 || password?.length == 0) {
ToastUtils.makeText(requireActivity(), "帐号或密码不能为空")
return
} else if (!isRegisterSuccess && isRegisterBack){
var name = DataStoreUtils.get(DataStoreParams.User.DS_NICKNAME, "") as String
if(TextUtils.isEmpty(name)){
name = account
}
//如果在openfire注册成功,没有同步到web服务器,点击登录按钮,则重新同步
var userBean = UserBean(account = account, name = name, nickName = name, address = "", email = "",phone = "", avatar = "", birthday = "", note = "")
userViewModel.insertUser(userBean)
} else {
SoftInputUtils.hideSoftInput(login_account)
showLoadingDialog()
try {
xmppService?.login(account, password)
} catch (e : Exception) {
e.printStackTrace()
//ToastUtils.makeText(R.string.wc_login_failure)
dismissLoadingDialog()
}
}
}
/*private fun initCall() {
//在前台了,发送广播 调起权限判断弹窗
val viop = Intent()
val intent: Intent = requireActivity().intent
viop.putExtra("room", intent.getStringExtra("room"))
viop.putExtra("audioOnly", intent.getBooleanExtra("audioOnly", false))
viop.putExtra("inviteId", intent.getStringExtra("inviteId"))
viop.putExtra("inviteUserName", intent.getStringExtra("inviteUserName"))
// viop.putExtra("msgId", intent.getLongExtra("msgId", 0));
viop.putExtra("userList", intent.getStringExtra("userList"))
viop.action = Utils.ACTION_VOIP_RECEIVER
viop.component = ComponentName(WcApp.getInstance().getPackageName(), VoipReceiver::class.java.getName())
WcApp.getInstance().sendBroadcast(viop)
}*/
private fun processLoginResult(type : Int) {
CoroutineScope(Dispatchers.Main).launch {
dismissLoadingDialog()
if(type == 1){
ToastUtils.makeText(R.string.wc_login_success)
/*var isFromCall = requireActivity().intent.getBooleanExtra("isFromCall", false)
if (isFromCall) { //无权限,来电申请权限会走这里
initCall()
}*/
DataStoreUtils.get(DataStoreParams.Install.DS_FIRST_INSTALL, true)
loginSusscess()
} else {
ToastUtils.makeText(R.string.wc_login_failure)
}
}
}
override fun onServiceConnected(p0: ComponentName?, binder: IBinder?) {
TagUtils.d("LoginService 连接成功")
var xmppBinder = binder as XmppService.XmppBinder
xmppService = xmppBinder.service
xmppService?.setCallback(object : XmppService.LoginCallback{
override fun onFailure() {
//TagUtils.d("LoginFragment登录失败")
processLoginResult(2)
}
override fun onSuccess(account: String, password: String) {
TagUtils.d("LoginFragment登录成功")
processLoginSuccess()
}
})
}
override fun onServiceDisconnected(p0: ComponentName?) {
TagUtils.d("LoginService onServiceDisconnected 断开连接")
}
private fun processLoginSuccess(){
CoroutineScope(Dispatchers.IO).launch {
DataStoreUtils.put(DataStoreParams.User.DS_ACCOUNT, account!!)
DataStoreUtils.put(DataStoreParams.User.DS_PASSWORD, password!!)
DataStoreUtils.put(DataStoreParams.User.DS_USER_EXIST, false)
//登录成功, 保存本地信息
var userBean = UserRepository.getUserByAccount(account!!)
TagUtils.d("登录成功:${account} , ${password} , userBean = ${userBean}")
// 设置用户名
WcApp.getInstance().username = account!!
// 添加登录回调
SocketManager.getInstance().addUserStateCallback(object : IUserState{
override fun userLogin() {
TagUtils.d("登录视频服务器成功login")
}
override fun userLogout() {
TagUtils.d("登录视频服务器失败logout")
}
})
// 连接socket:登录
SocketManager.getInstance().connect(BaseUtils.WS, account!!, 0)
processLoginResult(1)
}
}
//登录成功
private fun loginSusscess() {
Navigation.findNavController(btn_login).popBackStack(R.id.main_start_dest, true)
EventBus.getDefault().post(EventMsgBean(EventMsgBean.TYPE_LOGIN_SUCCESS))
}
override fun onResume() {
super.onResume()
getFocus()
}
private fun getFocus(){
view?.isFocusableInTouchMode = true
view?.requestFocus()
view?.setOnKeyListener { view, keyCode, event ->
if(event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
System.exit(1)
}
true
}
}
}
fun login(account : String, password : String){
CoroutineScope(Dispatchers.IO).launch {
try {
if(!xmppConnection.isAuthenticated){
xmppConnection.connect()
// 连接服务器
xmppConnection.login(account, password)
// 连接服务器成功,更改在线状态
val presence = Presence(Presence.Type.available)
xmppConnection.sendPacket(presence)
TagUtils.d("用户登录状态:${xmppConnection.isAuthenticated} , ${xmppConnection.isConnected}")
}
if(xmppConnection.isAuthenticated){
WcApp.setXmppConnection(xmppConnection)
ChatManagerUtils.getInstance().addChatListener()
AddFileListener.addFileListerer()
AddFriendsListener.addSubscriptionListener()
checkConnectionListener = CheckConnectionListener()
xmppConnection.addConnectionListener(checkConnectionListener)
TagUtils.d("xmpp 管理连接成功 ")
var result = UserService.getApi().getUser(account)
TagUtils.d("syncUser 用户返回数据:${result}")
if(result.isSuccess){
//插入本地数据库
UserRepository.insertUser(GsonUtils.getUserBean(result.data!!))
//处理同步信息
} else {
//如果在openfire注册成功,没有同步到web服务器,或者web服务器已经不存在该账户,只能重新创建
var userBean = UserBean(account = account, name = account, nickName = account, address = "", email = "",phone = "", avatar = "", birthday = "", note = "")
var userStr = Gson().toJson(userBean)
UserRepository.insertUser(userBean)
UserRepository.insertUserServer(userStr)
}
loginCallback?.onSuccess(account, password)
} else {
loginCallback?.onFailure()
}
} catch (e: Exception){
//ToastUtils.makeText(R.string.wc_login_failure)
TagUtils.d("用户登录状态:${e.message}")
e.printStackTrace()
loginCallback?.onFailure()
}
}
}
//登录成功,同步数据
private fun showLoadingDialog(){
var isFirstLogin = DataStoreUtils.get(DataStoreParams.User.DS_IS_FIRST_LOGIN, true) as Boolean
//首次安装
if(!isFirstLogin){
return
}
var account = DataStoreUtils.getAccount()
if(TextUtils.isEmpty(account)){
return
}
var view = LayoutInflater.from(this).inflate(R.layout.wc_sync_first_data, null)
loadingUtils = BaseDialogUtils(this)
loadingUtils!!.builder()
.showView(view)
.hideCancel()
.hideConfirm()
.setCancelable(false)
loadingUtils?.show()
SyncRepository.syncFirst(account, packageName, object : ICommonCallback{
override fun onFailure(obj: Any) {
dismissLoadingDialog()
}
override fun onSuccess(obj: Any) {
dismissLoadingDialog()
}
})
}

















