diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index 6d30101..a55e7a1 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,6 +1,5 @@
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index f3af304..1fdec27 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -11,6 +11,7 @@
+
diff --git a/app/build.gradle b/app/build.gradle
index cd7c75b..9f6630a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,6 +2,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
+apply plugin: 'com.google.protobuf'
apply from: versions
android {
@@ -17,6 +18,17 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
+ sourceSets {
+ main {
+ java {
+ srcDirs 'src/main/java'
+ }
+ proto {
+ srcDirs 'src/main/proto'
+ }
+ }
+ }
+
buildTypes {
release {
minifyEnabled false
@@ -34,15 +46,45 @@ android {
}
+protobuf {
+ protoc {
+ // You still need protoc like in the non-Android case
+ artifact = deps.protoc
+ }
+ plugins {
+ javalite {
+ // The codegen for lite comes as a separate artifact
+ artifact = deps.protoc_gen
+ }
+ }
+ generateProtoTasks {
+ all().each { task ->
+ task.builtins {
+ // In most cases you don't need the full Java output
+ // if you use the lite output.
+ remove java
+ }
+ task.plugins {
+ javalite {}
+ }
+ }
+ }
+}
+
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation deps.androidx.constraint_layout
- implementation project(':lib-architecture')
+ implementation project(':chat')
+ implementation deps.androidx.constraint_layout
implementation deps.navigation.runtime_ktx
implementation deps.navigation.fragment_ktx
implementation deps.navigation.ui_ktx
implementation deps.retrofit.runtime
implementation deps.retrofit.gson
+ implementation deps.proto
+
+ implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
+ implementation "com.qmuiteam:qmui:2.0.0-alpha10"
+ implementation "com.zhy:base-rvadapter:3.0.3"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3d177b2..d0dbdbb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,7 +2,7 @@
-
+
+ android:theme="@style/AppTheme"
+ android:usesCleartextTraffic="true">
+
+
+
diff --git a/app/src/main/java/com/zjy/arch/AppPreference.kt b/app/src/main/java/com/zjy/arch/AppPreference.kt
new file mode 100644
index 0000000..d79af61
--- /dev/null
+++ b/app/src/main/java/com/zjy/arch/AppPreference.kt
@@ -0,0 +1,19 @@
+package com.zjy.arch
+
+import com.tencent.mmkv.MMKV
+import com.zjy.architecture.util.preference.IStorage
+import com.zjy.architecture.util.preference.Preference
+import com.zjy.architecture.util.preference.PreferenceDelegate
+import com.zjy.architecture.util.preference.PreferenceStorage
+
+/**
+ * @author zhengjy
+ * @since 2020/07/15
+ * Description:
+ */
+object AppPreference : Preference {
+ override val sp: IStorage = PreferenceStorage(MMKV.defaultMMKV())
+
+ val isLogin by PreferenceDelegate("IS_LOGIN", false, sp)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zjy/arch/MainActivity.kt b/app/src/main/java/com/zjy/arch/MainActivity.kt
index 5415860..5f11cd6 100644
--- a/app/src/main/java/com/zjy/arch/MainActivity.kt
+++ b/app/src/main/java/com/zjy/arch/MainActivity.kt
@@ -1,7 +1,9 @@
package com.zjy.arch
-import androidx.appcompat.app.AppCompatActivity
+import android.content.Intent
import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.liveData
import com.bumptech.glide.request.RequestOptions
import com.google.gson.Gson
@@ -17,18 +19,31 @@ class MainActivity : AppCompatActivity() {
"N5FKueEq4oT4VxcqjELLtBaqkMMdh6Fkiaya1uLfD0clTbjocU5pvZo7VK2ak3A/640?wx_fmt=png&" +
"tp=webp&wxfrom=5&wx_lazy=1&wx_co=1"
+ private val viewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
+ val isEdit = AppPreference["app", true]
+ AppPreference["IS_LOGIN"] = "zhengjy"
+ AppPreference["IS_LOGIN"] = 128
+ val login = AppPreference.isLogin
+ if (AppPreference["IS_LOGIN", false] == AppPreference.isLogin) {
+ println("succeed!")
+ }
+
imageView?.apply {
load(imageUrl) {
apply(RequestOptions().placeholder(R.mipmap.ic_launcher))
}
setOnClickListener {
-
+ startActivity(Intent(this@MainActivity, WebViewActivity::class.java))
}
}
+ textView.setOnClickListener {
+ startActivity(Intent(this@MainActivity, WidgetActivity::class.java))
+ }
liveData(Dispatchers.IO) {
emit("")
}
diff --git a/app/src/main/java/com/zjy/arch/WebViewActivity.kt b/app/src/main/java/com/zjy/arch/WebViewActivity.kt
new file mode 100644
index 0000000..e6b5069
--- /dev/null
+++ b/app/src/main/java/com/zjy/arch/WebViewActivity.kt
@@ -0,0 +1,68 @@
+package com.zjy.arch
+
+import android.os.Bundle
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.qmuiteam.qmui.nestedScroll.QMUIContinuousNestedBottomAreaBehavior
+import com.qmuiteam.qmui.nestedScroll.QMUIContinuousNestedBottomRecyclerView
+import com.qmuiteam.qmui.nestedScroll.QMUIContinuousNestedTopAreaBehavior
+import com.qmuiteam.qmui.nestedScroll.QMUIContinuousNestedTopWebView
+import com.zhy.adapter.recyclerview.CommonAdapter
+import com.zhy.adapter.recyclerview.base.ViewHolder
+import kotlinx.android.synthetic.main.activity_web_view.*
+
+
+class WebViewActivity : AppCompatActivity() {
+
+ private var mNestedWebView: QMUIContinuousNestedTopWebView? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_web_view)
+
+ mNestedWebView = QMUIContinuousNestedTopWebView(this)
+ val matchParent = ViewGroup.LayoutParams.MATCH_PARENT
+ val webViewLp = CoordinatorLayout.LayoutParams(
+ matchParent, matchParent
+ )
+ webViewLp.behavior = QMUIContinuousNestedTopAreaBehavior(this)
+ mCoordinatorLayout.setTopAreaView(mNestedWebView, webViewLp)
+
+ val mRecyclerView = QMUIContinuousNestedBottomRecyclerView(this)
+ val recyclerViewLp = CoordinatorLayout.LayoutParams(
+ matchParent, matchParent
+ )
+ recyclerViewLp.behavior = QMUIContinuousNestedBottomAreaBehavior()
+ mCoordinatorLayout.setBottomAreaView(mRecyclerView, recyclerViewLp)
+
+ mRecyclerView.layoutManager = LinearLayoutManager(this)
+ mRecyclerView.adapter = object : CommonAdapter(this, android.R.layout.activity_list_item, listOf("a", "b", "c", "d", "e")) {
+ override fun convert(holder: ViewHolder, t: String, position: Int) {
+ holder.setText(android.R.id.text1, t)
+ }
+ }
+
+ mNestedWebView?.settings?.apply {
+ javaScriptEnabled = true
+ domStorageEnabled = true
+ blockNetworkImage = false
+ }
+ mNestedWebView?.loadUrl(
+// "https://api.xiaoheihe.cn/v3/bbs/app/api/web/share?link_id=42234008"
+// "http://fd.33.cn:1230/#/detail?id=40"
+// "https://www.baidu.com"
+ "https://server.chain199.com/#/detail?id=23"
+ )
+ }
+
+ override fun onDestroy() {
+ super.onDestroy();
+ if (mNestedWebView != null) {
+ mCoordinatorLayout.removeView(mNestedWebView);
+ mNestedWebView?.destroy()
+ mNestedWebView = null
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zjy/arch/WidgetActivity.kt b/app/src/main/java/com/zjy/arch/WidgetActivity.kt
new file mode 100644
index 0000000..ac48266
--- /dev/null
+++ b/app/src/main/java/com/zjy/arch/WidgetActivity.kt
@@ -0,0 +1,19 @@
+package com.zjy.arch
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.fragment.app.commit
+import com.zjy.arch.fragment.ChartFragment
+import com.zjy.arch.fragment.PullLayoutFragment
+
+class WidgetActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_widget)
+
+ supportFragmentManager.commit {
+// add(R.id.fcv_container, PullLayoutFragment(R.layout.fragment_pull_layout))
+ add(R.id.fcv_container, ChartFragment(R.layout.fragment_chart))
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zjy/arch/fragment/ChartFragment.kt b/app/src/main/java/com/zjy/arch/fragment/ChartFragment.kt
new file mode 100644
index 0000000..524aa94
--- /dev/null
+++ b/app/src/main/java/com/zjy/arch/fragment/ChartFragment.kt
@@ -0,0 +1,140 @@
+package com.zjy.arch.fragment
+
+import android.graphics.Color
+import android.graphics.DashPathEffect
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import com.github.mikephil.charting.components.Description
+import com.github.mikephil.charting.components.Legend
+import com.github.mikephil.charting.components.LegendEntry
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.Entry
+import com.github.mikephil.charting.data.LineData
+import com.github.mikephil.charting.data.LineDataSet
+import com.github.mikephil.charting.formatter.ValueFormatter
+import com.zjy.architecture.ext.dp
+import kotlinx.android.synthetic.main.fragment_chart.*
+
+
+/**
+ * @author zhengjy
+ * @since 2020/07/03
+ * Description:
+ */
+class ChartFragment(layout: Int) : Fragment(layout) {
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ lc_chart.apply {
+ minOffset = 25f
+ setTouchEnabled(false)
+ // 设置图例
+ legend.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM
+ legend.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT
+ legend.orientation = Legend.LegendOrientation.VERTICAL
+ legend.setDrawInside(true)
+ legend.yOffset = 18f
+ legend.setExtra(arrayOf(LegendEntry("单位(D)", Legend.LegendForm.NONE, 10f, 20f,
+ DashPathEffect(floatArrayOf(10f, 5f), 0f), Color.parseColor("#000000"))))
+ // 设置x轴
+ xAxis.setDrawGridLines(false)
+ xAxis.granularity = 1f
+ xAxis.labelCount = 12
+ xAxis.axisMinimum = 1f
+ xAxis.axisMaximum = 12f
+ xAxis.position = XAxis.XAxisPosition.BOTTOM
+ xAxis.valueFormatter = object : ValueFormatter() {
+ override fun getFormattedValue(value: Float): String {
+ return "${value.toInt()}月"
+ }
+ }
+ // 设置y轴
+ axisLeft.setGridDashedLine(DashPathEffect(floatArrayOf(10f, 5f), 0f))
+ axisLeft.setDrawAxisLine(false)
+ axisRight.isEnabled = false
+ // 设置表格名称
+ description = Description().apply {
+ text = "会员月视力趋势图"
+ textSize = 13f
+// val output = IntArray(2)
+// lc_chart.getLocationInWindow(output)
+// setPosition((output[0] + lc_chart.paddingLeft).toFloat(), (output[1] + lc_chart.paddingTop).toFloat())
+ setPosition(115.dp.toFloat(), 20.dp.toFloat())
+ }
+ // 设置数据
+ data = LineData(
+ LineDataSet(
+ listOf(
+ Entry(1f, 650f),
+ Entry(2f, 675f),
+ Entry(3f, 650f),
+ Entry(4f, 700f),
+ Entry(5f, 800f),
+ Entry(6f, 700f),
+ Entry(7f, 650f),
+ Entry(8f, -275f),
+ Entry(9f, 800f),
+ Entry(10f, 825f)
+ ), "会员左眼视力"
+ ).apply {
+ setDrawCircles(false)
+ mode = LineDataSet.Mode.CUBIC_BEZIER
+ },
+ LineDataSet(
+ listOf(
+ Entry(1f, 350f),
+ Entry(2f, 275f),
+ Entry(3f, 350f),
+ Entry(4f, -100f),
+ Entry(5f, 275f),
+ Entry(6f, 250f)
+ ), "会员右眼视力"
+ ).apply {
+ setDrawCircles(false)
+ setCircleColor(Color.parseColor("#86C649"))
+ color = Color.parseColor("#86C649")
+ mode = LineDataSet.Mode.CUBIC_BEZIER
+ },
+ LineDataSet(
+ listOf(
+ Entry(1f, 650f),
+ Entry(2f, 175f),
+ Entry(3f, 350f),
+ Entry(4f, 100f),
+ Entry(5f, 575f),
+ Entry(6f, 750f),
+ Entry(7f, 650f),
+ Entry(8f, 570f),
+ Entry(9f, 750f)
+ ), "会员右眼视力"
+ ).apply {
+ setDrawCircles(false)
+ setCircleColor(Color.parseColor("#9722FE"))
+ color = Color.parseColor("#9722FE")
+ mode = LineDataSet.Mode.CUBIC_BEZIER
+ },
+ LineDataSet(
+ listOf(
+ Entry(1f, 250f),
+ Entry(2f, 475f),
+ Entry(3f, 370f),
+ Entry(4f, -25f),
+ Entry(5f, 250f),
+ Entry(6f, 300f),
+ Entry(7f, -100f),
+ Entry(8f, 275f),
+ Entry(9f, 150f),
+ Entry(10f, 575f)
+ ), "会员右眼视力"
+ ).apply {
+ setDrawCircles(false)
+ setCircleColor(Color.parseColor("#03C9E6"))
+ color = Color.parseColor("#03C9E6")
+ mode = LineDataSet.Mode.CUBIC_BEZIER
+ }
+ )
+ // 设置动画
+ animateXY(400, 400)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zjy/arch/fragment/PullLayoutFragment.kt b/app/src/main/java/com/zjy/arch/fragment/PullLayoutFragment.kt
new file mode 100644
index 0000000..51511a0
--- /dev/null
+++ b/app/src/main/java/com/zjy/arch/fragment/PullLayoutFragment.kt
@@ -0,0 +1,35 @@
+package com.zjy.arch.fragment
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.zhy.adapter.recyclerview.CommonAdapter
+import com.zhy.adapter.recyclerview.base.ViewHolder
+import kotlinx.android.synthetic.main.fragment_pull_layout.*
+
+/**
+ * @author zhengjy
+ * @since 2020/07/02
+ * Description:
+ */
+class PullLayoutFragment(layout: Int) : Fragment(layout) {
+
+ private val data = listOf(
+ "苹果", "橘子", "香蕉", "草莓", "橙子", "榴莲", "荔枝", "樱桃", "西瓜",
+ "苹果", "橘子", "香蕉", "草莓", "橙子", "榴莲", "荔枝", "樱桃", "西瓜",
+ "苹果", "橘子", "香蕉", "草莓", "橙子", "榴莲", "荔枝", "樱桃", "西瓜",
+ "苹果", "橘子", "香蕉", "草莓", "橙子", "榴莲", "荔枝", "樱桃", "西瓜",
+ "苹果", "橘子", "香蕉", "草莓", "橙子", "榴莲", "荔枝", "樱桃", "西瓜",
+ "苹果", "橘子", "香蕉", "草莓", "橙子", "榴莲", "荔枝", "樱桃", "西瓜"
+ )
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ rv_text.layoutManager = LinearLayoutManager(context)
+ rv_text.adapter = object : CommonAdapter(context, android.R.layout.activity_list_item, data) {
+ override fun convert(holder: ViewHolder, t: String, position: Int) {
+ holder.setText(android.R.id.text1, t)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zjy/arch/widget/PullLayout.kt b/app/src/main/java/com/zjy/arch/widget/PullLayout.kt
new file mode 100644
index 0000000..beeea79
--- /dev/null
+++ b/app/src/main/java/com/zjy/arch/widget/PullLayout.kt
@@ -0,0 +1,201 @@
+package com.zjy.arch.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.Log
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.widget.FrameLayout
+import android.widget.OverScroller
+import android.widget.Scroller
+import android.widget.TextView
+import androidx.core.view.NestedScrollingParent3
+import androidx.core.view.NestedScrollingParentHelper
+import androidx.core.view.ViewCompat
+import androidx.core.view.children
+import com.zjy.architecture.ext.dp
+import kotlin.math.abs
+import kotlin.math.pow
+import kotlin.math.sqrt
+
+/**
+ * @author zhengjy
+ * @since 2020/07/02
+ * Description:
+ */
+class PullLayout : FrameLayout, NestedScrollingParent3 {
+
+ companion object {
+ val TAG = "PullLayout"
+ }
+
+ private var mNestedParent = NestedScrollingParentHelper(this)
+
+ constructor(context: Context) : super(context) {
+ init()
+ }
+
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+ init()
+ }
+
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
+ : super(context, attrs, defStyleAttr) {
+ init()
+ }
+
+ private var headerView: View? = null
+ private var contentView: View? = null
+ private lateinit var mScroller: Scroller
+ private lateinit var mOverScroller: OverScroller
+
+ private var mLastY = 0f
+ private var mScreenHeightPixels: Int = context.resources.displayMetrics.heightPixels
+
+ private val mTotalOffset
+ get() = abs(scrollY)
+ private val mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
+ private val mMinimumVelocity = ViewConfiguration.get(context).scaledMinimumFlingVelocity
+
+ private fun init() {
+ if (childCount > 1) {
+ throw IllegalStateException("PullLayout can host only one direct child")
+ }
+ if (childCount == 1) {
+ contentView = getChildAt(0)
+ }
+ headerView = TextView(context).apply {
+ layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 60f.dp)
+ text = "下拉刷新"
+ gravity = Gravity.CENTER
+ }
+ addView(headerView, 0)
+ isNestedScrollingEnabled = true
+ mScroller = Scroller(context)
+ mOverScroller = OverScroller(context)
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ children.forEach {
+ if (it == headerView) {
+ it.layout(0, -it.measuredHeight, it.measuredWidth, 0)
+ } else if (it == contentView) {
+ layout(0, 0, it.measuredWidth, it.measuredHeight)
+ }
+ }
+ }
+
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
+ when (event?.action) {
+ MotionEvent.ACTION_DOWN -> {
+ mLastY = event.y
+ stopNestedScroll()
+ return true
+ }
+ MotionEvent.ACTION_MOVE -> {
+ val deltaY = event.y - mLastY
+ if (deltaY >= mTouchSlop) {
+ val headerHeight = headerView?.measuredHeight ?: 0
+ scrollY = if (deltaY <= headerHeight) {
+ -deltaY.toInt()
+ } else {
+ -(headerHeight + (deltaY - headerHeight) / 2).toInt()
+ }
+ }
+ }
+ MotionEvent.ACTION_UP,
+ MotionEvent.ACTION_CANCEL -> {
+ mScroller.startScroll(0, scrollY, 0, abs(scrollY), 300)
+ invalidate()
+ }
+ }
+ return super.onTouchEvent(event)
+ }
+
+ override fun computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ scrollTo(mScroller.currX, mScroller.currY)
+ postInvalidate()
+// } else if (scrollY < 0) {
+// mScroller.startScroll(0, scrollY, 0, abs(scrollY), 300)
+// invalidate()
+ }
+ }
+
+ override fun getNestedScrollAxes(): Int {
+ return ViewCompat.SCROLL_AXIS_VERTICAL
+ }
+
+ override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
+ val accepted = isEnabled
+ && isNestedScrollingEnabled
+ && nestedScrollAxes and ViewCompat.SCROLL_AXIS_VERTICAL != 0
+ return accepted
+ }
+
+ override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
+ mNestedParent.onNestedScrollAccepted(child, target, axes, type)
+ }
+
+ override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
+ if (dy > 0 && scrollY < 0) {
+ if (dy > mTotalOffset) {
+ consumed[1] = mTotalOffset
+ } else {
+ consumed[1] = dy
+ }
+ Log.d(TAG, "PreScroll:${consumed[1]}")
+ scrollY += consumed[1]
+ }
+ }
+
+ override fun onNestedScroll(target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int,
+ dyUnconsumed: Int, type: Int, consumed: IntArray) {
+ Log.d(TAG, "x:${dxUnconsumed}, y:${dyUnconsumed}, offset:${mTotalOffset}")
+ var consumedY = 0
+ if (dyUnconsumed < 0) {
+ consumedY = if (mTotalOffset <= headerView!!.measuredHeight) {
+ dyUnconsumed
+ } else {
+ (sqrt(abs(dyUnconsumed.toDouble()) / (headerView!!.measuredHeight)) * dyUnconsumed / 2).toInt()
+ }
+ }
+
+ consumed[1] += consumedY
+ scrollY += consumed[1]
+ invalidate()
+ }
+
+ override fun onNestedScroll(target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int,
+ dyUnconsumed: Int, type: Int) {
+ onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, IntArray(2))
+ }
+
+ override fun onNestedScroll(target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int,
+ dyUnconsumed: Int) {
+ onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, ViewCompat.TYPE_NON_TOUCH, IntArray(2))
+ }
+
+ override fun onStopNestedScroll(target: View, type: Int) {
+ mNestedParent.onStopNestedScroll(target, type)
+ }
+
+// override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean {
+// Log.d(TAG, "Vx:${velocityX}, Vy:${velocityY}")
+// if (velocityY < 0 && scrollY <= 0) {
+// mScroller.fling(0, scrollY, velocityX.toInt(), velocityY.toInt(), 0, 0, -Int.MAX_VALUE, Int.MAX_VALUE)
+// invalidate()
+// }
+// return super.onNestedPreFling(target, velocityX, velocityY)
+// }
+//
+// override fun onNestedFling(target: View, velocityX: Float, velocityY: Float, consumed: Boolean): Boolean {
+// Log.d(TAG, "Vx:${velocityX}, Vy:${velocityY}, consumed:${consumed}")
+// mOverScroller.fling(0, scrollY, velocityX.toInt(), velocityY.toInt(), 0, 0, -Int.MAX_VALUE, Int.MAX_VALUE, 0, headerView!!.measuredHeight)
+// return super.onNestedFling(target, velocityX, velocityY, consumed)
+// }
+
+}
\ No newline at end of file
diff --git a/app/src/main/proto/chat.proto b/app/src/main/proto/chat.proto
new file mode 100644
index 0000000..e03225d
--- /dev/null
+++ b/app/src/main/proto/chat.proto
@@ -0,0 +1,24 @@
+syntax = "proto3";
+package com.zjy.chat.data.proto;
+
+message SendMessageRequest {
+ string access_token = 1;
+ string from = 2;
+ string to = 3;
+ string text = 4;
+ string topic = 5;
+}
+
+message SendMessageResponse {
+
+ enum Error {
+ ERR_OK = 0;
+ ERR_SYS = -1;
+ }
+
+ int32 err_code = 1;
+ string err_msg = 2;
+ string from = 3;
+ string text = 4;
+ string topic = 5;
+}
\ No newline at end of file
diff --git a/app/src/main/proto/main.proto b/app/src/main/proto/main.proto
new file mode 100644
index 0000000..c46fbeb
--- /dev/null
+++ b/app/src/main/proto/main.proto
@@ -0,0 +1,48 @@
+syntax = "proto3";
+package com.zjy.chat.data.proto;
+
+enum CmdID {
+ CMD_ID_UNKNOWN = 0;
+ CMD_ID_INVALID = -1;
+ CMD_ID_HELLO = 1;
+ CMD_ID_AUTH = 2;
+ CMD_ID_SEND_MESSAGE = 3;
+ CMD_ID_CONVERSATION_LIST = 4;
+ CMD_ID_JOIN_TOPIC = 5;
+ CMD_ID_LEFT_TOPIC = 7;
+}
+
+message HelloRequest {
+ string user = 1;
+ string text = 2;
+ bytes dump_content = 3;
+}
+
+message HelloResponse {
+ int32 retCode = 1;
+ string errMsg = 2;
+ bytes dump_content = 3;
+}
+
+message Conversation {
+ string topic = 1;
+ string notice = 2;
+ string name = 3;
+}
+
+message ConversationListRequest {
+ enum FilterType {
+ DEFAULT = 0;
+ ALL = 1;
+ NEAR_BY = 2;
+ FRIENDS = 3;
+ HOT = 4;
+ }
+
+ string access_token = 1;
+ int32 type = 2;
+}
+
+message ConversationListResponse {
+ repeated Conversation list = 1;
+}
diff --git a/app/src/main/proto/messagepush.proto b/app/src/main/proto/messagepush.proto
new file mode 100644
index 0000000..29b4a2a
--- /dev/null
+++ b/app/src/main/proto/messagepush.proto
@@ -0,0 +1,8 @@
+syntax = "proto3";
+package com.zjy.chat.data.proto;
+
+message MessagePush {
+ string topic = 1;
+ string content = 2;
+ string from = 3;
+}
diff --git a/app/src/main/proto/topic.proto b/app/src/main/proto/topic.proto
new file mode 100644
index 0000000..79c06b1
--- /dev/null
+++ b/app/src/main/proto/topic.proto
@@ -0,0 +1,17 @@
+syntax = "proto3";
+package com.zjy.chat.data.proto;
+
+message TopicRequest {
+ string topic = 1;
+}
+
+message TopicResponse {
+
+ enum Error {
+ ERR_OK = 0;
+ ERR_SYS = -1;
+ }
+
+ int32 err_code = 1;
+ string err_msg = 2;
+}
diff --git a/app/src/main/res/drawable/bg_chart.xml b/app/src/main/res/drawable/bg_chart.xml
new file mode 100644
index 0000000..e73036e
--- /dev/null
+++ b/app/src/main/res/drawable/bg_chart.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_web_view.xml b/app/src/main/res/layout/activity_web_view.xml
new file mode 100644
index 0000000..e755e86
--- /dev/null
+++ b/app/src/main/res/layout/activity_web_view.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_widget.xml b/app/src/main/res/layout/activity_widget.xml
new file mode 100644
index 0000000..6942fdf
--- /dev/null
+++ b/app/src/main/res/layout/activity_widget.xml
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_chart.xml b/app/src/main/res/layout/fragment_chart.xml
new file mode 100644
index 0000000..5c0e13f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_chart.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_pull_layout.xml b/app/src/main/res/layout/fragment_pull_layout.xml
new file mode 100644
index 0000000..3540dbc
--- /dev/null
+++ b/app/src/main/res/layout/fragment_pull_layout.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 73640a9..757972b 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -11,6 +11,6 @@
diff --git a/build.gradle b/build.gradle
index 999a67c..29f67b3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,6 +4,7 @@ apply plugin: 'koin'
buildscript {
ext {
versions = "${rootDir}/lib-architecture/versions.gradle"
+ kotlin_version = '1.3.72'
}
apply from: versions
repositories {
@@ -13,6 +14,7 @@ buildscript {
}
dependencies {
classpath deps.android_gradle_plugin
+ classpath deps.protobuf_gradle_plugin
classpath deps.kotlin.plugin
classpath deps.koin.plugin
diff --git a/chat/.gitignore b/chat/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/chat/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/chat/build.gradle b/chat/build.gradle
new file mode 100644
index 0000000..db491a4
--- /dev/null
+++ b/chat/build.gradle
@@ -0,0 +1,43 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
+apply from: versions
+
+android {
+ compileSdkVersion build_versions.compile_sdk
+
+ defaultConfig {
+ minSdkVersion build_versions.min_sdk
+ targetSdkVersion build_versions.target_sdk
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles 'consumer-rules.pro'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: "libs", include: ["*.jar"])
+
+ api(project(':lib-architecture')) {
+ exclude group: 'com.tencent.mars', module: 'mars-xlog'
+ }
+ implementation deps.mars.core
+}
\ No newline at end of file
diff --git a/chat/consumer-rules.pro b/chat/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/chat/proguard-rules.pro b/chat/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/chat/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/chat/src/androidTest/java/com/zjy/chat/ExampleInstrumentedTest.kt b/chat/src/androidTest/java/com/zjy/chat/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..a7540c0
--- /dev/null
+++ b/chat/src/androidTest/java/com/zjy/chat/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.zjy.chat
+
+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.zjy.chat.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/chat/src/main/AndroidManifest.xml b/chat/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e1e9431
--- /dev/null
+++ b/chat/src/main/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/chat/src/main/aidl/com/zjy/chat/remote/MarsPushMessageFilter.aidl b/chat/src/main/aidl/com/zjy/chat/remote/MarsPushMessageFilter.aidl
new file mode 100644
index 0000000..6df4e28
--- /dev/null
+++ b/chat/src/main/aidl/com/zjy/chat/remote/MarsPushMessageFilter.aidl
@@ -0,0 +1,11 @@
+// MarsRecvCallBack.aidl
+package com.zjy.chat.remote;
+
+// Declare any non-default types here with import statements
+
+interface MarsPushMessageFilter {
+
+ // returns processed ?
+ boolean onReceive(int cmdId, inout byte[] buffer);
+
+}
diff --git a/chat/src/main/aidl/com/zjy/chat/remote/MarsService.aidl b/chat/src/main/aidl/com/zjy/chat/remote/MarsService.aidl
new file mode 100644
index 0000000..6ad8627
--- /dev/null
+++ b/chat/src/main/aidl/com/zjy/chat/remote/MarsService.aidl
@@ -0,0 +1,21 @@
+// MarsService.aidl
+package com.zjy.chat.remote;
+
+// Declare any non-default types here with import statements
+import com.zjy.chat.remote.MarsTaskWrapper;
+import com.zjy.chat.remote.MarsPushMessageFilter;
+
+interface MarsService {
+
+ int send(MarsTaskWrapper taskWrapper, in Bundle taskProperties);
+
+ void cancel(int taskID);
+
+ void registerPushMessageFilter(MarsPushMessageFilter filter);
+
+ void unregisterPushMessageFilter(MarsPushMessageFilter filter);
+
+ void setAccountInfo(in long uin, in String userName);
+
+ void setForeground(in int isForeground);
+}
diff --git a/chat/src/main/aidl/com/zjy/chat/remote/MarsTaskWrapper.aidl b/chat/src/main/aidl/com/zjy/chat/remote/MarsTaskWrapper.aidl
new file mode 100644
index 0000000..e791bb9
--- /dev/null
+++ b/chat/src/main/aidl/com/zjy/chat/remote/MarsTaskWrapper.aidl
@@ -0,0 +1,15 @@
+// MarsTaskWrapper.aidl
+package com.zjy.chat.remote;
+
+// Declare any non-default types here with import statements
+
+interface MarsTaskWrapper {
+
+ Bundle getProperties(); // called locally
+
+ byte[] req2buf();
+
+ int buf2resp(in byte[] buf);
+
+ void onTaskEnd(in int errType, in int errCode);
+}
diff --git a/chat/src/main/java/com/zjy/chat/MarsServiceStub.kt b/chat/src/main/java/com/zjy/chat/MarsServiceStub.kt
new file mode 100644
index 0000000..5000d03
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/MarsServiceStub.kt
@@ -0,0 +1,253 @@
+package com.zjy.chat
+
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import android.os.RemoteException
+import com.tencent.mars.BaseEvent
+import com.tencent.mars.app.AppLogic
+import com.tencent.mars.sdt.SdtLogic
+import com.tencent.mars.stn.StnLogic
+import com.tencent.mars.xlog.Log
+import com.zjy.chat.config.ServiceProfile
+import com.zjy.chat.remote.MarsPushMessageFilter
+import com.zjy.chat.remote.MarsService
+import com.zjy.chat.remote.MarsTaskWrapper
+import com.zjy.chat.task.TaskProperty
+import java.io.ByteArrayOutputStream
+import java.io.IOException
+import java.util.*
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ConcurrentLinkedQueue
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:
+ */
+class MarsServiceStub(
+ private val context: Context,
+ private val profile: ServiceProfile
+) : MarsService.Stub(), StnLogic.ICallBack, SdtLogic.ICallBack, AppLogic.ICallBack {
+
+ companion object {
+ private const val TAG = "Mars.MarsServiceStub"
+ private val DEVICE_NAME = "${Build.MANUFACTURER}-${Build.MODEL}"
+ private val DEVICE_TYPE = "android-${Build.VERSION.SDK_INT}"
+ private val TASK_ID_TO_WRAPPER = ConcurrentHashMap()
+ }
+
+ private val deviceInfo = AppLogic.DeviceInfo(DEVICE_NAME, DEVICE_TYPE)
+ private val accountInfo = AppLogic.AccountInfo()
+
+ private val filters = ConcurrentLinkedQueue()
+
+ override fun send(taskWrapper: MarsTaskWrapper, taskProperties: Bundle): Int {
+ val _task = StnLogic.Task(StnLogic.Task.EShort, 0, "", ArrayList())
+
+ // Set host & cgi path
+ val host = taskProperties.getString(TaskProperty.OPTIONS_HOST)
+ val cgiPath = taskProperties.getString(TaskProperty.OPTIONS_CGI_PATH)
+ _task.shortLinkHostList.add(host)
+ _task.cgi = cgiPath
+
+ val shortSupport = taskProperties.getBoolean(TaskProperty.OPTIONS_CHANNEL_SHORT_SUPPORT, true)
+ val longSupport = taskProperties.getBoolean(TaskProperty.OPTIONS_CHANNEL_LONG_SUPPORT, false)
+ if (shortSupport && longSupport) {
+ _task.channelSelect = StnLogic.Task.EBoth
+ } else if (shortSupport) {
+ _task.channelSelect = StnLogic.Task.EShort
+ } else if (longSupport) {
+ _task.channelSelect = StnLogic.Task.ELong
+ } else {
+ Log.e(TAG, "invalid channel strategy")
+ throw RemoteException("Invalid Channel Strategy")
+ }
+
+ // Set cmdID if necessary
+ val cmdID = taskProperties.getInt(TaskProperty.OPTIONS_CMD_ID, -1)
+ if (cmdID != -1) {
+ _task.cmdID = cmdID
+ }
+
+ TASK_ID_TO_WRAPPER[_task.taskID] = taskWrapper
+
+ // Send
+ Log.i(TAG, "now start task with id %d", _task.taskID)
+ StnLogic.startTask(_task)
+ if (StnLogic.hasTask(_task.taskID)) {
+ Log.i(TAG, "stn task started with id %d", _task.taskID)
+ } else {
+ Log.e(TAG, "stn task start failed with id %d", _task.taskID)
+ }
+
+ return _task.taskID
+ }
+
+ override fun cancel(taskID: Int) {
+ Log.d(TAG, "cancel wrapper with taskID=%d using stn stop", taskID)
+ StnLogic.stopTask(taskID)
+ TASK_ID_TO_WRAPPER.remove(taskID) // TODO: check return
+ }
+
+ override fun registerPushMessageFilter(filter: MarsPushMessageFilter?) {
+ filters.remove(filter)
+ filters.add(filter)
+ }
+
+ override fun unregisterPushMessageFilter(filter: MarsPushMessageFilter?) {
+ filters.remove(filter)
+ }
+
+ override fun setAccountInfo(uin: Long, userName: String?) {
+ accountInfo.uin = uin
+ accountInfo.userName = userName
+ }
+
+ override fun setForeground(isForeground: Int) {
+ BaseEvent.onForeground(isForeground == 1)
+ }
+
+ override fun makesureAuthed(host: String?): Boolean {
+ //
+ // Allow you to block all tasks which need to be sent before certain 'AUTHENTICATED' actions
+ // Usually we use this to exchange encryption keys, sessions, etc.
+ //
+ return true
+ }
+
+ override fun onNewDns(host: String?): Array? {
+ // No default new dns support
+ return null
+ }
+
+ override fun onPush(cmdid: Int, data: ByteArray?) {
+ for (filter in filters) {
+ try {
+ if (filter.onReceive(cmdid, data)) {
+ break
+ }
+ } catch (e: RemoteException) {
+ Log.e(TAG, "", e)
+ }
+ }
+ }
+
+ override fun trafficData(send: Int, recv: Int) {
+ // onPush(BaseConstants.FLOW_CMDID, String.format("%d,%d", send, recv).getBytes(Charset.forName("UTF-8")));
+ }
+
+ override fun reportConnectInfo(status: Int, longlinkstatus: Int) {
+
+ }
+
+ override fun getLongLinkIdentifyCheckBuffer(identifyReqBuf: ByteArrayOutputStream?, hashCodeBuffer: ByteArrayOutputStream?, reqRespCmdID: IntArray?): Int {
+ // Send identify request buf to server
+ // identifyReqBuf.write();
+
+ return StnLogic.ECHECK_NEVER
+ }
+
+ override fun onLongLinkIdentifyResp(buffer: ByteArray?, hashCodeBuffer: ByteArray?): Boolean {
+ return false
+ }
+
+ override fun requestDoSync() {
+
+ }
+
+ override fun requestNetCheckShortLinkHosts(): Array {
+ return arrayOf()
+ }
+
+ override fun isLogoned(): Boolean {
+ return false
+ }
+
+ override fun onTaskEnd(taskID: Int, userContext: Any?, errType: Int, errCode: Int): Int {
+ val wrapper = TASK_ID_TO_WRAPPER.remove(taskID)
+ if (wrapper == null) {
+ Log.w(TAG, "stn task onTaskEnd callback may fail, null wrapper, taskID=%d", taskID)
+ return 0
+ }
+
+ try {
+ wrapper.onTaskEnd(errType, errCode)
+ } catch (e: RemoteException) {
+ e.printStackTrace()
+ }
+
+ return 0
+ }
+
+ override fun req2Buf(taskID: Int, userContext: Any?, reqBuffer: ByteArrayOutputStream?, errCode: IntArray?, channelSelect: Int, host: String?): Boolean {
+ val wrapper = TASK_ID_TO_WRAPPER[taskID]
+ if (wrapper == null) {
+ Log.e(TAG, "invalid req2Buf for task, taskID=%d", taskID)
+ return false
+ }
+
+ try {
+ reqBuffer?.write(wrapper.req2buf())
+ return true
+ } catch (e: IOException) {
+ e.printStackTrace()
+ Log.e(TAG, "task wrapper req2buf failed for short, check your encode process")
+ } catch (e: RemoteException) {
+ e.printStackTrace()
+ Log.e(TAG, "task wrapper req2buf failed for short, check your encode process")
+ }
+
+ return false
+ }
+
+ override fun buf2Resp(taskID: Int, userContext: Any?, respBuffer: ByteArray?, errCode: IntArray?, channelSelect: Int): Int {
+ val wrapper = TASK_ID_TO_WRAPPER[taskID]
+ if (wrapper == null) {
+ Log.e(TAG, "buf2Resp: wrapper not found for stn task, taskID=%", taskID)
+ return StnLogic.RESP_FAIL_HANDLE_TASK_END
+ }
+
+ try {
+ return wrapper.buf2resp(respBuffer)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "remote wrapper disconnected, clean this context, taskID=%d", taskID)
+ TASK_ID_TO_WRAPPER.remove(taskID)
+ }
+ return StnLogic.RESP_FAIL_HANDLE_TASK_END
+ }
+
+ override fun reportTaskProfile(taskString: String?) {
+ // onPush(BaseConstants.CGIHISTORY_CMDID, reportString.getBytes(Charset.forName("UTF-8")));
+ }
+
+ override fun reportSignalDetectResults(resultsJson: String?) {
+ // onPush(BaseConstants.SDTRESULT_CMDID, reportString.getBytes(Charset.forName("UTF-8")));
+ }
+
+ override fun getAppFilePath(): String? {
+ try {
+ val file = context.filesDir
+ if (!file.exists()) {
+ file.createNewFile()
+ }
+ return file.toString()
+ } catch (e: Exception) {
+ Log.e(TAG, "", e)
+ }
+
+ return null
+ }
+
+ override fun getAccountInfo(): AppLogic.AccountInfo {
+ return accountInfo
+ }
+
+ override fun getClientVersion(): Int {
+ return profile.clientVersion()
+ }
+
+ override fun getDeviceType(): AppLogic.DeviceInfo {
+ return deviceInfo
+ }
+}
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/MessageService.kt b/chat/src/main/java/com/zjy/chat/MessageService.kt
new file mode 100644
index 0000000..e0b894e
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/MessageService.kt
@@ -0,0 +1,96 @@
+package com.zjy.chat
+
+import android.app.Service
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import com.tencent.mars.Mars
+import com.tencent.mars.app.AppLogic
+import com.tencent.mars.sdt.SdtLogic
+import com.tencent.mars.stn.StnLogic
+import com.tencent.mars.xlog.Log
+import com.zjy.chat.config.ChatServiceProfileFactory
+import com.zjy.chat.config.DefaultServiceProfile
+import com.zjy.chat.remote.MarsPushMessageFilter
+import com.zjy.chat.remote.MarsService
+import com.zjy.chat.remote.MarsTaskWrapper
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:
+ */
+class MessageService : Service(), MarsService {
+
+ companion object {
+ const val TAG = "Mars.MessageServiceNative"
+
+ var factory = ChatServiceProfileFactory {
+ DefaultServiceProfile()
+ }
+ }
+
+ private lateinit var stub: MarsServiceStub
+
+ override fun onCreate() {
+ StnLogic()
+ val profile = factory.createServiceProfile()
+ stub = MarsServiceStub(applicationContext, profile)
+ // set callback
+ AppLogic.setCallBack(stub)
+ StnLogic.setCallBack(stub)
+ SdtLogic.setCallBack(stub)
+
+ // Initialize the Mars PlatformComm
+ Mars.init(applicationContext, Handler(Looper.getMainLooper()))
+
+ // Initialize the Mars
+ StnLogic.setLonglinkSvrAddr(profile.longLinkHost(), profile.longLinkPorts())
+ StnLogic.setShortlinkSvrAddr(profile.shortLinkPort())
+ StnLogic.setClientVersion(profile.clientVersion())
+ Mars.onCreate(true)
+
+ StnLogic.makesureLongLinkConnected()
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return stub
+ }
+
+ override fun asBinder(): IBinder {
+ return stub
+ }
+
+ override fun unregisterPushMessageFilter(filter: MarsPushMessageFilter?) {
+ stub.unregisterPushMessageFilter(filter)
+ }
+
+ override fun registerPushMessageFilter(filter: MarsPushMessageFilter?) {
+ stub.registerPushMessageFilter(filter)
+ }
+
+ override fun setAccountInfo(uin: Long, userName: String?) {
+ stub.setAccountInfo(uin, userName)
+ }
+
+ override fun setForeground(isForeground: Int) {
+ stub.setForeground(isForeground)
+ }
+
+ override fun send(taskWrapper: MarsTaskWrapper?, taskProperties: Bundle?): Int {
+ return stub.send(taskWrapper, taskProperties)
+ }
+
+ override fun cancel(taskID: Int) {
+ stub.cancel(taskID)
+ }
+
+ override fun onDestroy() {
+ Log.d(TAG, "message service native destroying")
+ Mars.onDestroy()
+ Log.d(TAG, "message service native destroyed")
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/MessageServiceProxy.kt b/chat/src/main/java/com/zjy/chat/MessageServiceProxy.kt
new file mode 100644
index 0000000..9ba8f62
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/MessageServiceProxy.kt
@@ -0,0 +1,207 @@
+package com.zjy.chat
+
+import android.app.Service
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import android.os.RemoteException
+import com.tencent.mars.app.AppLogic.AccountInfo
+import com.tencent.mars.xlog.Log
+import com.zjy.architecture.ext.tryWith
+import com.zjy.chat.config.ChatServiceProfileFactory
+import com.zjy.chat.config.ServiceProfile
+import com.zjy.chat.remote.MarsPushMessageFilter
+import com.zjy.chat.remote.MarsService
+import com.zjy.chat.remote.MarsTaskWrapper
+import com.zjy.chat.task.TaskProperty
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.LinkedBlockingQueue
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:
+ */
+class MessageServiceProxy : ServiceConnection {
+
+ companion object {
+ /**
+ * cmdId存储Map
+ */
+ val GLOBAL_CMD_ID_MAP = ConcurrentHashMap()
+ /**
+ * 消息处理器Map
+ */
+ val HANDLER_MAP = ConcurrentHashMap()
+ /**
+ * 登录用户信息
+ */
+ var accountInfo: AccountInfo = AccountInfo()
+
+ private var instance: MessageServiceProxy? = null
+ private const val TAG = "MessageManager"
+ private var gContext: Context? = null
+ private var gPackageName: String? = null
+ private var gClassName: String? = null
+ private const val SERVICE_DEFAULT_CLASSNAME = "com.zjy.chat.MessageServiceNative"
+
+ @JvmStatic
+ fun init(context: Context, packageName: String?, provider: (() -> ServiceProfile)? = null) {
+ gContext = context.applicationContext
+ gPackageName = packageName ?: context.packageName
+ gClassName = SERVICE_DEFAULT_CLASSNAME
+ provider?.also {
+ MessageService.factory = ChatServiceProfileFactory(it)
+ }
+
+ instance = MessageServiceProxy()
+ }
+
+ @JvmStatic
+ fun send(taskWrapper: MarsTaskWrapper) {
+ instance?.queue?.offer(taskWrapper)
+ }
+
+ @JvmStatic
+ fun cancel(taskWrapper: MarsTaskWrapper) {
+ instance?.cancelSpecifiedTaskWrapper(taskWrapper)
+ }
+
+ /**
+ * 前后台切换
+ */
+ fun setForeground(isForeground: Boolean) = checkService {
+ instance?.service?.setForeground(if (isForeground) 1 else 0)
+ }
+
+ /**
+ * 重置[MessageServiceProxy],清空所有数据和队列
+ */
+ fun reset() {
+ accountInfo = AccountInfo()
+ GLOBAL_CMD_ID_MAP.clear()
+ HANDLER_MAP.clear()
+ instance?.queue?.clear()
+ }
+
+ /**
+ * 检查服务是否正在运行,如果在运行,则进行指定操作
+ */
+ private fun checkService(block: (() -> Unit)? = null) = tryWith {
+ if (instance?.service == null) {
+ Log.d(TAG, "try to bind remote mars service, packageName: %s, className: %s", gPackageName, gClassName)
+ val i: Intent = Intent().setClassName(gPackageName!!, gClassName!!)
+ gContext?.startService(i)
+ if (!gContext!!.bindService(i, instance!!, Service.BIND_AUTO_CREATE)) {
+ Log.e(TAG, "remote mars service bind failed")
+ return@tryWith
+ }
+ block?.invoke()
+ }
+ }
+ }
+
+ private val worker: Worker
+ private var service: MarsService? = null
+
+ /**
+ * 任务队列
+ */
+ private val queue: LinkedBlockingQueue = LinkedBlockingQueue()
+
+ private val filter = object : MarsPushMessageFilter.Stub() {
+ override fun onReceive(cmdId: Int, buffer: ByteArray): Boolean {
+ val handler = HANDLER_MAP[cmdId]
+ if (handler != null) {
+ Log.i(TAG, "processing push message, cmdid = %d", cmdId)
+ val message = PushMessage(cmdId, buffer)
+ handler.process(message)
+ return true
+ } else {
+ Log.i(TAG, "no push message listener set for cmdid = %d, just ignored", cmdId)
+ }
+ return false
+ }
+ }
+
+ init {
+ worker = Worker()
+ worker.start()
+ }
+
+ override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
+ Log.d(TAG, "remote mars service connected")
+ try {
+ service = MarsService.Stub.asInterface(binder)
+ service?.registerPushMessageFilter(filter)
+ service?.setAccountInfo(accountInfo.uin, accountInfo.userName)
+
+ } catch (e: Exception) {
+ service = null
+ }
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ tryWith { service?.unregisterPushMessageFilter(filter) }
+ service = null
+ Log.d(TAG, "remote mars service disconnected")
+ }
+
+ /**
+ * 取消指定的任务
+ */
+ private fun cancelSpecifiedTaskWrapper(marsTaskWrapper: MarsTaskWrapper) {
+ if (queue.remove(marsTaskWrapper)) {
+ // Remove from queue, not exec yet, call MarsTaskWrapper::onTaskEnd
+ try {
+ marsTaskWrapper.onTaskEnd(-1, -1)
+ } catch (e: RemoteException) {
+ // Called in client, ignore RemoteException
+ e.printStackTrace()
+ Log.e(TAG, "cancel mars task wrapper in client, should not catch RemoteException")
+ }
+ } else {
+ // Already sent to remote service, need to cancel it
+ try {
+ service?.cancel(marsTaskWrapper.properties.getInt(TaskProperty.OPTIONS_TASK_ID))
+ } catch (e: RemoteException) {
+ e.printStackTrace()
+ Log.w(TAG, "cancel mars task wrapper in remote service failed, I'll make marsTaskWrapper.onTaskEnd")
+ }
+ }
+ }
+
+ /**
+ * 从消息队列中取出任务,发送给[MarsService]
+ */
+ private fun continueProcessTaskWrappers() = checkService {
+ val taskWrapper = queue.take() ?: return@checkService
+ Log.d(TAG, "sending task = %s", taskWrapper)
+ val cgiPath = taskWrapper.properties.getString(TaskProperty.OPTIONS_CGI_PATH)
+ val globalCmdID = GLOBAL_CMD_ID_MAP[cgiPath]
+ if (globalCmdID != null) {
+ taskWrapper.properties.putInt(TaskProperty.OPTIONS_CMD_ID, globalCmdID)
+ Log.i(TAG, "overwrite cmdID with global cmdID Map: %s -> %d", cgiPath, globalCmdID)
+ }
+
+ val taskID = service?.send(taskWrapper, taskWrapper.properties) ?: -1
+ // NOTE: Save taskID to taskWrapper here
+ taskWrapper.properties.putInt(TaskProperty.OPTIONS_CMD_ID, taskID)
+ }
+
+ private inner class Worker : Thread() {
+ override fun run() {
+ while (true) {
+ instance?.continueProcessTaskWrappers()
+ try {
+ sleep(20)
+ } catch (e: InterruptedException) {
+ Log.e(TAG, "", e)
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/PushMessage.kt b/chat/src/main/java/com/zjy/chat/PushMessage.kt
new file mode 100644
index 0000000..1ea5fc0
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/PushMessage.kt
@@ -0,0 +1,15 @@
+package com.zjy.chat
+
+import android.os.Parcelable
+import kotlinx.android.parcel.Parcelize
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:
+ */
+@Parcelize
+class PushMessage(
+ val cmdId: Int,
+ val buffer: ByteArray
+) : Parcelable
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/PushMessageHandler.kt b/chat/src/main/java/com/zjy/chat/PushMessageHandler.kt
new file mode 100644
index 0000000..4dff5b5
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/PushMessageHandler.kt
@@ -0,0 +1,11 @@
+package com.zjy.chat
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:
+ */
+interface PushMessageHandler {
+
+ fun process(message: PushMessage)
+}
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/config/ChatServiceProfileFactory.kt b/chat/src/main/java/com/zjy/chat/config/ChatServiceProfileFactory.kt
new file mode 100644
index 0000000..121b76d
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/config/ChatServiceProfileFactory.kt
@@ -0,0 +1,22 @@
+package com.zjy.chat.config
+
+import com.tencent.mars.xlog.Log
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:
+ */
+class ChatServiceProfileFactory(
+ private val provider: () -> ServiceProfile
+) : ServiceProfileFactory {
+
+ override fun createServiceProfile(): ServiceProfile {
+ return try {
+ provider.invoke()
+ } catch (e: Exception) {
+ Log.e("ServerProfileFactory", "${e.message}")
+ DefaultServiceProfile()
+ }
+ }
+}
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/config/DefaultServiceProfile.kt b/chat/src/main/java/com/zjy/chat/config/DefaultServiceProfile.kt
new file mode 100644
index 0000000..ae12206
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/config/DefaultServiceProfile.kt
@@ -0,0 +1,25 @@
+package com.zjy.chat.config
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:服务端配置空实现
+ */
+class DefaultServiceProfile : ServiceProfile {
+
+ override fun clientVersion(): Int {
+ return 0
+ }
+
+ override fun longLinkHost(): String {
+ return ""
+ }
+
+ override fun longLinkPorts(): IntArray {
+ return intArrayOf()
+ }
+
+ override fun shortLinkPort(): Int {
+ return 0
+ }
+}
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/config/ServiceProfile.kt b/chat/src/main/java/com/zjy/chat/config/ServiceProfile.kt
new file mode 100644
index 0000000..aba8f05
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/config/ServiceProfile.kt
@@ -0,0 +1,43 @@
+package com.zjy.chat.config
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:连接配置类
+ */
+interface ServiceProfile {
+
+ /**
+ * 客户端版本 放入长连私有协议头部
+ */
+ fun clientVersion(): Int
+
+ /**
+ * 长链接域名
+ */
+ fun longLinkHost(): String
+
+ /**
+ * 长链接端口列表
+ */
+ fun longLinkPorts(): IntArray
+
+ /**
+ * 长链接调试IP.如果有值,则忽略 host设置, 并使用该IP.
+ */
+ fun longLinkDebugIP(): String? {
+ return null
+ }
+
+ /**
+ * 短链接(HTTP)端口
+ */
+ fun shortLinkPort(): Int
+
+ /**
+ * 短链接调试IP.如果有值,则所有TASK走短链接时,使用该IP代替TASK中的HOST
+ */
+ fun shortLinkDebugIP(): String? {
+ return null
+ }
+}
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/config/ServiceProfileFactory.kt b/chat/src/main/java/com/zjy/chat/config/ServiceProfileFactory.kt
new file mode 100644
index 0000000..c0c6d76
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/config/ServiceProfileFactory.kt
@@ -0,0 +1,11 @@
+package com.zjy.chat.config
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:
+ */
+interface ServiceProfileFactory {
+
+ fun createServiceProfile(): ServiceProfile
+}
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/task/AbstractTaskWrapper.kt b/chat/src/main/java/com/zjy/chat/task/AbstractTaskWrapper.kt
new file mode 100644
index 0000000..3938b4a
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/task/AbstractTaskWrapper.kt
@@ -0,0 +1,70 @@
+package com.zjy.chat.task
+
+import android.os.Bundle
+import com.zjy.chat.remote.MarsTaskWrapper
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:抽象任务包装类
+ */
+abstract class AbstractTaskWrapper : MarsTaskWrapper.Stub() {
+
+ private val properties = Bundle()
+
+ init {
+ // Reflects task properties
+ val taskProperty = this.javaClass.getAnnotation(TaskConfig::class.java)
+ if (taskProperty != null) {
+ setHttpRequest(taskProperty.host, taskProperty.path)
+ setShortChannelSupport(taskProperty.shortChannelSupport)
+ setLongChannelSupport(taskProperty.longChannelSupport)
+ setCmdID(taskProperty.cmdID)
+ }
+ }
+
+ override fun getProperties(): Bundle {
+ return properties
+ }
+
+ abstract override fun onTaskEnd(errType: Int, errCode: Int)
+
+ fun setHttpRequest(host: String, path: String?): AbstractTaskWrapper {
+ properties.putString(TaskProperty.OPTIONS_HOST, if ("" == host) null else host)
+ properties.putString(TaskProperty.OPTIONS_CGI_PATH, path)
+ return this
+ }
+
+ fun setShortChannelSupport(support: Boolean): AbstractTaskWrapper {
+ properties.putBoolean(TaskProperty.OPTIONS_CHANNEL_SHORT_SUPPORT, support)
+ return this
+ }
+
+ fun setLongChannelSupport(support: Boolean): AbstractTaskWrapper {
+ properties.putBoolean(TaskProperty.OPTIONS_CHANNEL_LONG_SUPPORT, support)
+ return this
+ }
+
+ fun setCmdID(cmdID: Int): AbstractTaskWrapper {
+ properties.putInt(TaskProperty.OPTIONS_CMD_ID, cmdID)
+ return this
+ }
+
+ override fun toString(): String {
+ return "AbsMarsTask: " + format(properties)
+ }
+
+ private fun format(bundle: Bundle): String {
+ val sb = StringBuilder("{")
+ val keys = bundle.keySet()
+ for (k in keys) {
+ val obj = bundle[k]
+ if (obj is Bundle) {
+ sb.append(format(obj))
+ } else {
+ sb.append(k).append("=").append(obj).append("; ")
+ }
+ }
+ return sb.append("}").toString()
+ }
+}
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/task/TaskConfig.kt b/chat/src/main/java/com/zjy/chat/task/TaskConfig.kt
new file mode 100644
index 0000000..1b92a78
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/task/TaskConfig.kt
@@ -0,0 +1,19 @@
+package com.zjy.chat.task
+
+import java.lang.annotation.Inherited
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:
+ */
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
+@Inherited
+annotation class TaskConfig(
+ val host: String = "",
+ val path: String,
+ val shortChannelSupport: Boolean = true,
+ val longChannelSupport: Boolean = false,
+ val cmdID: Int = -1
+)
\ No newline at end of file
diff --git a/chat/src/main/java/com/zjy/chat/task/TaskProperty.kt b/chat/src/main/java/com/zjy/chat/task/TaskProperty.kt
new file mode 100644
index 0000000..a63e53e
--- /dev/null
+++ b/chat/src/main/java/com/zjy/chat/task/TaskProperty.kt
@@ -0,0 +1,15 @@
+package com.zjy.chat.task
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:
+ */
+object TaskProperty {
+ const val OPTIONS_HOST = "host"
+ const val OPTIONS_CGI_PATH = "cgi_path"
+ const val OPTIONS_CMD_ID = "cmd_id"
+ const val OPTIONS_CHANNEL_SHORT_SUPPORT = "short_support"
+ const val OPTIONS_CHANNEL_LONG_SUPPORT = "long_support"
+ const val OPTIONS_TASK_ID = "task_id"
+}
\ No newline at end of file
diff --git a/lib-architecture/src/main/java/com/zjy/architecture/Arch.kt b/lib-architecture/src/main/java/com/zjy/architecture/Arch.kt
index 8511b11..752a992 100644
--- a/lib-architecture/src/main/java/com/zjy/architecture/Arch.kt
+++ b/lib-architecture/src/main/java/com/zjy/architecture/Arch.kt
@@ -1,6 +1,10 @@
package com.zjy.architecture
+import android.app.ActivityManager
import android.content.Context
+import android.os.Process
+import com.tencent.mars.xlog.Log
+import com.tencent.mars.xlog.Xlog
/**
* @author zhengjy
@@ -17,7 +21,64 @@ object Arch {
private var mContext: Context? = null
- fun init(context: Context) {
+ /**
+ * 在Application的onCreate中初始化
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun init(context: Context, debug: Boolean = false) {
this.mContext = context.applicationContext
+ openXLog(context, debug)
+ }
+
+ /**
+ * 通常在Application的onTerminate中调用,用于释放资源,关闭日志
+ */
+ @JvmStatic
+ fun terminate() {
+ Log.appenderClose()
+ }
+
+ /**
+ * 开启日志
+ */
+ private fun openXLog(context: Context, debug: Boolean) {
+ System.loadLibrary("c++_shared")
+ System.loadLibrary("marsxlog")
+ val pid = Process.myPid()
+ var processName: String? = null
+ val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ for (appProcess in am.runningAppProcesses) {
+ if (appProcess.pid == pid) {
+ processName = appProcess.processName
+ break
+ }
+ }
+ if (processName == null) {
+ return
+ }
+
+ val root = context.getExternalFilesDir("")
+ val logPath = "$root/arch/log"
+
+ val logFileName = if (processName.indexOf(":") == -1)
+ "Arch"
+ else
+ "Arch_${processName.substring(processName.indexOf(":") + 1)}"
+
+ if (debug) {
+ Xlog.appenderOpen(
+ Xlog.LEVEL_VERBOSE, Xlog.AppednerModeAsync, "", logPath,
+ "DEBUG_$logFileName", 5, ""
+ )
+ Xlog.setConsoleLogOpen(true)
+ } else {
+ Xlog.appenderOpen(
+ Xlog.LEVEL_INFO, Xlog.AppednerModeAsync, "", logPath,
+ logFileName, 3, ""
+ )
+ Xlog.setConsoleLogOpen(false)
+ }
+ Log.setLogImp(Xlog())
}
}
\ No newline at end of file
diff --git a/lib-architecture/src/main/java/com/zjy/architecture/clean/CoroutineUseCase.kt b/lib-architecture/src/main/java/com/zjy/architecture/clean/CoroutineUseCase.kt
new file mode 100644
index 0000000..18a4907
--- /dev/null
+++ b/lib-architecture/src/main/java/com/zjy/architecture/clean/CoroutineUseCase.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zjy.architecture.clean
+
+import com.zjy.architecture.data.Result
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Executes business logic synchronously or asynchronously using Coroutines.
+ */
+abstract class UseCase(private val coroutineDispatcher: CoroutineDispatcher) {
+
+ /** Executes the use case asynchronously and returns a [Result].
+ *
+ * @return a [Result].
+ *
+ * @param parameters the input parameters to run the use case with
+ */
+ suspend operator fun invoke(parameters: P): Result {
+ return try {
+ // Moving all use case's executions to the injected dispatcher
+ // In production code, this is usually the Default dispatcher (background thread)
+ // In tests, this becomes a TestCoroutineDispatcher
+ withContext(coroutineDispatcher) {
+ execute(parameters).let {
+ Result.Success(it)
+ }
+ }
+ } catch (e: Exception) {
+ Result.Error(e)
+ }
+ }
+
+ /**
+ * Override this to set the code to be executed.
+ */
+ @Throws(RuntimeException::class)
+ protected abstract suspend fun execute(parameters: P): R
+}
diff --git a/lib-architecture/src/main/java/com/zjy/architecture/ext/UtilExt.kt b/lib-architecture/src/main/java/com/zjy/architecture/ext/UtilExt.kt
new file mode 100644
index 0000000..d10053a
--- /dev/null
+++ b/lib-architecture/src/main/java/com/zjy/architecture/ext/UtilExt.kt
@@ -0,0 +1,17 @@
+package com.zjy.architecture.ext
+
+import com.tencent.mars.xlog.Log
+
+/**
+ * @author zhengjy
+ * @since 2020/07/16
+ * Description:
+ */
+inline fun T.tryWith(crossinline block: () -> R): R? {
+ return try {
+ block()
+ } catch (e: Exception) {
+ Log.e(T::class.java.simpleName, e.message)
+ null
+ }
+}
\ No newline at end of file
diff --git a/lib-architecture/src/main/java/com/zjy/architecture/mvvm/RequestDSL.kt b/lib-architecture/src/main/java/com/zjy/architecture/mvvm/RequestDSL.kt
index a3123f8..0a5d3d1 100644
--- a/lib-architecture/src/main/java/com/zjy/architecture/mvvm/RequestDSL.kt
+++ b/lib-architecture/src/main/java/com/zjy/architecture/mvvm/RequestDSL.kt
@@ -1,6 +1,6 @@
package com.zjy.architecture.mvvm
-import android.util.Log
+import com.tencent.mars.xlog.Log
import com.zjy.architecture.data.Result
import com.zjy.architecture.ext.handleException
import kotlinx.coroutines.CancellationException
@@ -64,8 +64,9 @@ fun LoadingViewModel.request(
}
}
} catch (e: Exception) {
+ Log.e("LoadingViewModel", "Exception: ${e.message}")
if (e is CancellationException) {
- Log.e("LoadingViewModel", "Exception: ${e.message}")
+ // do nothing
} else {
processError(onFail, handleException(e))
}
diff --git a/lib-architecture/src/test/java/com/zjy/architecture/ExampleUnitTest.kt b/lib-architecture/src/test/java/com/zjy/architecture/ExampleUnitTest.kt
deleted file mode 100644
index 89a9f6a..0000000
--- a/lib-architecture/src/test/java/com/zjy/architecture/ExampleUnitTest.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.zjy.architecture
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-}
diff --git a/lib-architecture/versions.gradle b/lib-architecture/versions.gradle
index 0ae26dc..595d859 100644
--- a/lib-architecture/versions.gradle
+++ b/lib-architecture/versions.gradle
@@ -2,6 +2,7 @@ ext.deps = [:]
def deps = [:]
def versions = [:]
versions.android_gradle_plugin = '4.0.0'
+versions.protobuf_gradle_plugin = '0.8.8'
versions.constraint_layout = "1.1.3"
versions.appcompat = "1.1.0"
@@ -32,6 +33,7 @@ versions.brvah = "3.0.4"
versions.moshi = "1.9.3"
versions.mmkv = "1.2.1"
versions.mars = "1.2.3"
+versions.proto = "3.9.1"
def build_versions = [:]
build_versions.min_sdk = 21
@@ -40,6 +42,7 @@ build_versions.compile_sdk = 29
ext.build_versions = build_versions
deps.android_gradle_plugin = "com.android.tools.build:gradle:$versions.android_gradle_plugin"
+deps.protobuf_gradle_plugin = "com.google.protobuf:protobuf-gradle-plugin:$versions.protobuf_gradle_plugin"
def support = [:]
support.design = "com.google.android.material:material:$versions.support"
@@ -151,5 +154,9 @@ def mars = [:]
mars.core = "com.tencent.mars:mars-core:$versions.mars"
mars.xlog = "com.tencent.mars:mars-xlog:$versions.mars"
deps.mars = mars
+// ProtoBuf
+deps.proto = "com.google.protobuf:protobuf-javalite:$versions.proto"
+deps.protoc = "com.google.protobuf:protoc:$versions.proto"
+deps.protoc_gen = "com.google.protobuf:protoc:$versions.proto"
ext.deps = deps
diff --git a/settings.gradle b/settings.gradle
index b70db3a..89b605b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,4 @@
+include ':chat'
rootProject.name='Arch'
include ':app'
include ':lib-architecture'