Merge branch 'dev' into dev_custom_map

This commit is contained in:
wangcongtao
2020-07-31 13:23:55 +08:00
73 changed files with 5754 additions and 10 deletions

View File

@@ -1,7 +1,13 @@
// 基于socket长链的push推送
project.dependencies {
implementation rootProject.ext.dependencies.modulepushbase
launcherImplementation rootProject.ext.dependencies.modulepush
independentImplementation rootProject.ext.dependencies.modulepushnoop
if (Boolean.valueOf(RELEASE)) {
implementation rootProject.ext.dependencies.modulepushbase
launcherImplementation rootProject.ext.dependencies.modulepush
independentImplementation rootProject.ext.dependencies.modulepushnoop
} else {
implementation project(":modules:mogo-module-push-base")
launcherImplementation project(":modules:mogo-module-push")
independentImplementation project(":modules:mogo-module-push-noop")
}
}

View File

@@ -67,10 +67,8 @@ public class MogoApplication extends AbsMogoApplication {
MogoModulePaths.addModule( new MogoModule( TanluConstants.TAG, TanluConstants.MODEL_NAME ) );
MogoModulePaths.addModule( new MogoModule( MogoServicePaths.PATH_SHARE, "ShareControl" ) );
MogoModulePaths.addModule( new MogoModule( EventPanelConstants.PATH_NAME,
EventPanelConstants.MODULE_NAME ) );
MogoModulePaths.addModule( new MogoModule( LeftPanelConst.PATH_NAME,
LeftPanelConst.MODULE_NAME ) );
MogoModulePaths.addModule( new MogoModule( EventPanelConstants.PATH_NAME, EventPanelConstants.MODULE_NAME ) );
MogoModulePaths.addModule( new MogoModule( LeftPanelConst.PATH_NAME, LeftPanelConst.MODULE_NAME ) );
MogoModulePaths.addBaseModule( new MogoModule( ServiceConst.PATH_REFRESH_STRATEGY, ServiceConst.PATH_REFRESH_STRATEGY ) );
MogoModulePaths.addBaseModule( new MogoModule( V2XConst.PATH_V2X_UI, V2XConst.PATH_V2X_UI ) );

View File

@@ -173,5 +173,14 @@ targetSdkVersion : 22,
// 基础服务实现
mogobaseservicesdk : "com.mogo.base:services-sdk:${MOGO_BASE_SERVICES_SDK_VERSION}",
mogobaseserviceapk : "com.mogo.base:services-apk:${MOGO_BASE_SERVICES_APK_VERSION}",
// google
googlezxing : "com.google.zxing:core:3.3.3",
litezxing : "com.google.zxing:litezxing:1.0.29.8",
// android - room
androidxroomruntime : "androidx.room:room-runtime:2.2.3",
androidxroomcompiler : "androidx.room:room-compiler:2.2.3",
androidxroomktx : "androidx.room:room-ktx:2.2.3",
]
}

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,47 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode Integer.valueOf(VERSION_CODE)
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()

View File

@@ -0,0 +1,3 @@
GROUP=com.mogo.module
POM_ARTIFACT_ID=module-push-base
VERSION_CODE=1

View File

@@ -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

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.module.push.base"></manifest>

View File

@@ -0,0 +1,7 @@
package com.mogo.module.push.base;
public class PushUIConstants {
public static final String NAME = "PUSH_UI";
public static final String PATH = "/push/ui";
public static final String Push_MESSAGE_ACTIVITY_PATH = "/push/ui/message"; //消息列表activity
}

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,4 @@
# 基于 socketserver 实现的push推送
---
目前,仅 launcher 实现推送,独立 app 不用

View File

@@ -0,0 +1,57 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode Integer.valueOf(VERSION_CODE)
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 小智语音,免唤醒词等服务
compileOnly rootProject.ext.dependencies.mogoserviceapi
compileOnly rootProject.ext.dependencies.arouter
kapt rootProject.ext.dependencies.aroutercompiler
if( Boolean.valueOf(RELEASE) ){
implementation rootProject.ext.dependencies.modulepushbase
} else {
implementation project(":modules:mogo-module-push-base")
}
}
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()

View File

@@ -0,0 +1,3 @@
GROUP=com.mogo.module
POM_ARTIFACT_ID=module-push-noop
VERSION_CODE=1

View File

@@ -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

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.module.push.noop"></manifest>

View File

@@ -0,0 +1,80 @@
package com.mogo.module.push.noop;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.mogo.map.listener.IMogoMapListener;
import com.mogo.map.location.IMogoLocationListener;
import com.mogo.map.marker.IMogoMarkerClickListener;
import com.mogo.map.navi.IMogoNaviListener;
import com.mogo.module.push.base.PushUIConstants;
import com.mogo.service.module.IMogoModuleLifecycle;
import com.mogo.service.module.IMogoModuleProvider;
@Route(path = PushUIConstants.PATH)
public class PushModuleProvider implements IMogoModuleProvider {
@Override
public Fragment createFragment(Context context, Bundle data) {
return null;
}
@Override
public View createView(Context context) {
return null;
}
@NonNull
@Override
public String getModuleName() {
return "";
}
@Override
public IMogoModuleLifecycle getCardLifecycle() {
return null;
}
@Override
public IMogoMapListener getMapListener() {
return null;
}
@Override
public int getType() {
return 0;
}
@Override
public IMogoNaviListener getNaviListener() {
return null;
}
@Override
public IMogoLocationListener getLocationListener() {
return null;
}
@Override
public IMogoMarkerClickListener getMarkerClickListener() {
return null;
}
@Override
public String getAppPackage() {
return " ";
}
@Override
public String getAppName() {
return " ";
}
@Override
public void init(final Context context) {
}
}

1
modules/mogo-module-push/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,12 @@
# 基于 socketserver 实现的push推送
---
目前,仅 launcher 实现推送,独立 app 不用
## launcher 在前台
通过launcher内部空白区域的弹层承载推送内容
## launcher 在后台
通过 windowmanger 方式承载推送内容

View File

@@ -0,0 +1,71 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode Integer.valueOf(VERSION_CODE)
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 小智语音,免唤醒词等服务
compileOnly rootProject.ext.dependencies.mogomap
compileOnly rootProject.ext.dependencies.mogoutils
compileOnly rootProject.ext.dependencies.mogocommons
compileOnly rootProject.ext.dependencies.mogoserviceapi
compileOnly rootProject.ext.dependencies.modulecommon
compileOnly rootProject.ext.dependencies.androidxconstraintlayout
compileOnly rootProject.ext.dependencies.arouter
compileOnly rootProject.ext.dependencies.aiassist
kapt rootProject.ext.dependencies.aroutercompiler
compileOnly rootProject.ext.dependencies.androidxrecyclerview
implementation rootProject.ext.dependencies.androidxappcompat
implementation rootProject.ext.dependencies.kotlinstdlibjdk7
implementation rootProject.ext.dependencies.androidxccorektx
implementation rootProject.ext.dependencies.litezxing
implementation rootProject.ext.dependencies.androidxroomruntime
implementation rootProject.ext.dependencies.androidxroomktx
kapt rootProject.ext.dependencies.androidxroomcompiler
if( Boolean.valueOf(RELEASE) ){
implementation rootProject.ext.dependencies.modulepushbase
} else {
implementation project(":modules:mogo-module-push-base")
}
}
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()

View File

@@ -0,0 +1,3 @@
GROUP=com.mogo.module
POM_ARTIFACT_ID=module-push
VERSION_CODE=1

View File

@@ -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

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.module.push">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application>
<activity
android:name=".activity.PushMessageActivity"
android:theme="@style/ModulePushMessageTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,20 @@
package com.mogo.module.push
object Config {
const val PUSH_TYPE = 100 //注册长连接类型
const val NEWS_ARRIVE = "news_arrive" //Push到达
const val NEWS_CARD_SHOW = "news_card_show"//push 展示
const val NEWS_CARD_DISAPPEAR = "news_card_disappear"// push 展示到期,自动消失
const val NEWS_CARD_CLICK = "news_card_click"//点击消息体
const val NEWS_CARD_SWIPE = "news_card_swipe"//划掉消息
const val NEWS_CARD_CLICK_BTN = "news_card_click_btn"//点击按钮
const val NEWS_HISTORY_OPEN = "news_history_open"//打开消息列表
const val NEWS_HISTORY_CLOSE = "news_history_close"//关闭消息列表
const val NEWS_HISTORY_ALL_CLEAR = "news_history_all_clear"//清除消息列表
const val NEWS_HISTORY_ONE_CLEAR = "news_history_one_clear"//清除消息历史中的消息
const val NEWS_HISTORY_ONE_CLICK = "news_history_one_click"//点击消息历史中的消息
}

View File

@@ -0,0 +1,89 @@
package com.mogo.module.push;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.mogo.map.listener.IMogoMapListener;
import com.mogo.map.location.IMogoLocationListener;
import com.mogo.map.marker.IMogoMarkerClickListener;
import com.mogo.map.navi.IMogoNaviListener;
import com.mogo.module.push.base.PushUIConstants;
import com.mogo.module.push.repository.PushRepository;
import com.mogo.module.push.utils.HandlerUtils;
import com.mogo.service.module.IMogoModuleLifecycle;
import com.mogo.service.module.IMogoModuleProvider;
@Route(path = PushUIConstants.PATH)
public class PushModuleProvider implements IMogoModuleProvider {
@Override
public Fragment createFragment(Context context, Bundle data) {
return null;
}
@Override
public View createView(Context context) {
return null;
}
@NonNull
@Override
public String getModuleName() {
return "";
}
@Override
public IMogoModuleLifecycle getCardLifecycle() {
return null;
}
@Override
public IMogoMapListener getMapListener() {
return null;
}
@Override
public int getType() {
return 0;
}
@Override
public IMogoNaviListener getNaviListener() {
return null;
}
@Override
public IMogoLocationListener getLocationListener() {
return null;
}
@Override
public IMogoMarkerClickListener getMarkerClickListener() {
return null;
}
@Override
public String getAppPackage() {
return " ";
}
@Override
public String getAppName() {
return " ";
}
@Override
public void init(final Context context) {
HandlerUtils.INSTANCE.getMBgHandler().post(new Runnable() {
@Override
public void run() {
PushRepository.Companion.init(context);
}
});
}
}

View File

@@ -0,0 +1,108 @@
package com.mogo.module.push.activity
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.alibaba.android.arouter.facade.annotation.Route
import com.mogo.module.push.Config
import com.mogo.module.push.base.PushUIConstants
import com.mogo.module.push.R
import com.mogo.module.push.adapter.PushMessageAdapter
import com.mogo.module.push.model.PushBean
import com.mogo.module.push.repository.PushRepository
import com.mogo.module.push.utils.AnalyticsUtils
import com.mogo.module.push.utils.HandlerUtils
import com.mogo.module.push.utils.startClearAnimator
import com.mogo.module.push.view.PushItemAnimator
import com.mogo.module.push.view.SwipeItemLayout
import com.mogo.module.push.viewmodel.MessageViewModel
import com.mogo.utils.UiThreadHandler
import kotlinx.android.synthetic.main.module_push_message_activity.*
@Route(path = PushUIConstants.Push_MESSAGE_ACTIVITY_PATH)
class PushMessageActivity : AppCompatActivity() {
private lateinit var viewModel: MessageViewModel
private var adapter = PushMessageAdapter()
private var clearing = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
setContentView(R.layout.module_push_message_activity)
AnalyticsUtils.track(Config.NEWS_HISTORY_OPEN)
module_push_activity_close.setOnClickListener {
AnalyticsUtils.track(Config.NEWS_HISTORY_CLOSE)
finish()
}
module_push_activity_clear.setOnClickListener {
AnalyticsUtils.track(Config.NEWS_HISTORY_ALL_CLEAR)
if (!clearing) {
clearing = true
startClearAnimator(module_push_activity_recycler_view) {
viewModel.deleteAll()
clearing = false
}
}
}
adapter.deletePushBean = object : PushMessageAdapter.PushAdapterListener {
override fun lastItemShow(show: Boolean) {
if (!show && !clearing) {
val size = viewModel.list?.size ?: 0
if (size > 0 && size < module_push_activity_recycler_view.childCount) {
return
}
}
module_push_activity_clear.visibility =
if (show) View.VISIBLE else View.GONE
}
override fun deleteBean(bean: PushBean, action: Boolean) {
if (clearing) return
viewModel.delete(bean)
if (action) {
AnalyticsUtils.track(Config.NEWS_HISTORY_ONE_CLICK, "title", bean.title)
finish()
} else {
AnalyticsUtils.track(Config.NEWS_HISTORY_ONE_CLEAR, "title", bean.title)
adapter.removeItem(bean)
if (adapter.datas?.size ?: 0 == 0) {
module_push_activity_not_data.visibility = View.VISIBLE
}
updateHistoryMessageCount()
}
}
}
module_push_activity_recycler_view.layoutManager = LinearLayoutManager(this)
module_push_activity_recycler_view.adapter = adapter
module_push_activity_recycler_view.itemAnimator = PushItemAnimator()
module_push_activity_recycler_view.addOnItemTouchListener(
SwipeItemLayout.OnSwipeItemTouchListener(this)
)
viewModel = MessageViewModel(object :
MessageViewModel.MessageListChange {
override fun messageListChange(list: MutableList<PushBean>?) {
runOnUiThread {
var size = list?.size ?: 0
adapter.datas = list
module_push_activity_not_data.visibility = if (size > 0) View.GONE else View.VISIBLE
updateHistoryMessageCount()
}
}
})
}
private fun updateHistoryMessageCount(){
HandlerUtils.mBgHandler.post{
var count = PushRepository.pushRepository.pushBeanDao.getAllCount()
UiThreadHandler.post {
module_push_activity_title.text = if (count > 0) "历史消息(${count}" else "历史消息"
}
}
}
override fun onResume() {
super.onResume()
adapter.notifyDataSetChanged()
}
}

View File

@@ -0,0 +1,127 @@
package com.mogo.module.push.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.mogo.module.push.R
import com.mogo.module.push.model.PushBean
import com.mogo.module.push.repository.PushRepository
import com.mogo.module.push.utils.dealSchema
import com.mogo.module.push.utils.stringConverterBitmap
import com.mogo.module.push.view.getApis
import com.mogo.service.imageloader.MogoImageView
import kotlin.math.abs
class PushMessageAdapter : RecyclerView.Adapter<PushMessageAdapter.MessageViewHolder>() {
interface PushAdapterListener {
fun deleteBean(bean: PushBean, action: Boolean)
fun lastItemShow(show: Boolean)
}
var onAttachStateChangeListener = object : View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(p0: View?) {
deletePushBean.lastItemShow(false)
}
override fun onViewAttachedToWindow(p0: View?) {
deletePushBean.lastItemShow(true)
}
}
lateinit var deletePushBean: PushAdapterListener
var datas: MutableList<PushBean>? = null
set(value) {
field = value
notifyDataSetChanged()
}
fun removeItem(bean: PushBean) {
datas?.let {
val position = it.indexOf(bean)
if (position >= 0) {
it.removeAt(position)
notifyItemRemoved(position)
}
if (itemCount == 0) {
deletePushBean.lastItemShow(false)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
return MessageViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.module_push_message_item,
parent,
false
)
)
}
override fun getItemCount(): Int {
return datas?.size ?: 0
}
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
holder.setPushBean(datas!![position], position)
}
inner class MessageViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val pushAppIcon: MogoImageView = view.findViewById(R.id.module_push_item_app_icon)
private val pushTitle: TextView = view.findViewById(R.id.module_push_item_title)
private val pushContent: TextView = view.findViewById(R.id.module_push_item_content)
private val pushImage: MogoImageView = view.findViewById(R.id.module_push_item_image)
private val pushTimer: TextView = view.findViewById(R.id.module_push_item_time)
private val pushDelete: TextView = view.findViewById(R.id.module_push_item_delete)
private val pushClick: View = view.findViewById(R.id.module_push_item_click)
fun setPushBean(bean: PushBean, position: Int) {
if (position == (datas?.size ?: 0) - 1) {
itemView.addOnAttachStateChangeListener(onAttachStateChangeListener)
} else {
itemView.removeOnAttachStateChangeListener(onAttachStateChangeListener)
}
pushDelete.setOnClickListener {
deletePushBean.deleteBean(bean, false)
}
if (!bean.mainSchema.isNullOrEmpty()) {
pushClick.setOnClickListener {
dealSchema(bean.mainSchema, itemView.context)
deletePushBean.deleteBean(bean, true)
}
} else {
pushClick.setOnClickListener(null)
}
getApis(itemView.context).imageLoaderApi.displayImage(bean.appIcon, pushAppIcon)
pushTitle.text = bean.title
pushContent.text = bean.content
pushContent.visibility = if (bean.content.isNullOrEmpty()) View.GONE else View.VISIBLE
if (bean.QRCode.isNullOrEmpty() && bean.imageUrl.isNotEmpty()) {
getApis(itemView.context).imageLoaderApi.displayImage(bean.imageUrl, pushImage)
}
if (!bean.QRCode.isNullOrEmpty()) {
pushImage.setImageBitmap(
stringConverterBitmap(
bean.QRCode,
pushImage.context.resources.getDimensionPixelSize(R.dimen.module_push_message_item_image_size),
pushImage.context.resources.getDimensionPixelSize(R.dimen.module_push_message_item_image_size)
)
)
}
val diff = abs((System.currentTimeMillis() - bean.timestamp) / 1000).toInt()
pushTimer.text = when {
diff == 0 -> "现在"
diff < 60 -> "${diff}秒前"
diff < 60 * 60 -> "${diff / 60}分钟前"
diff < 60 * 60 * 24 -> "${diff / 60 / 60}小时前"
else -> "${diff / 60 / 60 / 24}天前"
}
}
}
}

View File

@@ -0,0 +1,25 @@
package com.mogo.module.push.dao
import androidx.room.*
import com.mogo.module.push.model.PushBean
@Dao
interface PushBeanDao {
@Query("SELECT * FROM pushBean ORDER BY timestamp DESC")
fun getAll(): MutableList<PushBean>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(vararg bean: PushBean)
@Delete
fun delete(vararg bean: PushBean)
@Query("DELETE FROM pushBean")
fun deleteAll()
@Query("SELECT count(1) FROM pushBean")
fun getAllCount(): Int
@Query("DELETE FROM pushBean WHERE timestamp IN (SELECT MIN(timestamp) FROM pushBean)")
fun deleteMin()
}

View File

@@ -0,0 +1,10 @@
package com.mogo.module.push.dao
import androidx.room.Database
import androidx.room.RoomDatabase
import com.mogo.module.push.model.PushBean
@Database(entities = [PushBean::class], version = 1)
abstract class PushBeanDatabase : RoomDatabase() {
abstract fun pushBeanDao(): PushBeanDao
}

View File

@@ -0,0 +1,54 @@
package com.mogo.module.push.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.google.gson.annotations.SerializedName
@Entity
data class PushBean(
@Ignore
val speedLimit: Int = 0, //超过速度后延迟显示
@Ignore
var showTimeout: Int = 0, //显示等待时长
@Ignore
var showTimeoutShadow: Int = 0, // 显示等待时长备份
@ColumnInfo(name = "icon")
var appIcon: String = "", //目标app icon图标地址
@ColumnInfo(name = "title")
var title: String = "", //标题
@ColumnInfo(name = "content")
var content: String = "", //详细内容
@ColumnInfo(name = "image")
var imageUrl: String = "", //图片地址
@ColumnInfo(name = "qr")
var QRCode: String = "", //二维码地址
@Ignore
val tts: String = "", //语音播报词
@ColumnInfo(name = "scheme")
var mainSchema: String = "", //schema跳转协议
@Ignore
val mainVoiceCmd: List<String>? = null, //触发主schema 命令词
@Ignore
val cancelVoiceCmd: List<String>? = null, //隐藏当前push命令词
@Ignore
val buttons: List<Button>? = null, //底部buttons列表
@PrimaryKey
var timestamp: Long = System.currentTimeMillis()
) {
override fun equals(other: Any?): Boolean {
if (other is PushBean) {
return timestamp == other.timestamp
}
return super.equals(other)
}
}
data class Button(
val text: String = "", // button名称
val action: String = "", //scheme 协议
val voiceCmd: List<String>? = null //注册命令词
)

View File

@@ -0,0 +1,233 @@
package com.mogo.module.push.repository
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.room.Room
import com.mogo.module.push.Config
import com.mogo.module.push.base.PushUIConstants
import com.mogo.module.push.dao.PushBeanDatabase
import com.mogo.module.push.model.PushBean
import com.mogo.module.push.utils.AnalyticsUtils
import com.mogo.module.push.utils.HandlerUtils
import com.mogo.module.push.view.getApis
import com.mogo.module.push.viewmodel.PushViewModel
import com.mogo.service.connection.IMogoOnMessageListener
import com.mogo.service.statusmanager.IMogoStatusChangedListener
import com.mogo.service.statusmanager.StatusDescriptor
import com.mogo.utils.logger.Logger
import java.util.*
import kotlin.collections.HashSet
class PushRepository(mContext: Context) {
companion object {
private lateinit var appContext: Context
fun init(context: Context) {
appContext = context.applicationContext
pushRepository
}
val globalContext: Context by lazy {
appContext
}
val pushRepository: PushRepository by lazy {
PushRepository(appContext)
}
@JvmField
val TAG: String = "PushRepository.kt"
}
// 被中断的push消息仅再次展示一次
private val singleRePushSet = HashSet<PushBean>()
private val pushViewModel: PushViewModel = PushViewModel(mContext, this)
private val pushBeanQueue: Queue<PushBean> = LinkedList()
private val statusManager = getApis(mContext).statusManagerApi
private val msgCenter = getApis(mContext).msgCenterApi
val pushBeanDao by lazy {
Room.databaseBuilder(appContext, PushBeanDatabase::class.java, "database_push").build()
.pushBeanDao()
}
private val locationClient =
getApis(mContext).mapServiceApi.getSingletonLocationClient(appContext)
private val mHandler = Handler(Looper.getMainLooper())
private val statusChangedListener: IMogoStatusChangedListener =
IMogoStatusChangedListener { sd, open ->
Log.d("PushRepository", "sd = $sd open = $open")
}
init {
// 注册push通道监听 type 100时为push消息
getApis(mContext).getSocketManagerApi(mContext)
.registerOnMessageListener(Config.PUSH_TYPE, object : IMogoOnMessageListener<PushBean> {
override fun target(): Class<PushBean> {
return PushBean::class.java
}
override fun onMsgReceived(bean: PushBean?) {
Log.d("PushRepository", "pushBean = $bean")
if (bean != null) {
AnalyticsUtils.track(Config.NEWS_ARRIVE, "title", bean.title)
if (bean.mainSchema == null || bean.mainSchema == "") {
bean.mainSchema = ""
}
if (bean.imageUrl == null || bean.imageUrl == "null") {
bean.imageUrl = ""
}
if (bean.appIcon == null || bean.appIcon == "null") {
bean.appIcon = ""
}
pushBeanQueue.offer(bean)
}
startIterate()
}
})
HandlerUtils.mBgHandler.postDelayed({
updateMsgNum()
}, 5000)
}
private fun updateMsgNum() {
val num = pushBeanDao.getAllCount()
if (num > 100) { //历史消息最多保留100条
pushBeanDao.deleteMin()
updateMsgNum()
return
}
mHandler.post {
msgCenter.setMsgStatus(num > 0, num)
}
}
private fun startIterate() {
Log.d("PushRepository", "startIterate ${pushViewModel.pushBean}")
if (!pushViewModel.isAddWindow()) {
val bean = pushBeanQueue.peek()
if (bean != null) {
if (needDelay(bean)) {
mHandler.removeCallbacks(delayRunnable)
mHandler.postDelayed(delayRunnable, 15000)
return
}
} else {
return
}
iterateNext()
}
}
/**
* 缓存被中断的push消息
*/
fun push(pushBean: PushBean) {
if (singleRePushSet.contains(pushBean)) {
return
}
Logger.d(TAG, "保存待下一次开启")
singleRePushSet.add(pushBean)
pushBeanQueue.offer(pushBean)
}
private var delayRunnable = {
startIterate()
}
private inline fun needDelay(bean: PushBean): Boolean {
if (bean == null) {
return false
}
if (locationClient.lastKnowLocation != null) {
if (bean.speedLimit > 0 && bean.speedLimit <= locationClient.lastKnowLocation.speed * 18 / 5) {
Log.d("PushRepository", "speedLimit" + locationClient.lastKnowLocation.speed)
return true
}
}
if (statusManager.isV2XShow) {
return true
}
if (statusManager.isVoiceShow) {
return true
}
return false
}
fun iterateNext(needSave: Boolean = false) {
if (needSave && pushViewModel.pushBean != null) {
val bean = pushViewModel.pushBean!!
HandlerUtils.mBgHandler.post {
pushBeanDao.insertAll(bean)
updateMsgNum()
}
}
try {
val nextBean = pushBeanQueue.peek()
if (nextBean == null) {
pushViewModel.pushBean = nextBean
return
}
if (needDelay(nextBean)) {
mHandler.removeCallbacksAndMessages(null)
mHandler.postDelayed({
iterateNext(needSave)
}, 15000)
return
}
pushViewModel.pushBean = pushBeanQueue.poll()
} catch (e: Exception) {
Logger.e(TAG, e, "")
}
if (pushViewModel.pushBean != null) {
statusManager.registerStatusChangedListener(
PushUIConstants.PATH,
StatusDescriptor.V2X_UI,
statusChangedListener
)
statusManager.registerStatusChangedListener(
PushUIConstants.PATH,
StatusDescriptor.VOICE_UI,
statusChangedListener
)
} else {
statusManager.unregisterStatusChangedListener(
PushUIConstants.PATH,
StatusDescriptor.VOICE_UI,
statusChangedListener
)
statusManager.unregisterStatusChangedListener(
PushUIConstants.PATH,
StatusDescriptor.V2X_UI,
statusChangedListener
)
}
}
fun setPushUIShow(show: Boolean) {
statusManager.setPushUIShow(PushUIConstants.PATH, show)
}
fun delete(bean: PushBean) {
HandlerUtils.mBgHandler.post {
pushBeanDao.delete(bean)
updateMsgNum()
}
}
fun deleteAll() {
HandlerUtils.mBgHandler.post {
pushBeanDao.deleteAll()
mHandler.post {
msgCenter.setMsgStatus(false, 0)
}
}
}
fun getAll(): MutableList<PushBean> {
return pushBeanDao.getAll()
}
}

View File

@@ -0,0 +1,22 @@
package com.mogo.module.push.utils
import android.util.ArrayMap
import com.alibaba.android.arouter.launcher.ARouter
import com.mogo.module.push.repository.PushRepository
import com.mogo.service.MogoServicePaths
import com.mogo.service.analytics.IMogoAnalytics
object AnalyticsUtils {
private val Analytics =
ARouter.getInstance().build(MogoServicePaths.PATH_UTILS_ANALYTICS).navigation(PushRepository.globalContext) as IMogoAnalytics
fun track(event: String, vararg keyValue: String) {
val map = ArrayMap<String, Any>()
if (keyValue.isNotEmpty()) {
for (i in 0..keyValue.size - 2 step 2) {
map[keyValue[i]] = keyValue[i + 1]
}
}
Analytics.track(event, map)
}
}

View File

@@ -0,0 +1,25 @@
package com.mogo.module.push.utils
import android.view.View
import android.view.ViewGroup
import androidx.core.view.get
import androidx.core.view.isNotEmpty
fun startClearAnimator(root: ViewGroup, method: () -> Unit) {
if (root.isNotEmpty()) {
var view: View
var size = root.childCount - 1
for (i in size downTo 0) {
view = root[i]
view.animate().translationX(-view.width.toFloat()).apply {
if (i == 0) {
withEndAction(method)
}
duration = 200
startDelay = 100 * (size - i).toLong()
}
}
} else {
method()
}
}

View File

@@ -0,0 +1,14 @@
package com.mogo.module.push.utils
import android.os.Handler
import android.os.HandlerThread
object HandlerUtils {
private val handlerThread = HandlerThread("push_ui_thread")
val mBgHandler: Handler
init {
handlerThread.start()
mBgHandler = Handler(handlerThread.looper)
}
}

View File

@@ -0,0 +1,130 @@
package com.mogo.module.push.utils
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import android.util.Log
private const val ACTION = "AUTONAVI_STANDARD_BROADCAST_RECV"
private const val KEY_TYPE = "KEY_TYPE"
private const val KEYWORDS = "KEYWORDS"
private const val SOURCE_APP = "SOURCE_APP"
private const val VALUE_TYPE = "10036"
private const val MAP_SCHEME = "map"
private const val WELFARE_SCHEME = "welfare"
private const val BROADCAST = "broadcast"
private const val PACKAGE = "package_name"
fun dealSchema(content: String, context: Context): Boolean {
if (!content.isNullOrEmpty()) {
val uri = Uri.parse(content)
if (uri != null && !uri.scheme.isNullOrEmpty()) {
if (mapIsValid(content) && startMap(
context,
uri
)
) {
return true
}
if (checkBroadcastNeedSend(context, uri)) {
return true
}
if (schemeIsValid(context, uri)) {
return startActivityForUri(context, uri)
}
}
}
return false
}
private fun checkBroadcastNeedSend(context: Context, uri: Uri): Boolean {
if (BROADCAST == uri.scheme) {
if (!uri.host.isNullOrEmpty()) {
val intent = Intent(uri.host)
intent.putExtra("uri", uri.toString())
val packageName = uri.getQueryParameter(PACKAGE)
val set = uri.queryParameterNames
if (!set.isNullOrEmpty()) {
for (key in set) {
if(key.isNotEmpty()) {
intent.putExtra(key, uri.getQueryParameter(key) ?: "")
}
}
}
if (!packageName.isNullOrEmpty()) {
intent.setPackage(packageName)
}
context.sendBroadcast(intent)
return true
}
}
return false
}
private fun startActivityForUri(context: Context, uri: Uri): Boolean {
return try {
val intent = Intent()
intent.action = "android.intent.action.VIEW"
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.data = uri
context.startActivity(intent)
true
} catch (e: Exception) {
false
}
}
private fun mapIsValid(uri: String): Boolean {
if (!TextUtils.isEmpty(uri)) {
val uriTmp = Uri.parse(uri)
return MAP_SCHEME == uriTmp.scheme && VALUE_TYPE.equals(
uriTmp.getQueryParameter(KEY_TYPE.toLowerCase())!!,
ignoreCase = true
)
}
return false
}
private fun welfareIsValid(uri: String): Boolean {
if (!TextUtils.isEmpty(uri)) {
val uriTmp = Uri.parse(uri)
return WELFARE_SCHEME == uriTmp.scheme
}
return false
}
private fun startMap(context: Context, uri: Uri): Boolean {
val intent = Intent(ACTION)
return try {
intent.putExtra(
KEY_TYPE, Integer.valueOf(
uri.getQueryParameter(
KEY_TYPE.toLowerCase()
)!!
)
)
intent.putExtra(KEYWORDS, uri.getQueryParameter(KEYWORDS.toLowerCase()))
intent.putExtra(SOURCE_APP, uri.getQueryParameter(SOURCE_APP.toLowerCase()))
context.sendBroadcast(intent)
true
} catch (e: NumberFormatException) {
Log.e("CustomMessageInfo", e.localizedMessage)
false
}
}
private fun schemeIsValid(context: Context, uri: Uri): Boolean {
val packageManager = context.packageManager
val intent = Intent(Intent.ACTION_VIEW, uri)
val activities = packageManager.queryIntentActivities(intent, 0)
var isValid = false
try {
isValid = activities.isNotEmpty()
} catch (e: Exception) {
e.printStackTrace()
}
return isValid
}

View File

@@ -0,0 +1,33 @@
package com.mogo.module.push.utils
import android.graphics.Bitmap
import com.google.zxing.EncodeHintType
import java.util.*
import com.google.zxing.BarcodeFormat
import com.google.zxing.qrcode.QRCodeWriter
fun stringConverterBitmap(address: String, width: Int, height: Int): Bitmap? {
val hints = Hashtable<EncodeHintType, String>()
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
val bitMatrix = QRCodeWriter().encode(
address,
BarcodeFormat.QR_CODE, width, height, hints
)
val pixels = IntArray(width * height)
//下面这里按照二维码的算法,逐个生成二维码的图片,
//两个for循环是图片横列扫描的结果
for (y in 0 until height) {
for (x in 0 until width) {
if (bitMatrix.get(x, y)) {
pixels[y * width + x] = -0x1000000
} else {
pixels[y * width + x] = -0x1
}
}
}
//生成二维码图片的格式使用ARGB_8888
var bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
return bitmap
}

View File

@@ -0,0 +1,563 @@
package com.mogo.module.push.view
import android.animation.Animator
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.PixelFormat
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.*
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.Scroller
import android.widget.TextView
import androidx.annotation.LayoutRes
import androidx.core.animation.doOnEnd
import androidx.core.view.isVisible
import com.elegant.analytics.utils.Logger
import com.mogo.commons.voice.AIAssist
import com.mogo.module.push.Config
import com.mogo.module.push.R
import com.mogo.module.push.model.PushBean
import com.mogo.module.push.utils.AnalyticsUtils
import com.mogo.module.push.utils.stringConverterBitmap
import com.mogo.module.push.view.roundimage.RoundedImageView
import com.mogo.module.push.viewmodel.PushViewModel
import com.mogo.service.windowview.IMogoTopViewManager
import com.mogo.service.windowview.IMogoTopViewStatusListener
import com.mogo.utils.ResourcesHelper
import com.mogo.utils.ThreadPoolService
import com.mogo.utils.UiThreadHandler
import com.mogo.utils.glide.GlideApp
class FloatView constructor(
private val pushViewModel: PushViewModel,
private val context: Context
) {
companion object {
const val TYPE_TOP_VIEW = 1
const val TYPE_WINDOW_MANAGER = 2
const val TAG: String = "FloatView.kt"
}
interface PushViewController {
fun show(bean: PushBean?)
fun hide()
fun timer(time: Int)
fun inflateView(@LayoutRes layoutId: Int)
}
open abstract inner class PushView(context: Context) : FrameLayout(context),
PushViewController {
lateinit var appIcon: ImageView
lateinit var titleIconContainer: View
lateinit var pushTitle: TextView
lateinit var pushImage: RoundedImageView
lateinit var pushContent: TextView
lateinit var pushTimer: TextView
lateinit var pushButtonLeft: TextView
lateinit var pushButtonRight: TextView
lateinit var pushButton: View
override fun inflateView(layoutId: Int) {
LayoutInflater.from(context).inflate(layoutId, this, true)
appIcon = findViewById(R.id.module_push_app_icon)
pushTitle = findViewById(R.id.module_push_title)
pushImage = findViewById(R.id.module_push_image)
pushContent = findViewById(R.id.module_push_content)
pushButtonLeft = findViewById(R.id.module_push_button_left)
pushButtonRight = findViewById(R.id.module_push_button_right)
pushTimer = findViewById(R.id.module_push_timer)
titleIconContainer = findViewById(R.id.module_push_app_icon_title)
pushButton = findViewById(R.id.module_push_buttons)
setOnClickListener {
pushViewModel.dealCmd(PushViewModel.VOICE_ACTION_PUSH_MAIN, "1")
turnNextMessage()
}
pushButtonLeft.setOnClickListener {
pushViewModel.dealCmd(PushViewModel.VOICE_ACTION_PUSH_LEFT, "1")
turnNextMessage()
}
pushButtonRight.setOnClickListener {
pushViewModel.dealCmd(PushViewModel.VOICE_ACTION_PUSH_RIGHT, "1")
turnNextMessage()
}
}
private fun turnNextMessage() {
pushViewModel?.pushBean?.apply {
showTimeout = 0
updateTimer()
}
}
fun hasButtons(bean: PushBean?): Boolean {
bean?.buttons?.forEach {
if (!it?.text?.isNullOrEmpty()) {
return true
}
}
return false
}
fun hasTextContent(bean: PushBean?): Boolean =
bean?.content?.isNullOrEmpty()?.not() ?: false
fun hasImgContent(bean: PushBean?): Boolean = bean?.QRCode?.isNullOrEmpty()?.not() ?: false
open fun setBean(bean: PushBean) {
// app icon
if (!bean.appIcon.isNullOrEmpty()) {
appIcon.visible()
GlideApp.with(this).load(bean.appIcon).into(appIcon)
} else {
appIcon.gone()
}
// title
pushTitle.text = bean.title
// decrease timer
pushTimer.text = if (bean.showTimeout > 99) "" else "${bean.showTimeout}s"
// image
if (bean.imageUrl.isNullOrEmpty() && bean.QRCode.isNullOrEmpty()) {
pushImage.gone()
} else if (!bean.imageUrl.isNullOrEmpty()) {
var params = pushImage.layoutParams
params.width = getImgWidth()
params.height = getImgHeight()
pushImage.layoutParams = params
pushImage.visible()
GlideApp.with(this).load(bean.imageUrl).into(pushImage)
} else if (!bean.QRCode.isNullOrEmpty()) {
var params = pushImage.layoutParams
params.width = getQrImgWidth()
params.height = getQrImgHeight()
pushImage.layoutParams = params
ThreadPoolService.execute {
val bmp = stringConverterBitmap(
bean.QRCode,
getQrImgWidth(),
getQrImgHeight()
)
UiThreadHandler.post {
pushImage.setImageBitmap(bmp)
pushImage.visible()
}
}
}
// button
pushButton.gone()
pushButtonLeft.gone()
pushButtonRight.gone()
if (!bean.buttons.isNullOrEmpty()) {
if (bean.buttons[0].text.isNotEmpty()) {
pushButton.visible()
pushButtonLeft.text = bean.buttons[0].text
pushButtonLeft.visible()
}
if (bean.buttons.size > 1 && bean.buttons[1].text.isNotEmpty()) {
pushButtonRight.text = bean.buttons[1].text
pushButtonRight.visible()
}
}
// content
if (bean.content.isNullOrEmpty()) {
pushContent.gone()
} else {
pushContent.text = bean.content
pushContent.visible()
}
// tts
if (!bean?.tts?.isNullOrEmpty()) {
AIAssist.getInstance(context).speakTTSVoice(bean.tts)
}
}
abstract fun getImgWidth(): Int
abstract fun getImgHeight(): Int
abstract fun getQrImgWidth(): Int
abstract fun getQrImgHeight(): Int
override fun timer(time: Int) {
Logger.d(TAG, "time = $time")
pushTimer.text = "${time}s"
}
override fun show(bean: PushBean?) {
isAddWindow = true
uiHandler.removeCallbacks(delayClosePush)
}
override fun hide() {
isAddWindow = false
}
}
open inner class PushViewInTopView(context: Context) : PushView(context) {
private val mTopViewManager: IMogoTopViewManager = getApis(context).topViewManager
init {
inflateView(R.layout.module_push_item)
}
private var topViewStatusListener = object : IMogoTopViewStatusListener {
override fun onViewRemoved(view: View?) {
isAddWindow = false
if (pushViewModel?.pushBean?.showTimeout ?: 0 > 0) {
if (getApis(context).statusManagerApi.isV2XShow) {
// 被中断的消息,需要再次被显示一次
uiHandler.removeCallbacks(delayClosePush)
pushViewModel.push()
pushViewModel.pushMessageFinish()
return
}
}
}
override fun onViewAdded(view: View?) {
if (pushViewModel.pushBean != null) {
startClosePush()
}
}
override fun beforeViewRemoveAnim(view: View?) {
}
override fun beforeViewAddAnim(view: View?) {
}
}
override fun show(bean: PushBean?) {
super.show(bean)
mLastVisibleType = TYPE_TOP_VIEW
mTopViewManager.addView(this, topViewStatusListener)
setBean(bean!!)
}
override fun hide() {
super.hide()
mTopViewManager.removeView(this)
}
override fun getImgWidth(): Int =
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_width)
override fun getImgHeight(): Int =
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_height)
override fun getQrImgWidth(): Int =
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_height)
override fun getQrImgHeight(): Int =
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_height)
}
inner class PushViewInWindowView(context: Context) : PushView(context), View.OnTouchListener {
private val mContentContainer: View
private val mWindowManager =
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val params = WindowManager.LayoutParams()
init {
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
params.flags = (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
or WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE
or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
params.width = WindowManager.LayoutParams.WRAP_CONTENT
params.height = WindowManager.LayoutParams.WRAP_CONTENT
params.gravity = Gravity.LEFT or Gravity.BOTTOM
params.format = PixelFormat.TRANSLUCENT
params.x = context.resources.getDimensionPixelSize(R.dimen.module_push_window_x)
params.y = context.resources.getDimensionPixelSize(R.dimen.module_push_window_x)
if (Build.VERSION.SDK_INT > 25) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
}
inflateView(R.layout.module_push_item_vertical)
mContentContainer = findViewById(R.id.module_push_content_container)
setOnTouchListener(this)
}
override fun show(bean: PushBean?) {
super.show(bean)
mLastVisibleType = TYPE_WINDOW_MANAGER
setBean(bean!!)
try {
mWindowManager.addView(this, params)
} catch (e: Exception) {
mWindowManager.updateViewLayout(this, params)
}
translationXAnimation(
-ResourcesHelper.getDimension(context, R.dimen.module_push_ui_width_vertical),
0f
) {
if (pushViewModel.pushBean != null) {
startClosePush()
}
}
}
private fun translationXAnimation(
from: Float,
to: Float,
doOnEnd: (animator: Animator) -> Unit
) {
val transitionXAnimator: ObjectAnimator =
ObjectAnimator.ofFloat(
this,
View.TRANSLATION_X,
from,
to
)
transitionXAnimator.duration = 200
transitionXAnimator.doOnEnd(doOnEnd)
transitionXAnimator.start()
}
override fun setBean(bean: PushBean) {
super.setBean(bean)
var paddingBottom: Int = 0
if (pushButton.isVisible) {
paddingBottom =
context.resources.getDimensionPixelSize(R.dimen.module_push_content_paddingBottom_vertical)
}
mContentContainer.setPadding(0, 0, 0, paddingBottom)
}
override fun hide() {
super.hide()
translationXAnimation(
this.x,
-ResourcesHelper.getDimension(context, R.dimen.module_push_ui_width_vertical)
) {
Logger.d(TAG, "here")
this.x = 0f
mWindowManager.removeViewImmediate(this)
}
}
override fun onTouch(v: View?, ev: MotionEvent?): Boolean {
if (mLastVisibleType != TYPE_WINDOW_MANAGER) {
return false
}
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
needInterceptClick = false
startX = ev.x
}
MotionEvent.ACTION_MOVE -> {
moveX = startX - ev.x
scrollBy(moveX.toInt(), 0)
startX = ev.x
if (scrollX < 0) {
scrollTo(0, 0)
}
if (!needInterceptClick && scrollX > 20) {
needInterceptClick = true
}
invalidate()
}
MotionEvent.ACTION_UP -> {
if (scrollX > 0) {
mScroller.startScroll(scrollX, 0, width - scrollX, 0)
invalidate()
return true
}
if (needInterceptClick) {
return true
}
}
else -> {
startX = 0f
moveX = 0f
}
}
return false
}
override fun computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.currX, mScroller.currY)
invalidate()
} else {
if (mScroller.currX == 0) {
return
}
mScroller.finalX = 0
removeCallbacks(delayClosePush)
if (isAddWindow) {
if (currentBean != null) {
AnalyticsUtils.track(Config.NEWS_CARD_SWIPE, "trigger_type", "1")
}
mWindowManager.removeView(this)
isAddWindow = false
}
pushViewModel.pushBean?.showTimeout = 0
updateTimer()
}
}
override fun getImgWidth(): Int =
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_width_vertical)
override fun getImgHeight(): Int =
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_height_vertical)
override fun getQrImgWidth(): Int =
context.resources.getDimensionPixelSize(R.dimen.module_push_image_qr_size_vertical)
override fun getQrImgHeight(): Int =
context.resources.getDimensionPixelSize(R.dimen.module_push_image_qr_size_vertical)
}
private val delayClosePush: Runnable
private var isAddWindow = false
private val uiHandler = Handler(Looper.getMainLooper())
private var startX = 0f
private var moveX = 0f
private val mScroller: Scroller = Scroller(context)
private var needInterceptClick = false
private var pause = false
private var currentBean: PushBean? = null
private var mLastVisibleType = -1
private var pushViewController: PushViewController? = null
init {
delayClosePush = Runnable {
updateTimer()
}
}
fun pushBeanChanged(bean: PushBean?) {
uiHandler.post {
uiHandler.removeCallbacks(delayClosePush)
if (bean == null) {
if (currentBean != null) {
AnalyticsUtils.track(Config.NEWS_CARD_DISAPPEAR, "title", currentBean!!.title)
}
hide()
} else {
show(bean)
AnalyticsUtils.track(Config.NEWS_CARD_SHOW, "title", bean.title)
}
currentBean = bean
}
}
private fun updateTimer() {
uiHandler.removeCallbacks(delayClosePush)
var time = pushViewModel.pushBean?.showTimeout ?: 0
if (time > 0) {
pushViewModel.pushBean!!.showTimeout--
pushViewController?.timer(time)
uiHandler.postDelayed(delayClosePush, 1000)
} else {
pushViewController?.timer(0)
pushViewModel.pushMessageFinish(true)
}
}
private fun show(bean: PushBean) {
if (isAddWindow) {
if (getApis(context).statusManagerApi.isMainPageOnResume) {
if (mLastVisibleType != TYPE_TOP_VIEW) {
hide()
(pushViewController as View).postDelayed({
show(bean)
}, 750L)
} else {
showByTopView(bean)
}
} else {
if (mLastVisibleType != TYPE_WINDOW_MANAGER) {
hide()
(pushViewController as View).postDelayed({
show(bean)
}, 750L)
} else {
showByWindowManager(bean)
}
}
startClosePush()
} else {
if (getApis(context).statusManagerApi.isMainPageOnResume) {
showByTopView(bean)
} else {
showByWindowManager(bean)
}
}
}
private fun showByTopView(bean: PushBean) {
if (pushViewController !is PushViewInTopView) {
pushViewController = PushViewInTopView(context)
}
pushViewController?.show(bean)
}
private fun showByWindowManager(bean: PushBean?) {
if (pushViewController !is PushViewInWindowView) {
pushViewController = PushViewInWindowView(context)
}
pushViewController?.show(bean)
}
private fun startClosePush() {
uiHandler.removeCallbacks(delayClosePush)
uiHandler.postDelayed(
delayClosePush,
1000L
)
}
fun hide() {
if (!isAddWindow) {
return
}
try {
pushViewController?.hide()
} catch (e: Exception) {
e.printStackTrace()
}
}
fun pauseTimer(on: Boolean) {
if (on) {
uiHandler.removeCallbacks(delayClosePush)
} else {
updateTimer()
uiHandler.post {
if (on) {
pause = true
uiHandler.removeCallbacks(delayClosePush)
} else if (pause) {
pause = false
updateTimer()
}
}
}
}
fun isAddWindow(): Boolean = isAddWindow
}

View File

@@ -0,0 +1,40 @@
package com.mogo.module.push.view
import android.content.Context
import android.view.View
import com.alibaba.android.arouter.launcher.ARouter
import com.mogo.service.IMogoServiceApis
import com.mogo.service.MogoServicePaths
/**
* @author congtaowang
* @since 2020/6/28
*
* 描述
*/
private var apis: IMogoServiceApis? = null
fun getApis(context: Context): IMogoServiceApis {
if (apis == null) {
apis = ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS).navigation(context) as IMogoServiceApis;
}
return apis!!
}
fun View.gone() {
if (this.visibility != View.GONE) {
this.visibility = View.GONE
}
}
fun View.visible() {
if (this.visibility != View.VISIBLE) {
this.visibility = View.VISIBLE
}
}
fun View.invisible() {
if (this.visibility != View.INVISIBLE) {
this.visibility = View.INVISIBLE
}
}

View File

@@ -0,0 +1,657 @@
package com.mogo.module.push.view;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.view.View;
import android.view.ViewPropertyAnimator;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import java.util.ArrayList;
import java.util.List;
/**
* This implementation of {@link RecyclerView.ItemAnimator} provides basic
* animations on remove, add, and move events that happen to the items in
* a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
*
* @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
*/
public class PushItemAnimator extends SimpleItemAnimator {
private static final boolean DEBUG = false;
private static TimeInterpolator sDefaultInterpolator;
private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>();
private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>();
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
ArrayList<ArrayList<RecyclerView.ViewHolder>> mAdditionsList = new ArrayList<>();
ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
ArrayList<RecyclerView.ViewHolder> mAddAnimations = new ArrayList<>();
ArrayList<RecyclerView.ViewHolder> mMoveAnimations = new ArrayList<>();
ArrayList<RecyclerView.ViewHolder> mRemoveAnimations = new ArrayList<>();
ArrayList<RecyclerView.ViewHolder> mChangeAnimations = new ArrayList<>();
private static class MoveInfo {
public RecyclerView.ViewHolder holder;
public int fromX, fromY, toX, toY;
MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
this.holder = holder;
this.fromX = fromX;
this.fromY = fromY;
this.toX = toX;
this.toY = toY;
}
}
private static class ChangeInfo {
public RecyclerView.ViewHolder oldHolder, newHolder;
public int fromX, fromY, toX, toY;
private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) {
this.oldHolder = oldHolder;
this.newHolder = newHolder;
}
ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
int fromX, int fromY, int toX, int toY) {
this(oldHolder, newHolder);
this.fromX = fromX;
this.fromY = fromY;
this.toX = toX;
this.toY = toY;
}
@Override
public String toString() {
return "ChangeInfo{"
+ "oldHolder=" + oldHolder
+ ", newHolder=" + newHolder
+ ", fromX=" + fromX
+ ", fromY=" + fromY
+ ", toX=" + toX
+ ", toY=" + toY
+ '}';
}
}
@Override
public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
// nothing to animate
return;
}
// First, remove stuff
for (RecyclerView.ViewHolder holder : mPendingRemovals) {
animateRemoveImpl(holder);
}
mPendingRemovals.clear();
// Next, move stuff
if (movesPending) {
final ArrayList<MoveInfo> moves = new ArrayList<>();
moves.addAll(mPendingMoves);
mMovesList.add(moves);
mPendingMoves.clear();
Runnable mover = new Runnable() {
@Override
public void run() {
for (MoveInfo moveInfo : moves) {
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
moveInfo.toX, moveInfo.toY);
}
moves.clear();
mMovesList.remove(moves);
}
};
if (removalsPending) {
View view = moves.get(0).holder.itemView;
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
} else {
mover.run();
}
}
// Next, change stuff, to run in parallel with move animations
if (changesPending) {
final ArrayList<ChangeInfo> changes = new ArrayList<>();
changes.addAll(mPendingChanges);
mChangesList.add(changes);
mPendingChanges.clear();
Runnable changer = new Runnable() {
@Override
public void run() {
for (ChangeInfo change : changes) {
animateChangeImpl(change);
}
changes.clear();
mChangesList.remove(changes);
}
};
if (removalsPending) {
RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
} else {
changer.run();
}
}
// Next, add stuff
if (additionsPending) {
final ArrayList<RecyclerView.ViewHolder> additions = new ArrayList<>();
additions.addAll(mPendingAdditions);
mAdditionsList.add(additions);
mPendingAdditions.clear();
Runnable adder = new Runnable() {
@Override
public void run() {
for (RecyclerView.ViewHolder holder : additions) {
animateAddImpl(holder);
}
additions.clear();
mAdditionsList.remove(additions);
}
};
if (removalsPending || movesPending || changesPending) {
long removeDuration = removalsPending ? getRemoveDuration() : 0;
long moveDuration = movesPending ? getMoveDuration() : 0;
long changeDuration = changesPending ? getChangeDuration() : 0;
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
View view = additions.get(0).itemView;
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
} else {
adder.run();
}
}
}
@Override
public boolean animateRemove(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
mPendingRemovals.add(holder);
return true;
}
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimator animation = view.animate();
mRemoveAnimations.add(holder);
animation.setDuration(getRemoveDuration()).alpha(0).translationX(-view.getWidth()).setListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(Animator animator) {
animation.setListener(null);
view.setAlpha(1);
dispatchRemoveFinished(holder);
mRemoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
holder.itemView.setAlpha(0);
mPendingAdditions.add(holder);
return true;
}
void animateAddImpl(final RecyclerView.ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimator animation = view.animate();
mAddAnimations.add(holder);
animation.alpha(1).setDuration(getAddDuration())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationCancel(Animator animator) {
view.setAlpha(1);
}
@Override
public void onAnimationEnd(Animator animator) {
animation.setListener(null);
dispatchAddFinished(holder);
mAddAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
@Override
public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY,
int toX, int toY) {
final View view = holder.itemView;
fromX += (int) holder.itemView.getTranslationX();
fromY += (int) holder.itemView.getTranslationY();
resetAnimation(holder);
int deltaX = toX - fromX;
int deltaY = toY - fromY;
if (deltaX == 0 && deltaY == 0) {
dispatchMoveFinished(holder);
return false;
}
if (deltaX != 0) {
view.setTranslationX(-deltaX);
}
if (deltaY != 0) {
view.setTranslationY(-deltaY);
}
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
return true;
}
void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
final View view = holder.itemView;
final int deltaX = toX - fromX;
final int deltaY = toY - fromY;
if (deltaX != 0) {
view.animate().translationX(0);
}
if (deltaY != 0) {
view.animate().translationY(0);
}
// TODO: make EndActions end listeners instead, since end actions aren't called when
// vpas are canceled (and can't end them. why?)
// need listener functionality in VPACompat for this. Ick.
final ViewPropertyAnimator animation = view.animate();
mMoveAnimations.add(holder);
animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchMoveStarting(holder);
}
@Override
public void onAnimationCancel(Animator animator) {
if (deltaX != 0) {
view.setTranslationX(0);
}
if (deltaY != 0) {
view.setTranslationY(0);
}
}
@Override
public void onAnimationEnd(Animator animator) {
animation.setListener(null);
dispatchMoveFinished(holder);
mMoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
int fromX, int fromY, int toX, int toY) {
if (oldHolder == newHolder) {
// Don't know how to run change animations when the same view holder is re-used.
// run a move animation to handle position changes.
return animateMove(oldHolder, fromX, fromY, toX, toY);
}
final float prevTranslationX = oldHolder.itemView.getTranslationX();
final float prevTranslationY = oldHolder.itemView.getTranslationY();
final float prevAlpha = oldHolder.itemView.getAlpha();
resetAnimation(oldHolder);
int deltaX = (int) (toX - fromX - prevTranslationX);
int deltaY = (int) (toY - fromY - prevTranslationY);
// recover prev translation state after ending animation
oldHolder.itemView.setTranslationX(prevTranslationX);
oldHolder.itemView.setTranslationY(prevTranslationY);
oldHolder.itemView.setAlpha(prevAlpha);
if (newHolder != null) {
// carry over translation values
resetAnimation(newHolder);
newHolder.itemView.setTranslationX(-deltaX);
newHolder.itemView.setTranslationY(-deltaY);
newHolder.itemView.setAlpha(0);
}
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
return true;
}
void animateChangeImpl(final ChangeInfo changeInfo) {
final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
final View view = holder == null ? null : holder.itemView;
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
if (view != null) {
final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
getChangeDuration());
mChangeAnimations.add(changeInfo.oldHolder);
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchChangeStarting(changeInfo.oldHolder, true);
}
@Override
public void onAnimationEnd(Animator animator) {
oldViewAnim.setListener(null);
view.setAlpha(1);
view.setTranslationX(0);
view.setTranslationY(0);
dispatchChangeFinished(changeInfo.oldHolder, true);
mChangeAnimations.remove(changeInfo.oldHolder);
dispatchFinishedWhenDone();
}
}).start();
}
if (newView != null) {
final ViewPropertyAnimator newViewAnimation = newView.animate();
mChangeAnimations.add(changeInfo.newHolder);
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
.alpha(1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchChangeStarting(changeInfo.newHolder, false);
}
@Override
public void onAnimationEnd(Animator animator) {
newViewAnimation.setListener(null);
newView.setAlpha(1);
newView.setTranslationX(0);
newView.setTranslationY(0);
dispatchChangeFinished(changeInfo.newHolder, false);
mChangeAnimations.remove(changeInfo.newHolder);
dispatchFinishedWhenDone();
}
}).start();
}
}
private void endChangeAnimation(List<ChangeInfo> infoList, RecyclerView.ViewHolder item) {
for (int i = infoList.size() - 1; i >= 0; i--) {
ChangeInfo changeInfo = infoList.get(i);
if (endChangeAnimationIfNecessary(changeInfo, item)) {
if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
infoList.remove(changeInfo);
}
}
}
}
private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
if (changeInfo.oldHolder != null) {
endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
}
if (changeInfo.newHolder != null) {
endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
}
}
private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) {
boolean oldItem = false;
if (changeInfo.newHolder == item) {
changeInfo.newHolder = null;
} else if (changeInfo.oldHolder == item) {
changeInfo.oldHolder = null;
oldItem = true;
} else {
return false;
}
item.itemView.setAlpha(1);
item.itemView.setTranslationX(0);
item.itemView.setTranslationY(0);
dispatchChangeFinished(item, oldItem);
return true;
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
final View view = item.itemView;
// this will trigger end callback which should set properties to their target values.
view.animate().cancel();
// TODO if some other animations are chained to end, how do we cancel them as well?
for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
MoveInfo moveInfo = mPendingMoves.get(i);
if (moveInfo.holder == item) {
view.setTranslationY(0);
view.setTranslationX(0);
dispatchMoveFinished(item);
mPendingMoves.remove(i);
}
}
endChangeAnimation(mPendingChanges, item);
if (mPendingRemovals.remove(item)) {
view.setAlpha(1);
dispatchRemoveFinished(item);
}
if (mPendingAdditions.remove(item)) {
view.setAlpha(1);
dispatchAddFinished(item);
}
for (int i = mChangesList.size() - 1; i >= 0; i--) {
ArrayList<ChangeInfo> changes = mChangesList.get(i);
endChangeAnimation(changes, item);
if (changes.isEmpty()) {
mChangesList.remove(i);
}
}
for (int i = mMovesList.size() - 1; i >= 0; i--) {
ArrayList<MoveInfo> moves = mMovesList.get(i);
for (int j = moves.size() - 1; j >= 0; j--) {
MoveInfo moveInfo = moves.get(j);
if (moveInfo.holder == item) {
view.setTranslationY(0);
view.setTranslationX(0);
dispatchMoveFinished(item);
moves.remove(j);
if (moves.isEmpty()) {
mMovesList.remove(i);
}
break;
}
}
}
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
if (additions.remove(item)) {
view.setAlpha(1);
dispatchAddFinished(item);
if (additions.isEmpty()) {
mAdditionsList.remove(i);
}
}
}
// animations should be ended by the cancel above.
//noinspection PointlessBooleanExpression,ConstantConditions
if (mRemoveAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mRemoveAnimations list");
}
//noinspection PointlessBooleanExpression,ConstantConditions
if (mAddAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mAddAnimations list");
}
//noinspection PointlessBooleanExpression,ConstantConditions
if (mChangeAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mChangeAnimations list");
}
//noinspection PointlessBooleanExpression,ConstantConditions
if (mMoveAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mMoveAnimations list");
}
dispatchFinishedWhenDone();
}
private void resetAnimation(RecyclerView.ViewHolder holder) {
if (sDefaultInterpolator == null) {
sDefaultInterpolator = new ValueAnimator().getInterpolator();
}
holder.itemView.animate().setInterpolator(sDefaultInterpolator);
endAnimation(holder);
}
@Override
public boolean isRunning() {
return (!mPendingAdditions.isEmpty()
|| !mPendingChanges.isEmpty()
|| !mPendingMoves.isEmpty()
|| !mPendingRemovals.isEmpty()
|| !mMoveAnimations.isEmpty()
|| !mRemoveAnimations.isEmpty()
|| !mAddAnimations.isEmpty()
|| !mChangeAnimations.isEmpty()
|| !mMovesList.isEmpty()
|| !mAdditionsList.isEmpty()
|| !mChangesList.isEmpty());
}
/**
* Check the state of currently pending and running animations. If there are none
* pending/running, call {@link #dispatchAnimationsFinished()} to notify any
* listeners.
*/
void dispatchFinishedWhenDone() {
if (!isRunning()) {
dispatchAnimationsFinished();
}
}
@Override
public void endAnimations() {
int count = mPendingMoves.size();
for (int i = count - 1; i >= 0; i--) {
MoveInfo item = mPendingMoves.get(i);
View view = item.holder.itemView;
view.setTranslationY(0);
view.setTranslationX(0);
dispatchMoveFinished(item.holder);
mPendingMoves.remove(i);
}
count = mPendingRemovals.size();
for (int i = count - 1; i >= 0; i--) {
RecyclerView.ViewHolder item = mPendingRemovals.get(i);
dispatchRemoveFinished(item);
mPendingRemovals.remove(i);
}
count = mPendingAdditions.size();
for (int i = count - 1; i >= 0; i--) {
RecyclerView.ViewHolder item = mPendingAdditions.get(i);
item.itemView.setAlpha(1);
dispatchAddFinished(item);
mPendingAdditions.remove(i);
}
count = mPendingChanges.size();
for (int i = count - 1; i >= 0; i--) {
endChangeAnimationIfNecessary(mPendingChanges.get(i));
}
mPendingChanges.clear();
if (!isRunning()) {
return;
}
int listCount = mMovesList.size();
for (int i = listCount - 1; i >= 0; i--) {
ArrayList<MoveInfo> moves = mMovesList.get(i);
count = moves.size();
for (int j = count - 1; j >= 0; j--) {
MoveInfo moveInfo = moves.get(j);
RecyclerView.ViewHolder item = moveInfo.holder;
View view = item.itemView;
view.setTranslationY(0);
view.setTranslationX(0);
dispatchMoveFinished(moveInfo.holder);
moves.remove(j);
if (moves.isEmpty()) {
mMovesList.remove(moves);
}
}
}
listCount = mAdditionsList.size();
for (int i = listCount - 1; i >= 0; i--) {
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
count = additions.size();
for (int j = count - 1; j >= 0; j--) {
RecyclerView.ViewHolder item = additions.get(j);
View view = item.itemView;
view.setAlpha(1);
dispatchAddFinished(item);
additions.remove(j);
if (additions.isEmpty()) {
mAdditionsList.remove(additions);
}
}
}
listCount = mChangesList.size();
for (int i = listCount - 1; i >= 0; i--) {
ArrayList<ChangeInfo> changes = mChangesList.get(i);
count = changes.size();
for (int j = count - 1; j >= 0; j--) {
endChangeAnimationIfNecessary(changes.get(j));
if (changes.isEmpty()) {
mChangesList.remove(changes);
}
}
}
cancelAll(mRemoveAnimations);
cancelAll(mMoveAnimations);
cancelAll(mAddAnimations);
cancelAll(mChangeAnimations);
dispatchAnimationsFinished();
}
void cancelAll(List<RecyclerView.ViewHolder> viewHolders) {
for (int i = viewHolders.size() - 1; i >= 0; i--) {
viewHolders.get(i).itemView.animate().cancel();
}
}
/**
* {@inheritDoc}
* <p>
* If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
* When this is the case:
* <ul>
* <li>If you override {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}, both
* ViewHolder arguments will be the same instance.
* </li>
* <li>
* If you are not overriding {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)},
* then DefaultItemAnimator will call {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int)} and
* run a move animation instead.
* </li>
* </ul>
*/
@Override
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull List<Object> payloads) {
return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
}
}

View File

@@ -0,0 +1,790 @@
package com.mogo.module.push.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Interpolator;
import android.widget.Scroller;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
/**
* recyclerview滑动删除类
*/
public class SwipeItemLayout extends ViewGroup {
enum Mode {
RESET, DRAG, FLING, TAP
}
private Mode mTouchMode;
private ViewGroup mMainView;
private ViewGroup mSideView;
private ScrollRunnable mScrollRunnable;
private int mScrollOffset;
private int mMaxScrollOffset;
private boolean mInLayout;
private boolean mIsLaidOut;
public void setOpenSwipe(boolean mOpenSwipe) {
this.mOpenSwipe = mOpenSwipe;
}
private boolean mOpenSwipe = true;
public SwipeItemLayout(Context context) {
this(context, null);
}
public SwipeItemLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchMode = Mode.RESET;
mScrollOffset = 0;
mIsLaidOut = false;
mScrollRunnable = new ScrollRunnable(context);
}
public boolean isOpen() {
return mScrollOffset != 0;
}
Mode getTouchMode() {
return mTouchMode;
}
void setTouchMode(Mode mode) {
switch (mTouchMode) {
case FLING:
mScrollRunnable.abort();
break;
case RESET:
break;
}
mTouchMode = mode;
}
public void open() {
if (mScrollOffset != -mMaxScrollOffset) {
//正在open不需要处理
if (mTouchMode == Mode.FLING && mScrollRunnable.isScrollToLeft())
return;
//当前正在向右滑abort
if (mTouchMode == Mode.FLING /*&& !mScrollRunnable.mScrollToLeft*/)
mScrollRunnable.abort();
mScrollRunnable.startScroll(mScrollOffset, -mMaxScrollOffset);
}
}
@Override
public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
super.addOnAttachStateChangeListener(listener);
}
public void close() {
if (mScrollOffset != 0) {
//正在close不需要处理
if (mTouchMode == Mode.FLING && !mScrollRunnable.isScrollToLeft())
return;
//当前正向左滑abort
if (mTouchMode == Mode.FLING /*&& mScrollRunnable.mScrollToLeft*/)
mScrollRunnable.abort();
mScrollRunnable.startScroll(mScrollOffset, 0);
}
}
void fling(int xVel) {
mScrollRunnable.startFling(mScrollOffset, xVel);
}
void revise() {
if (mScrollOffset < -mMaxScrollOffset / 2)
open();
else
close();
}
boolean trackMotionScroll(int deltaX) {
if (deltaX == 0)
return false;
boolean over = false;
int newLeft = mScrollOffset + deltaX;
if ((deltaX > 0 && newLeft > 0) || (deltaX < 0 && newLeft < -mMaxScrollOffset)) {
over = true;
newLeft = Math.min(newLeft, 0);
newLeft = Math.max(newLeft, -mMaxScrollOffset);
}
offsetChildrenLeftAndRight(newLeft - mScrollOffset);
mScrollOffset = newLeft;
return over;
}
private boolean ensureChildren() {
int childCount = getChildCount();
if (childCount != 2)
return false;
View childView = getChildAt(0);
if (!(childView instanceof ViewGroup))
return false;
mMainView = (ViewGroup) childView;
childView = getChildAt(1);
if (!(childView instanceof ViewGroup))
return false;
mSideView = (ViewGroup) childView;
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!ensureChildren())
throw new RuntimeException("SwipeItemLayout的子视图不符合规定");
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
MarginLayoutParams lp = null;
int horizontalMargin, verticalMargin;
int horizontalPadding = getPaddingLeft() + getPaddingRight();
int verticalPadding = getPaddingTop() + getPaddingBottom();
lp = (MarginLayoutParams) mMainView.getLayoutParams();
horizontalMargin = lp.leftMargin + lp.rightMargin;
verticalMargin = lp.topMargin + lp.bottomMargin;
measureChildWithMargins(mMainView,
widthMeasureSpec, horizontalMargin + horizontalPadding,
heightMeasureSpec, verticalMargin + verticalPadding);
if (widthMode == MeasureSpec.AT_MOST)
widthSize = Math.min(widthSize, mMainView.getMeasuredWidth() + horizontalMargin + horizontalPadding);
else if (widthMode == MeasureSpec.UNSPECIFIED)
widthSize = mMainView.getMeasuredWidth() + horizontalMargin + horizontalPadding;
if (heightMode == MeasureSpec.AT_MOST)
heightSize = Math.min(heightSize, mMainView.getMeasuredHeight() + verticalMargin + verticalPadding);
else if (heightMode == MeasureSpec.UNSPECIFIED)
heightSize = mMainView.getMeasuredHeight() + verticalMargin + verticalPadding;
setMeasuredDimension(widthSize, heightSize);
//side layout大小为自身实际大小
lp = (MarginLayoutParams) mSideView.getLayoutParams();
verticalMargin = lp.topMargin + lp.bottomMargin;
mSideView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(getMeasuredHeight() - verticalMargin - verticalPadding, MeasureSpec.EXACTLY));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!ensureChildren())
throw new RuntimeException("SwipeItemLayout的子视图不符合规定");
mInLayout = true;
int pl = getPaddingLeft();
int pt = getPaddingTop();
int pr = getPaddingRight();
int pb = getPaddingBottom();
MarginLayoutParams mainLp = (MarginLayoutParams) mMainView.getLayoutParams();
MarginLayoutParams sideParams = (MarginLayoutParams) mSideView.getLayoutParams();
int childLeft = pl + mainLp.leftMargin;
int childTop = pt + mainLp.topMargin;
int childRight = getWidth() - (pr + mainLp.rightMargin);
int childBottom = getHeight() - (mainLp.bottomMargin + pb);
mMainView.layout(childLeft, childTop, childRight, childBottom);
childLeft = childRight + sideParams.leftMargin;
childTop = pt + sideParams.topMargin;
childRight = childLeft + sideParams.leftMargin + sideParams.rightMargin + mSideView.getMeasuredWidth();
childBottom = getHeight() - (sideParams.bottomMargin + pb);
mSideView.layout(childLeft, childTop, childRight, childBottom);
if (mOpenSwipe) {
mMaxScrollOffset = mSideView.getWidth() + sideParams.leftMargin + sideParams.rightMargin;
} else {
mMaxScrollOffset = 0;
}
mScrollOffset = mScrollOffset < -mMaxScrollOffset / 2 ? -mMaxScrollOffset : 0;
offsetChildrenLeftAndRight(mScrollOffset);
mInLayout = false;
mIsLaidOut = true;
}
void offsetChildrenLeftAndRight(int delta) {
ViewCompat.offsetLeftAndRight(mMainView, delta);
ViewCompat.offsetLeftAndRight(mSideView, delta);
}
@Override
public void requestLayout() {
if (!mInLayout) {
super.requestLayout();
}
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return p instanceof MarginLayoutParams ? p : new MarginLayoutParams(p);
}
@Override
protected boolean checkLayoutParams(LayoutParams p) {
return p instanceof MarginLayoutParams && super.checkLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mScrollOffset != 0 && mIsLaidOut) {
offsetChildrenLeftAndRight(-mScrollOffset);
mScrollOffset = 0;
} else
mScrollOffset = 0;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mScrollOffset != 0 && mIsLaidOut) {
offsetChildrenLeftAndRight(-mScrollOffset);
mScrollOffset = 0;
} else
mScrollOffset = 0;
removeCallbacks(mScrollRunnable);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
//click main view但是它处于open状态所以不需要点击效果直接拦截不调用click listener
switch (action) {
case MotionEvent.ACTION_DOWN: {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
View pointView = findTopChildUnder(this, x, y);
if (pointView != null && pointView == mMainView && mScrollOffset != 0)
return true;
break;
}
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_UP: {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
View pointView = findTopChildUnder(this, x, y);
if (pointView != null && pointView == mMainView && mTouchMode == Mode.TAP && mScrollOffset != 0)
return true;
}
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
//click main view但是它处于open状态所以不需要点击效果直接拦截不调用click listener
switch (action) {
case MotionEvent.ACTION_DOWN: {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
View pointView = findTopChildUnder(this, x, y);
if (pointView != null && pointView == mMainView && mScrollOffset != 0)
return true;
break;
}
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_UP: {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
View pointView = findTopChildUnder(this, x, y);
if (pointView != null && pointView == mMainView && mTouchMode == Mode.TAP && mScrollOffset != 0) {
close();
return true;
}
}
}
return false;
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (getVisibility() != View.VISIBLE) {
mScrollOffset = 0;
invalidate();
}
}
private static final Interpolator sInterpolator = new Interpolator() {
@Override
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
class ScrollRunnable implements Runnable {
private static final int FLING_DURATION = 200;
private Scroller mScroller;
private boolean mAbort;
private int mMinVelocity;
private boolean mScrollToLeft;
ScrollRunnable(Context context) {
mScroller = new Scroller(context, sInterpolator);
mAbort = false;
mScrollToLeft = false;
ViewConfiguration configuration = ViewConfiguration.get(context);
mMinVelocity = configuration.getScaledMinimumFlingVelocity();
}
void startScroll(int startX, int endX) {
if (startX != endX) {
// Log.e("scroll - startX - endX", "" + startX + " " + endX);
setTouchMode(Mode.FLING);
mAbort = false;
mScrollToLeft = endX < startX;
mScroller.startScroll(startX, 0, endX - startX, 0, 400);
ViewCompat.postOnAnimation(SwipeItemLayout.this, this);
}
}
void startFling(int startX, int xVel) {
// Log.e("fling - startX", "" + startX);
if (xVel > mMinVelocity && startX != 0) {
startScroll(startX, 0);
return;
}
if (xVel < -mMinVelocity && startX != -mMaxScrollOffset) {
startScroll(startX, -mMaxScrollOffset);
return;
}
startScroll(startX, startX > -mMaxScrollOffset / 2 ? 0 : -mMaxScrollOffset);
}
void abort() {
if (!mAbort) {
mAbort = true;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
removeCallbacks(this);
}
}
}
//是否正在滑动需要另外判断
boolean isScrollToLeft() {
return mScrollToLeft;
}
@Override
public void run() {
// Log.e("abort", Boolean.toString(mAbort));
if (!mAbort) {
boolean more = mScroller.computeScrollOffset();
int curX = mScroller.getCurrX();
// Log.e("curX", "" + curX);
boolean atEdge = trackMotionScroll(curX - mScrollOffset);
if (more && !atEdge) {
ViewCompat.postOnAnimation(SwipeItemLayout.this, this);
return;
}
if (atEdge) {
removeCallbacks(this);
if (!mScroller.isFinished())
mScroller.abortAnimation();
setTouchMode(Mode.RESET);
}
if (!more) {
setTouchMode(Mode.RESET);
//绝对不会出现这种意外的!!!可以注释掉
if (mScrollOffset != 0) {
if (Math.abs(mScrollOffset) > mMaxScrollOffset / 2)
mScrollOffset = -mMaxScrollOffset;
else
mScrollOffset = 0;
ViewCompat.postOnAnimation(SwipeItemLayout.this, this);
}
}
}
}
}
public static class OnSwipeItemTouchListener implements RecyclerView.OnItemTouchListener {
private SwipeItemLayout mCaptureItem;
private float mLastMotionX;
private float mLastMotionY;
private VelocityTracker mVelocityTracker;
private int mActivePointerId;
private int mTouchSlop;
private int mMaximumVelocity;
private boolean mDealByParent;
private boolean mIsProbeParent;
public OnSwipeItemTouchListener(Context context) {
ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mActivePointerId = -1;
mDealByParent = false;
mIsProbeParent = false;
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
if (mIsProbeParent)
return false;
boolean intercept = false;
final int action = ev.getActionMasked();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
switch (action) {
case MotionEvent.ACTION_DOWN: {
mActivePointerId = ev.getPointerId(0);
final float x = ev.getX();
final float y = ev.getY();
mLastMotionX = x;
mLastMotionY = y;
boolean pointOther = false;
SwipeItemLayout pointItem = null;
//首先知道ev针对的是哪个item
View pointView = findTopChildUnder(rv, (int) x, (int) y);
if (pointView == null || !(pointView instanceof SwipeItemLayout)) {
//可能是head view或bottom view
pointOther = true;
} else
pointItem = (SwipeItemLayout) pointView;
//此时的pointOther=true意味着点击的view为空或者点击的不是item
//还没有把点击的是item但是不是capture item给过滤出来
if (!pointOther && (mCaptureItem == null || mCaptureItem != pointItem))
pointOther = true;
//点击的是capture item
if (!pointOther) {
Mode touchMode = mCaptureItem.getTouchMode();
//如果它在fling就转为drag
//需要拦截并且requestDisallowInterceptTouchEvent
boolean disallowIntercept = false;
if (touchMode == Mode.FLING) {
mCaptureItem.setTouchMode(Mode.DRAG);
disallowIntercept = true;
intercept = true;
} else {//如果是expand的就不允许parent拦截
mCaptureItem.setTouchMode(Mode.TAP);
if (mCaptureItem.isOpen())
disallowIntercept = true;
}
if (disallowIntercept) {
final ViewParent parent = rv.getParent();
if (parent != null)
parent.requestDisallowInterceptTouchEvent(true);
}
} else {//capture item为null或者与point item不一样
//直接将其close掉
if (mCaptureItem != null && mCaptureItem.isOpen()) {
mCaptureItem.close();
mCaptureItem = null;
intercept = true;
}
if (pointItem != null) {
mCaptureItem = pointItem;
mCaptureItem.setTouchMode(Mode.TAP);
} else
mCaptureItem = null;
}
//如果parent处于fling状态此时parent就会转为drag。此时应该将后续move都交给parent处理
mIsProbeParent = true;
mDealByParent = rv.onInterceptTouchEvent(ev);
mIsProbeParent = false;
if (mDealByParent)
intercept = false;
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
final int actionIndex = ev.getActionIndex();
mActivePointerId = ev.getPointerId(actionIndex);
mLastMotionX = ev.getX(actionIndex);
mLastMotionY = ev.getY(actionIndex);
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int actionIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(actionIndex);
if (pointerId == mActivePointerId) {
final int newIndex = actionIndex == 0 ? 1 : 0;
mActivePointerId = ev.getPointerId(newIndex);
mLastMotionX = ev.getX(newIndex);
mLastMotionY = ev.getY(newIndex);
}
break;
}
//down时已经将capture item定下来了。所以后面可以安心考虑event处理
case MotionEvent.ACTION_MOVE: {
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1)
break;
//在down时就被认定为parent的drag所以直接交给parent处理即可
if (mDealByParent) {
if (mCaptureItem != null && mCaptureItem.isOpen())
mCaptureItem.close();
return false;
}
final int x = (int) (ev.getX(activePointerIndex) + .5f);
final int y = (int) ((int) ev.getY(activePointerIndex) + .5f);
int deltaX = (int) (x - mLastMotionX);
int deltaY = (int) (y - mLastMotionY);
final int xDiff = Math.abs(deltaX);
final int yDiff = Math.abs(deltaY);
if (mCaptureItem != null && !mDealByParent) {
Mode touchMode = mCaptureItem.getTouchMode();
if (touchMode == Mode.TAP) {
//如果capture item是open的下拉有两种处理方式
// 1、下拉后直接close item
// 2、只要是open的就拦截所有它的消息这样如果点击open的就只能滑动该capture item
//网易邮箱在open的情况下下拉直接close
//QQ在open的情况下下拉也是close。但是做的不够好没有达到该效果。
if (xDiff > mTouchSlop && xDiff > yDiff) {
mCaptureItem.setTouchMode(Mode.DRAG);
final ViewParent parent = rv.getParent();
parent.requestDisallowInterceptTouchEvent(true);
deltaX = deltaX > 0 ? deltaX - mTouchSlop : deltaX + mTouchSlop;
} else {// if(yDiff>mTouchSlop){
mIsProbeParent = true;
boolean isParentConsume = rv.onInterceptTouchEvent(ev);
mIsProbeParent = false;
if (isParentConsume) {
//表明不是水平滑动即不判定为SwipeItemLayout的滑动
//但是可能是下拉刷新SwipeRefreshLayout或者RecyclerView的滑动
//一般的下拉判定都是yDiff>mTouchSlop所以此处这么写不会出问题
//这里这么做以后如果判定为下拉就直接close
mDealByParent = true;
mCaptureItem.close();
}
}
}
touchMode = mCaptureItem.getTouchMode();
if (touchMode == Mode.DRAG) {
intercept = true;
mLastMotionX = x;
mLastMotionY = y;
//对capture item进行拖拽
mCaptureItem.trackMotionScroll(deltaX);
}
}
break;
}
case MotionEvent.ACTION_UP:
if (mCaptureItem != null) {
Mode touchMode = mCaptureItem.getTouchMode();
if (touchMode == Mode.DRAG) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int xVel = (int) velocityTracker.getXVelocity(mActivePointerId);
mCaptureItem.fling(xVel);
intercept = true;
}
}
cancel();
break;
case MotionEvent.ACTION_CANCEL:
if (mCaptureItem != null)
mCaptureItem.revise();
cancel();
break;
}
return intercept;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
final int action = ev.getActionMasked();
final int actionIndex = ev.getActionIndex();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
switch (action) {
case MotionEvent.ACTION_POINTER_DOWN:
mActivePointerId = ev.getPointerId(actionIndex);
mLastMotionX = ev.getX(actionIndex);
mLastMotionY = ev.getY(actionIndex);
break;
case MotionEvent.ACTION_POINTER_UP:
final int pointerId = ev.getPointerId(actionIndex);
if (pointerId == mActivePointerId) {
final int newIndex = actionIndex == 0 ? 1 : 0;
mActivePointerId = ev.getPointerId(newIndex);
mLastMotionX = ev.getX(newIndex);
mLastMotionY = ev.getY(newIndex);
}
break;
//down时已经将capture item定下来了。所以后面可以安心考虑event处理
case MotionEvent.ACTION_MOVE: {
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1)
break;
final float x = ev.getX(activePointerIndex);
final float y = (int) ev.getY(activePointerIndex);
int deltaX = (int) (x - mLastMotionX);
if (mCaptureItem != null && mCaptureItem.getTouchMode() == Mode.DRAG) {
mLastMotionX = x;
mLastMotionY = y;
//对capture item进行拖拽
mCaptureItem.trackMotionScroll(deltaX);
}
break;
}
case MotionEvent.ACTION_UP:
if (mCaptureItem != null) {
Mode touchMode = mCaptureItem.getTouchMode();
if (touchMode == Mode.DRAG) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int xVel = (int) velocityTracker.getXVelocity(mActivePointerId);
mCaptureItem.fling(xVel);
}
}
cancel();
break;
case MotionEvent.ACTION_CANCEL:
if (mCaptureItem != null)
mCaptureItem.revise();
cancel();
break;
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
void cancel() {
mDealByParent = false;
mActivePointerId = -1;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
}
static View findTopChildUnder(ViewGroup parent, int x, int y) {
final int childCount = parent.getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View child = parent.getChildAt(i);
if (x >= child.getLeft() && x < child.getRight()
&& y >= child.getTop() && y < child.getBottom()) {
return child;
}
}
return null;
}
public static void closeAllItems(RecyclerView recyclerView) {
for (int i = 0; i < recyclerView.getChildCount(); i++) {
View child = recyclerView.getChildAt(i);
if (child instanceof SwipeItemLayout) {
SwipeItemLayout swipeItemLayout = (SwipeItemLayout) child;
if (swipeItemLayout.isOpen())
swipeItemLayout.close();
}
}
}
}

View File

@@ -0,0 +1,19 @@
package com.mogo.module.push.view.roundimage;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Corner.TOP_LEFT, Corner.TOP_RIGHT,
Corner.BOTTOM_LEFT, Corner.BOTTOM_RIGHT
})
public @interface Corner {
int TOP_LEFT = 0;
int TOP_RIGHT = 1;
int BOTTOM_RIGHT = 2;
int BOTTOM_LEFT = 3;
}

View File

@@ -0,0 +1,633 @@
/*
* Copyright (C) 2015 Vincent Mi
*
* 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
*
* http://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.mogo.module.push.view.roundimage;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.Log;
import android.widget.ImageView.ScaleType;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import java.util.HashSet;
import java.util.Set;
@SuppressWarnings("UnusedDeclaration")
public class RoundedDrawable extends Drawable {
public static final String TAG = "RoundedDrawable";
public static final int DEFAULT_BORDER_COLOR = Color.BLACK;
private final RectF mBounds = new RectF();
private final RectF mDrawableRect = new RectF();
private final RectF mBitmapRect = new RectF();
private final Bitmap mBitmap;
private final Paint mBitmapPaint;
private final int mBitmapWidth;
private final int mBitmapHeight;
private final RectF mBorderRect = new RectF();
private final Paint mBorderPaint;
private final Matrix mShaderMatrix = new Matrix();
private final RectF mSquareCornersRect = new RectF();
private Shader.TileMode mTileModeX = Shader.TileMode.CLAMP;
private Shader.TileMode mTileModeY = Shader.TileMode.CLAMP;
private boolean mRebuildShader = true;
// [ topLeft, topRight, bottomLeft, bottomRight ]
private float mCornerRadius = 0f;
private final boolean[] mCornersRounded = new boolean[] { true, true, true, true };
private boolean mOval = false;
private float mBorderWidth = 0;
private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
private ScaleType mScaleType = ScaleType.FIT_CENTER;
public RoundedDrawable(Bitmap bitmap) {
mBitmap = bitmap;
mBitmapWidth = bitmap.getWidth();
mBitmapHeight = bitmap.getHeight();
mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight);
mBitmapPaint = new Paint();
mBitmapPaint.setStyle(Paint.Style.FILL);
mBitmapPaint.setAntiAlias(true);
mBorderPaint = new Paint();
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
mBorderPaint.setStrokeWidth(mBorderWidth);
}
public static RoundedDrawable fromBitmap(Bitmap bitmap) {
if (bitmap != null) {
return new RoundedDrawable(bitmap);
} else {
return null;
}
}
public static Drawable fromDrawable(Drawable drawable) {
if (drawable != null) {
if (drawable instanceof RoundedDrawable) {
// just return if it's already a RoundedDrawable
return drawable;
} else if (drawable instanceof LayerDrawable) {
LayerDrawable ld = (LayerDrawable) drawable;
int num = ld.getNumberOfLayers();
// loop through layers to and change to RoundedDrawables if possible
for (int i = 0; i < num; i++) {
Drawable d = ld.getDrawable(i);
ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d));
}
return ld;
}
// try to get a bitmap from the drawable and
Bitmap bm = drawableToBitmap(drawable);
if (bm != null) {
return new RoundedDrawable(bm);
}
}
return drawable;
}
public static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap;
int width = Math.max(drawable.getIntrinsicWidth(), 2);
int height = Math.max(drawable.getIntrinsicHeight(), 2);
try {
bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, "Failed to create bitmap from drawable!");
bitmap = null;
}
return bitmap;
}
public Bitmap getSourceBitmap() {
return mBitmap;
}
@Override
public boolean isStateful() {
return mBorderColor.isStateful();
}
@Override
protected boolean onStateChange(int[] state) {
int newColor = mBorderColor.getColorForState(state, 0);
if (mBorderPaint.getColor() != newColor) {
mBorderPaint.setColor(newColor);
return true;
} else {
return super.onStateChange(state);
}
}
private void updateShaderMatrix() {
float scale;
float dx;
float dy;
switch (mScaleType) {
case CENTER:
mBorderRect.set(mBounds);
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
mShaderMatrix.reset();
mShaderMatrix.setTranslate((int) ((mBorderRect.width() - mBitmapWidth) * 0.5f + 0.5f),
(int) ((mBorderRect.height() - mBitmapHeight) * 0.5f + 0.5f));
break;
case CENTER_CROP:
mBorderRect.set(mBounds);
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
mShaderMatrix.reset();
dx = 0;
dy = 0;
if (mBitmapWidth * mBorderRect.height() > mBorderRect.width() * mBitmapHeight) {
scale = mBorderRect.height() / (float) mBitmapHeight;
dx = (mBorderRect.width() - mBitmapWidth * scale) * 0.5f;
} else {
scale = mBorderRect.width() / (float) mBitmapWidth;
dy = (mBorderRect.height() - mBitmapHeight * scale) * 0.5f;
}
mShaderMatrix.setScale(scale, scale);
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth / 2,
(int) (dy + 0.5f) + mBorderWidth / 2);
break;
case CENTER_INSIDE:
mShaderMatrix.reset();
if (mBitmapWidth <= mBounds.width() && mBitmapHeight <= mBounds.height()) {
scale = 1.0f;
} else {
scale = Math.min(mBounds.width() / (float) mBitmapWidth,
mBounds.height() / (float) mBitmapHeight);
}
dx = (int) ((mBounds.width() - mBitmapWidth * scale) * 0.5f + 0.5f);
dy = (int) ((mBounds.height() - mBitmapHeight * scale) * 0.5f + 0.5f);
mShaderMatrix.setScale(scale, scale);
mShaderMatrix.postTranslate(dx, dy);
mBorderRect.set(mBitmapRect);
mShaderMatrix.mapRect(mBorderRect);
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
break;
default:
case FIT_CENTER:
mBorderRect.set(mBitmapRect);
mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.CENTER);
mShaderMatrix.mapRect(mBorderRect);
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
break;
case FIT_END:
mBorderRect.set(mBitmapRect);
mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.END);
mShaderMatrix.mapRect(mBorderRect);
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
break;
case FIT_START:
mBorderRect.set(mBitmapRect);
mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.START);
mShaderMatrix.mapRect(mBorderRect);
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
break;
case FIT_XY:
mBorderRect.set(mBounds);
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
mShaderMatrix.reset();
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
break;
}
mDrawableRect.set(mBorderRect);
}
@Override
protected void onBoundsChange(@NonNull Rect bounds) {
super.onBoundsChange(bounds);
mBounds.set(bounds);
updateShaderMatrix();
}
@Override
public void draw(@NonNull Canvas canvas) {
if (mRebuildShader) {
BitmapShader bitmapShader = new BitmapShader(mBitmap, mTileModeX, mTileModeY);
if (mTileModeX == Shader.TileMode.CLAMP && mTileModeY == Shader.TileMode.CLAMP) {
bitmapShader.setLocalMatrix(mShaderMatrix);
}
mBitmapPaint.setShader(bitmapShader);
mRebuildShader = false;
}
if (mOval) {
if (mBorderWidth > 0) {
canvas.drawOval(mDrawableRect, mBitmapPaint);
canvas.drawOval(mBorderRect, mBorderPaint);
} else {
canvas.drawOval(mDrawableRect, mBitmapPaint);
}
} else {
if (any(mCornersRounded)) {
float radius = mCornerRadius;
if (mBorderWidth > 0) {
canvas.drawRoundRect(mDrawableRect, radius, radius, mBitmapPaint);
canvas.drawRoundRect(mBorderRect, radius, radius, mBorderPaint);
redrawBitmapForSquareCorners(canvas);
redrawBorderForSquareCorners(canvas);
} else {
canvas.drawRoundRect(mDrawableRect, radius, radius, mBitmapPaint);
redrawBitmapForSquareCorners(canvas);
}
} else {
canvas.drawRect(mDrawableRect, mBitmapPaint);
if (mBorderWidth > 0) {
canvas.drawRect(mBorderRect, mBorderPaint);
}
}
}
}
private void redrawBitmapForSquareCorners(Canvas canvas) {
if (all(mCornersRounded)) {
// no square corners
return;
}
if (mCornerRadius == 0) {
return; // no round corners
}
float left = mDrawableRect.left;
float top = mDrawableRect.top;
float right = left + mDrawableRect.width();
float bottom = top + mDrawableRect.height();
float radius = mCornerRadius;
if (!mCornersRounded[Corner.TOP_LEFT]) {
mSquareCornersRect.set(left, top, left + radius, top + radius);
canvas.drawRect(mSquareCornersRect, mBitmapPaint);
}
if (!mCornersRounded[Corner.TOP_RIGHT]) {
mSquareCornersRect.set(right - radius, top, right, radius);
canvas.drawRect(mSquareCornersRect, mBitmapPaint);
}
if (!mCornersRounded[Corner.BOTTOM_RIGHT]) {
mSquareCornersRect.set(right - radius, bottom - radius, right, bottom);
canvas.drawRect(mSquareCornersRect, mBitmapPaint);
}
if (!mCornersRounded[Corner.BOTTOM_LEFT]) {
mSquareCornersRect.set(left, bottom - radius, left + radius, bottom);
canvas.drawRect(mSquareCornersRect, mBitmapPaint);
}
}
private void redrawBorderForSquareCorners(Canvas canvas) {
if (all(mCornersRounded)) {
// no square corners
return;
}
if (mCornerRadius == 0) {
return; // no round corners
}
float left = mDrawableRect.left;
float top = mDrawableRect.top;
float right = left + mDrawableRect.width();
float bottom = top + mDrawableRect.height();
float radius = mCornerRadius;
float offset = mBorderWidth / 2;
if (!mCornersRounded[Corner.TOP_LEFT]) {
canvas.drawLine(left - offset, top, left + radius, top, mBorderPaint);
canvas.drawLine(left, top - offset, left, top + radius, mBorderPaint);
}
if (!mCornersRounded[Corner.TOP_RIGHT]) {
canvas.drawLine(right - radius - offset, top, right, top, mBorderPaint);
canvas.drawLine(right, top - offset, right, top + radius, mBorderPaint);
}
if (!mCornersRounded[Corner.BOTTOM_RIGHT]) {
canvas.drawLine(right - radius - offset, bottom, right + offset, bottom, mBorderPaint);
canvas.drawLine(right, bottom - radius, right, bottom, mBorderPaint);
}
if (!mCornersRounded[Corner.BOTTOM_LEFT]) {
canvas.drawLine(left - offset, bottom, left + radius, bottom, mBorderPaint);
canvas.drawLine(left, bottom - radius, left, bottom, mBorderPaint);
}
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getAlpha() {
return mBitmapPaint.getAlpha();
}
@Override
public void setAlpha(int alpha) {
mBitmapPaint.setAlpha(alpha);
invalidateSelf();
}
@Override
public ColorFilter getColorFilter() {
return mBitmapPaint.getColorFilter();
}
@Override
public void setColorFilter(ColorFilter cf) {
mBitmapPaint.setColorFilter(cf);
invalidateSelf();
}
@Override
public void setDither(boolean dither) {
mBitmapPaint.setDither(dither);
invalidateSelf();
}
@Override
public void setFilterBitmap(boolean filter) {
mBitmapPaint.setFilterBitmap(filter);
invalidateSelf();
}
@Override
public int getIntrinsicWidth() {
return mBitmapWidth;
}
@Override
public int getIntrinsicHeight() {
return mBitmapHeight;
}
/**
* @return the corner radius.
*/
public float getCornerRadius() {
return mCornerRadius;
}
/**
* @param corner the specific corner to get radius of.
* @return the corner radius of the specified corner.
*/
public float getCornerRadius(@Corner int corner) {
return mCornersRounded[corner] ? mCornerRadius : 0f;
}
/**
* Sets all corners to the specified radius.
*
* @param radius the radius.
* @return the {@link RoundedDrawable} for chaining.
*/
public RoundedDrawable setCornerRadius(float radius) {
setCornerRadius(radius, radius, radius, radius);
return this;
}
/**
* Sets the corner radius of one specific corner.
*
* @param corner the corner.
* @param radius the radius.
* @return the {@link RoundedDrawable} for chaining.
*/
public RoundedDrawable setCornerRadius(@Corner int corner, float radius) {
if (radius != 0 && mCornerRadius != 0 && mCornerRadius != radius) {
throw new IllegalArgumentException("Multiple nonzero corner radii not yet supported.");
}
if (radius == 0) {
if (only(corner, mCornersRounded)) {
mCornerRadius = 0;
}
mCornersRounded[corner] = false;
} else {
if (mCornerRadius == 0) {
mCornerRadius = radius;
}
mCornersRounded[corner] = true;
}
return this;
}
/**
* Sets the corner radii of all the corners.
*
* @param topLeft top left corner radius.
* @param topRight top right corner radius
* @param bottomRight bototm right corner radius.
* @param bottomLeft bottom left corner radius.
* @return the {@link RoundedDrawable} for chaining.
*/
public RoundedDrawable setCornerRadius(float topLeft, float topRight, float bottomRight,
float bottomLeft) {
Set<Float> radiusSet = new HashSet<>(4);
radiusSet.add(topLeft);
radiusSet.add(topRight);
radiusSet.add(bottomRight);
radiusSet.add(bottomLeft);
radiusSet.remove(0f);
if (radiusSet.size() > 1) {
throw new IllegalArgumentException("Multiple nonzero corner radii not yet supported.");
}
if (!radiusSet.isEmpty()) {
float radius = radiusSet.iterator().next();
if (Float.isInfinite(radius) || Float.isNaN(radius) || radius < 0) {
throw new IllegalArgumentException("Invalid radius value: " + radius);
}
mCornerRadius = radius;
} else {
mCornerRadius = 0f;
}
mCornersRounded[Corner.TOP_LEFT] = topLeft > 0;
mCornersRounded[Corner.TOP_RIGHT] = topRight > 0;
mCornersRounded[Corner.BOTTOM_RIGHT] = bottomRight > 0;
mCornersRounded[Corner.BOTTOM_LEFT] = bottomLeft > 0;
return this;
}
public float getBorderWidth() {
return mBorderWidth;
}
public RoundedDrawable setBorderWidth(float width) {
mBorderWidth = width;
mBorderPaint.setStrokeWidth(mBorderWidth);
return this;
}
public int getBorderColor() {
return mBorderColor.getDefaultColor();
}
public RoundedDrawable setBorderColor(@ColorInt int color) {
return setBorderColor(ColorStateList.valueOf(color));
}
public ColorStateList getBorderColors() {
return mBorderColor;
}
public RoundedDrawable setBorderColor(ColorStateList colors) {
mBorderColor = colors != null ? colors : ColorStateList.valueOf(0);
mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
return this;
}
public boolean isOval() {
return mOval;
}
public RoundedDrawable setOval(boolean oval) {
mOval = oval;
return this;
}
public ScaleType getScaleType() {
return mScaleType;
}
public RoundedDrawable setScaleType(ScaleType scaleType) {
if (scaleType == null) {
scaleType = ScaleType.FIT_CENTER;
}
if (mScaleType != scaleType) {
mScaleType = scaleType;
updateShaderMatrix();
}
return this;
}
public Shader.TileMode getTileModeX() {
return mTileModeX;
}
public RoundedDrawable setTileModeX(Shader.TileMode tileModeX) {
if (mTileModeX != tileModeX) {
mTileModeX = tileModeX;
mRebuildShader = true;
invalidateSelf();
}
return this;
}
public Shader.TileMode getTileModeY() {
return mTileModeY;
}
public RoundedDrawable setTileModeY(Shader.TileMode tileModeY) {
if (mTileModeY != tileModeY) {
mTileModeY = tileModeY;
mRebuildShader = true;
invalidateSelf();
}
return this;
}
private static boolean only(int index, boolean[] booleans) {
for (int i = 0, len = booleans.length; i < len; i++) {
if (booleans[i] != (i == index)) {
return false;
}
}
return true;
}
private static boolean any(boolean[] booleans) {
for (boolean b : booleans) {
if (b) { return true; }
}
return false;
}
private static boolean all(boolean[] booleans) {
for (boolean b : booleans) {
if (b) { return false; }
}
return true;
}
public Bitmap toBitmap() {
return drawableToBitmap(this);
}
}

View File

@@ -0,0 +1,587 @@
/*
* Copyright (C) 2015 Vincent Mi
*
* 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
*
* http://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.mogo.module.push.view.roundimage;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.ColorFilter;
import android.graphics.Shader;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
import androidx.annotation.ColorInt;
import androidx.annotation.DimenRes;
import androidx.annotation.DrawableRes;
import androidx.appcompat.widget.AppCompatImageView;
import com.mogo.module.push.R;
@SuppressWarnings("UnusedDeclaration")
public class RoundedImageView extends AppCompatImageView {
// Constants for tile mode attributes
private static final int TILE_MODE_UNDEFINED = -2;
private static final int TILE_MODE_CLAMP = 0;
private static final int TILE_MODE_REPEAT = 1;
private static final int TILE_MODE_MIRROR = 2;
public static final String TAG = "RoundedImageView";
public static final float DEFAULT_RADIUS = 0f;
public static final float DEFAULT_BORDER_WIDTH = 0f;
public static final Shader.TileMode DEFAULT_TILE_MODE = Shader.TileMode.CLAMP;
private static final ImageView.ScaleType[] SCALE_TYPES = {
ImageView.ScaleType.MATRIX,
ImageView.ScaleType.FIT_XY,
ImageView.ScaleType.FIT_START,
ImageView.ScaleType.FIT_CENTER,
ImageView.ScaleType.FIT_END,
ImageView.ScaleType.CENTER,
ImageView.ScaleType.CENTER_CROP,
ImageView.ScaleType.CENTER_INSIDE
};
private final float[] mCornerRadii =
new float[] { DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS };
private Drawable mBackgroundDrawable;
private ColorStateList mBorderColor =
ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR);
private float mBorderWidth = DEFAULT_BORDER_WIDTH;
private ColorFilter mColorFilter = null;
private boolean mColorMod = false;
private Drawable mDrawable;
private boolean mHasColorFilter = false;
private boolean mIsOval = false;
private boolean mMutateBackground = false;
private int mResource;
private int mBackgroundResource;
private ImageView.ScaleType mScaleType;
private Shader.TileMode mTileModeX = DEFAULT_TILE_MODE;
private Shader.TileMode mTileModeY = DEFAULT_TILE_MODE;
public RoundedImageView(Context context) {
super(context);
}
public RoundedImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundedImageView, defStyle, 0);
int index = a.getInt(R.styleable.RoundedImageView_android_scaleType, -1);
if (index >= 0) {
setScaleType(SCALE_TYPES[index]);
} else {
// default scaletype to FIT_CENTER
setScaleType(ImageView.ScaleType.FIT_CENTER);
}
float cornerRadiusOverride =
a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius, -1);
mCornerRadii[Corner.TOP_LEFT] =
a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius_top_left, -1);
mCornerRadii[Corner.TOP_RIGHT] =
a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius_top_right, -1);
mCornerRadii[Corner.BOTTOM_RIGHT] =
a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius_bottom_right, -1);
mCornerRadii[Corner.BOTTOM_LEFT] =
a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius_bottom_left, -1);
boolean any = false;
for (int i = 0, len = mCornerRadii.length; i < len; i++) {
if (mCornerRadii[i] < 0) {
mCornerRadii[i] = 0f;
} else {
any = true;
}
}
if (!any) {
if (cornerRadiusOverride < 0) {
cornerRadiusOverride = DEFAULT_RADIUS;
}
for (int i = 0, len = mCornerRadii.length; i < len; i++) {
mCornerRadii[i] = cornerRadiusOverride;
}
}
mBorderWidth = a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_border_width, -1);
if (mBorderWidth < 0) {
mBorderWidth = DEFAULT_BORDER_WIDTH;
}
mBorderColor = a.getColorStateList(R.styleable.RoundedImageView_riv_border_color);
if (mBorderColor == null) {
mBorderColor = ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR);
}
mMutateBackground = a.getBoolean(R.styleable.RoundedImageView_riv_mutate_background, false);
mIsOval = a.getBoolean(R.styleable.RoundedImageView_riv_oval, false);
final int tileMode = a.getInt(R.styleable.RoundedImageView_riv_tile_Mode, TILE_MODE_UNDEFINED);
if (tileMode != TILE_MODE_UNDEFINED) {
setTileModeX(parseTileMode(tileMode));
setTileModeY(parseTileMode(tileMode));
}
final int tileModeX =
a.getInt(R.styleable.RoundedImageView_riv_tile_Mode_x, TILE_MODE_UNDEFINED);
if (tileModeX != TILE_MODE_UNDEFINED) {
setTileModeX(parseTileMode(tileModeX));
}
final int tileModeY =
a.getInt(R.styleable.RoundedImageView_riv_tile_Mode_y, TILE_MODE_UNDEFINED);
if (tileModeY != TILE_MODE_UNDEFINED) {
setTileModeY(parseTileMode(tileModeY));
}
updateDrawableAttrs();
updateBackgroundDrawableAttrs(true);
if (mMutateBackground) {
// when setBackground() is called by View constructor, mMutateBackground is not loaded from the attribute,
// so it's false by default, what doesn't allow to create the RoundedDrawable. At this point, after load
// mMutateBackground and updated BackgroundDrawable to RoundedDrawable, the View's background drawable needs to
// be changed to this new drawable.
//noinspection deprecation
super.setBackgroundDrawable(mBackgroundDrawable);
}
a.recycle();
}
private static Shader.TileMode parseTileMode(int tileMode) {
switch (tileMode) {
case TILE_MODE_CLAMP:
return Shader.TileMode.CLAMP;
case TILE_MODE_REPEAT:
return Shader.TileMode.REPEAT;
case TILE_MODE_MIRROR:
return Shader.TileMode.MIRROR;
default:
return null;
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
invalidate();
}
@Override
public ImageView.ScaleType getScaleType() {
return mScaleType;
}
@Override
public void setScaleType(ImageView.ScaleType scaleType) {
assert scaleType != null;
if (mScaleType != scaleType) {
mScaleType = scaleType;
switch (scaleType) {
case CENTER:
case CENTER_CROP:
case CENTER_INSIDE:
case FIT_CENTER:
case FIT_START:
case FIT_END:
case FIT_XY:
super.setScaleType(ImageView.ScaleType.FIT_XY);
break;
default:
super.setScaleType(scaleType);
break;
}
updateDrawableAttrs();
updateBackgroundDrawableAttrs(false);
invalidate();
}
}
@Override
public void setImageDrawable(Drawable drawable) {
mResource = 0;
mDrawable = RoundedDrawable.fromDrawable(drawable);
updateDrawableAttrs();
super.setImageDrawable(mDrawable);
}
@Override
public void setImageBitmap(Bitmap bm) {
mResource = 0;
mDrawable = RoundedDrawable.fromBitmap(bm);
updateDrawableAttrs();
super.setImageDrawable(mDrawable);
}
@Override
public void setImageResource(@DrawableRes int resId) {
if (mResource != resId) {
mResource = resId;
mDrawable = resolveResource();
updateDrawableAttrs();
super.setImageDrawable(mDrawable);
}
}
@Override public void setImageURI(Uri uri) {
super.setImageURI(uri);
setImageDrawable(getDrawable());
}
private Drawable resolveResource() {
Resources rsrc = getResources();
if (rsrc == null) { return null; }
Drawable d = null;
if (mResource != 0) {
try {
d = rsrc.getDrawable(mResource);
} catch (Exception e) {
Log.w(TAG, "Unable to find resource: " + mResource, e);
// Don't try again.
mResource = 0;
}
}
return RoundedDrawable.fromDrawable(d);
}
@Override
public void setBackground(Drawable background) {
setBackgroundDrawable(background);
}
@Override
public void setBackgroundResource(@DrawableRes int resId) {
if (mBackgroundResource != resId) {
mBackgroundResource = resId;
mBackgroundDrawable = resolveBackgroundResource();
setBackgroundDrawable(mBackgroundDrawable);
}
}
@Override
public void setBackgroundColor(int color) {
mBackgroundDrawable = new ColorDrawable(color);
setBackgroundDrawable(mBackgroundDrawable);
}
private Drawable resolveBackgroundResource() {
Resources rsrc = getResources();
if (rsrc == null) { return null; }
Drawable d = null;
if (mBackgroundResource != 0) {
try {
d = rsrc.getDrawable(mBackgroundResource);
} catch (Exception e) {
Log.w(TAG, "Unable to find resource: " + mBackgroundResource, e);
// Don't try again.
mBackgroundResource = 0;
}
}
return RoundedDrawable.fromDrawable(d);
}
private void updateDrawableAttrs() {
updateAttrs(mDrawable, mScaleType);
}
private void updateBackgroundDrawableAttrs(boolean convert) {
if (mMutateBackground) {
if (convert) {
mBackgroundDrawable = RoundedDrawable.fromDrawable(mBackgroundDrawable);
}
updateAttrs(mBackgroundDrawable, ImageView.ScaleType.FIT_XY);
}
}
@Override public void setColorFilter(ColorFilter cf) {
if (mColorFilter != cf) {
mColorFilter = cf;
mHasColorFilter = true;
mColorMod = true;
applyColorMod();
invalidate();
}
}
private void applyColorMod() {
// Only mutate and apply when modifications have occurred. This should
// not reset the mColorMod flag, since these filters need to be
// re-applied if the Drawable is changed.
if (mDrawable != null && mColorMod) {
mDrawable = mDrawable.mutate();
if (mHasColorFilter) {
mDrawable.setColorFilter(mColorFilter);
}
//mDrawable.setXfermode(mXfermode);
//mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
}
}
private void updateAttrs(Drawable drawable, ImageView.ScaleType scaleType) {
if (drawable == null) { return; }
if (drawable instanceof RoundedDrawable) {
((RoundedDrawable) drawable)
.setScaleType(scaleType)
.setBorderWidth(mBorderWidth)
.setBorderColor(mBorderColor)
.setOval(mIsOval)
.setTileModeX(mTileModeX)
.setTileModeY(mTileModeY);
if (mCornerRadii != null) {
((RoundedDrawable) drawable).setCornerRadius(
mCornerRadii[Corner.TOP_LEFT],
mCornerRadii[Corner.TOP_RIGHT],
mCornerRadii[Corner.BOTTOM_RIGHT],
mCornerRadii[Corner.BOTTOM_LEFT]);
}
applyColorMod();
} else if (drawable instanceof LayerDrawable) {
// loop through layers to and set drawable attrs
LayerDrawable ld = ((LayerDrawable) drawable);
for (int i = 0, layers = ld.getNumberOfLayers(); i < layers; i++) {
updateAttrs(ld.getDrawable(i), scaleType);
}
}
}
@Override
@Deprecated
public void setBackgroundDrawable(Drawable background) {
mBackgroundDrawable = background;
updateBackgroundDrawableAttrs(true);
//noinspection deprecation
super.setBackgroundDrawable(mBackgroundDrawable);
}
/**
* @return the largest corner radius.
*/
public float getCornerRadius() {
return getMaxCornerRadius();
}
/**
* @return the largest corner radius.
*/
public float getMaxCornerRadius() {
float maxRadius = 0;
for (float r : mCornerRadii) {
maxRadius = Math.max(r, maxRadius);
}
return maxRadius;
}
/**
* Get the corner radius of a specified corner.
*
* @param corner the corner.
* @return the radius.
*/
public float getCornerRadius(@Corner int corner) {
return mCornerRadii[corner];
}
/**
* Set all the corner radii from a dimension resource id.
*
* @param resId dimension resource id of radii.
*/
public void setCornerRadiusDimen(@DimenRes int resId) {
float radius = getResources().getDimension(resId);
setCornerRadius(radius, radius, radius, radius);
}
/**
* Set the corner radius of a specific corner from a dimension resource id.
*
* @param corner the corner to set.
* @param resId the dimension resource id of the corner radius.
*/
public void setCornerRadiusDimen(@Corner int corner, @DimenRes int resId) {
setCornerRadius(corner, getResources().getDimensionPixelSize(resId));
}
/**
* Set the corner radii of all corners in px.
*
* @param radius the radius to set.
*/
public void setCornerRadius(float radius) {
setCornerRadius(radius, radius, radius, radius);
}
/**
* Set the corner radius of a specific corner in px.
*
* @param corner the corner to set.
* @param radius the corner radius to set in px.
*/
public void setCornerRadius(@Corner int corner, float radius) {
if (mCornerRadii[corner] == radius) {
return;
}
mCornerRadii[corner] = radius;
updateDrawableAttrs();
updateBackgroundDrawableAttrs(false);
invalidate();
}
/**
* Set the corner radii of each corner individually. Currently only one unique nonzero value is
* supported.
*
* @param topLeft radius of the top left corner in px.
* @param topRight radius of the top right corner in px.
* @param bottomRight radius of the bottom right corner in px.
* @param bottomLeft radius of the bottom left corner in px.
*/
public void setCornerRadius(float topLeft, float topRight, float bottomLeft, float bottomRight) {
if (mCornerRadii[Corner.TOP_LEFT] == topLeft
&& mCornerRadii[Corner.TOP_RIGHT] == topRight
&& mCornerRadii[Corner.BOTTOM_RIGHT] == bottomRight
&& mCornerRadii[Corner.BOTTOM_LEFT] == bottomLeft) {
return;
}
mCornerRadii[Corner.TOP_LEFT] = topLeft;
mCornerRadii[Corner.TOP_RIGHT] = topRight;
mCornerRadii[Corner.BOTTOM_LEFT] = bottomLeft;
mCornerRadii[Corner.BOTTOM_RIGHT] = bottomRight;
updateDrawableAttrs();
updateBackgroundDrawableAttrs(false);
invalidate();
}
public float getBorderWidth() {
return mBorderWidth;
}
public void setBorderWidth(@DimenRes int resId) {
setBorderWidth(getResources().getDimension(resId));
}
public void setBorderWidth(float width) {
if (mBorderWidth == width) { return; }
mBorderWidth = width;
updateDrawableAttrs();
updateBackgroundDrawableAttrs(false);
invalidate();
}
@ColorInt
public int getBorderColor() {
return mBorderColor.getDefaultColor();
}
public void setBorderColor(@ColorInt int color) {
setBorderColor(ColorStateList.valueOf(color));
}
public ColorStateList getBorderColors() {
return mBorderColor;
}
public void setBorderColor(ColorStateList colors) {
if (mBorderColor.equals(colors)) { return; }
mBorderColor =
(colors != null) ? colors : ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR);
updateDrawableAttrs();
updateBackgroundDrawableAttrs(false);
if (mBorderWidth > 0) {
invalidate();
}
}
public boolean isOval() {
return mIsOval;
}
public void setOval(boolean oval) {
mIsOval = oval;
updateDrawableAttrs();
updateBackgroundDrawableAttrs(false);
invalidate();
}
public Shader.TileMode getTileModeX() {
return mTileModeX;
}
public void setTileModeX(Shader.TileMode tileModeX) {
if (this.mTileModeX == tileModeX) { return; }
this.mTileModeX = tileModeX;
updateDrawableAttrs();
updateBackgroundDrawableAttrs(false);
invalidate();
}
public Shader.TileMode getTileModeY() {
return mTileModeY;
}
public void setTileModeY(Shader.TileMode tileModeY) {
if (this.mTileModeY == tileModeY) { return; }
this.mTileModeY = tileModeY;
updateDrawableAttrs();
updateBackgroundDrawableAttrs(false);
invalidate();
}
public boolean mutatesBackground() {
return mMutateBackground;
}
public void mutateBackground(boolean mutate) {
if (mMutateBackground == mutate) { return; }
mMutateBackground = mutate;
updateBackgroundDrawableAttrs(true);
invalidate();
}
}

View File

@@ -0,0 +1,33 @@
package com.mogo.module.push.viewmodel
import com.mogo.module.push.model.PushBean
import com.mogo.module.push.repository.PushRepository
import com.mogo.module.push.utils.HandlerUtils
class MessageViewModel(private val messageViewModel: MessageListChange) {
var list: MutableList<PushBean>? = null
set(value) {
field = value
messageViewModel.messageListChange(value)
}
init {
HandlerUtils.mBgHandler.post {
list = PushRepository.pushRepository.getAll()
}
}
interface MessageListChange {
fun messageListChange(list: MutableList<PushBean>?)
}
fun delete(bean: PushBean) {
PushRepository.pushRepository.delete(bean)
}
fun deleteAll() {
list = null
PushRepository.pushRepository.deleteAll()
}
}

View File

@@ -0,0 +1,192 @@
package com.mogo.module.push.viewmodel
import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.mogo.commons.voice.AIAssist
import com.mogo.commons.voice.IMogoVoiceCmdCallBack
import com.mogo.module.push.Config
import com.zhidao.auto.platform.voice.VoiceClient
import com.mogo.module.push.model.PushBean
import com.mogo.module.push.repository.PushRepository
import com.mogo.module.push.utils.AnalyticsUtils
import com.mogo.module.push.utils.dealSchema
import com.mogo.module.push.view.FloatView
class PushViewModel(
private val mContext: Context,
private val pushRepository: PushRepository
) {
companion object {
const val VOICE_ACTION_PUSH_MAIN = "VOICE_ACTION_PUSH_MAIN"
const val VOICE_ACTION_PUSH_LEFT = "VOICE_ACTION_PUSH_LEFT"
const val VOICE_ACTION_PUSH_RIGHT = "VOICE_ACTION_PUSH_RIGHT"
const val VOICE_ACTION_PUSH_CANCEL = "VOICE_ACTION_PUSH_CANCEL"
}
var floatView: FloatView? = null
private var mVoiceClient: AIAssist = AIAssist.getInstance(mContext)
private val voiceCmdCallback = object : IMogoVoiceCmdCallBack {
override fun onSpeakEnd(speakText: String?) {
}
override fun onCmdSelected(cmd: String?) {
if (!cmd.isNullOrEmpty()) {
dealCmd(cmd)
}
}
override fun onCmdAction(speakText: String?) {
}
override fun onCmdCancel(speakText: String?) {
}
override fun onSpeakSelectTimeOut(speakText: String?) {
}
}
fun dealCmd(cmd: String, isClick: String = "2") {
if (TextUtils.isEmpty(cmd)) {
return
}
pushBean?.let {
when (cmd) {
VOICE_ACTION_PUSH_MAIN -> {
AnalyticsUtils.track(Config.NEWS_CARD_CLICK, "trigger_type", isClick)
if (isClick == "2") {
mVoiceClient.speakTTSVoice("好的", voiceCmdCallback)
}
if (!it.mainSchema.isNullOrEmpty()) {
dealSchema(it.mainSchema, mContext)
}
}
VOICE_ACTION_PUSH_LEFT -> {
if (it.buttons.isNullOrEmpty()) {
return
}
AnalyticsUtils.track(
Config.NEWS_CARD_CLICK_BTN,
"trigger_type",
isClick, "btn_text", it.buttons[0].text
)
if (isClick == "2") {
mVoiceClient.speakTTSVoice("好的", voiceCmdCallback)
}
if (!it.buttons[0].action.isNullOrEmpty()) {
dealSchema(it.buttons[0].action, mContext)
}
}
VOICE_ACTION_PUSH_RIGHT -> {
if (it.buttons.isNullOrEmpty()) {
return
}
AnalyticsUtils.track(
Config.NEWS_CARD_CLICK_BTN,
"trigger_type",
isClick, "btn_text", it.buttons[1].text
)
if (isClick == "2") {
mVoiceClient.speakTTSVoice("好的", voiceCmdCallback)
}
if (!it.buttons[1].action.isNullOrEmpty()) {
dealSchema(it.buttons[1].action, mContext)
}
}
VOICE_ACTION_PUSH_CANCEL -> {
AnalyticsUtils.track(
Config.NEWS_CARD_SWIPE,
"trigger_type",
isClick
)
if (isClick == "2") {
mVoiceClient.speakTTSVoice("好的", voiceCmdCallback)
}
}
}
}
}
var pushBean: PushBean? = null
set(value) {
field = value
if (value == null) {
floatView?.hide()
floatView = null
return
}
field?.showTimeoutShadow = field?.showTimeout?:0
Log.d("yilz", "pushbean = $value")
if (value != null && value!!.imageUrl == null) {
value!!.imageUrl = ""
}
if (floatView == null) {
floatView = FloatView(this, mContext)
}
floatView?.pushBeanChanged(field)
registerVoiceCMD()
pushRepository.setPushUIShow(field != null)
}
fun pushMessageFinish(needSave: Boolean = false) {
pushRepository.iterateNext(needSave)
}
private fun registerVoiceCMD() {
mVoiceClient.unregisterUnWakeupCommand(VOICE_ACTION_PUSH_CANCEL)
mVoiceClient.unregisterUnWakeupCommand(VOICE_ACTION_PUSH_MAIN)
mVoiceClient.unregisterUnWakeupCommand(VOICE_ACTION_PUSH_LEFT)
mVoiceClient.unregisterUnWakeupCommand(VOICE_ACTION_PUSH_RIGHT)
if (pushBean != null && !pushBean!!.cancelVoiceCmd.isNullOrEmpty()) {
mVoiceClient.registerUnWakeupCommand(
VOICE_ACTION_PUSH_CANCEL,
pushBean!!.cancelVoiceCmd!!.toTypedArray(),
voiceCmdCallback
)
} else {
mVoiceClient.registerUnWakeupCommand(
VOICE_ACTION_PUSH_CANCEL,
arrayOf("忽略", "取消", "算了", "不要", "不要了", "不看了", "关闭"),
voiceCmdCallback
)
}
if (pushBean != null && !pushBean!!.mainVoiceCmd.isNullOrEmpty()) {
mVoiceClient.registerUnWakeupCommand(
VOICE_ACTION_PUSH_MAIN,
pushBean!!.mainVoiceCmd!!.toTypedArray(),
voiceCmdCallback
)
}
pushBean?.buttons?.forEach {
it?.voiceCmd?.apply {
mVoiceClient.registerUnWakeupCommand(
VOICE_ACTION_PUSH_RIGHT,
toTypedArray(),
voiceCmdCallback
)
}
}
}
fun speakDefault(voice: String) {
mVoiceClient.speakTTSVoice(voice, voiceCmdCallback)
}
fun pauseAnimator(on: Boolean) {
floatView?.pauseTimer(on)
}
fun isAddWindow(): Boolean = floatView?.isAddWindow() ?: false
fun push() {
pushBean?.apply {
showTimeout = showTimeoutShadow
pushRepository.push(this)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/module_push_clear_bg_radius" />
<solid android:color="#FF494B66 "/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/module_push_content_only_line_space" />
<solid android:color=" #222533" />
</shape>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<corners android:radius="@dimen/module_push_ui_decrease_timer_corner"/>
<solid android:color="#7F000000"/>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/module_push_ui_bkg_corner" />
<solid android:color="#F23F4057" />
</shape>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape>
<corners android:radius="@dimen/module_push_ui_button_radius" />
<gradient android:angle="135" android:endColor="#1F7EFF" android:startColor="#1E57A4" android:type="linear" />
</shape>
</item>
<item android:state_pressed="true">
<shape>
<corners android:radius="@dimen/module_push_ui_button_radius" />
<gradient android:angle="135" android:endColor="#124C9A" android:startColor="#123463" android:type="linear" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FA36374A"/>
</shape>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/module_push_button_margin_top" />
<solid android:color="#242736" />
<!-- <gradient
android:angle="-90"
android:endColor="#2F3047"
android:startColor="#3F4057"
android:type="linear" />-->
</shape>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape android:shape="rectangle">
<corners android:radius="@dimen/module_push_ui_button_radius" />
<gradient android:angle="135" android:endColor="#616381" android:startColor="#48495E" />
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<corners android:radius="@dimen/module_push_ui_button_radius" />
<gradient android:angle="135" android:endColor="#3A3B4D" android:startColor="#2B2C38" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360">
<shape
android:innerRadius="@dimen/module_push_timer_inner_radius"
android:shape="ring"
android:thickness="@dimen/module_push_timer_thickness"
android:useLevel="false">
<gradient
android:centerY="0.50"
android:endColor="#001F7FFF"
android:startColor="#7A8199"
android:type="sweep"
android:useLevel="false" />
</shape>
</rotate>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/module_push_ui_height"
android:background="@drawable/module_push_item_background">
<com.mogo.module.push.view.roundimage.RoundedImageView
android:id="@+id/module_push_image"
android:layout_width="@dimen/module_push_ui_image_width"
android:layout_height="@dimen/module_push_ui_image_height"
android:layout_marginLeft="@dimen/module_push_ui_image_marLeft"
android:background="#333333"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:riv_corner_radius="@dimen/module_push_ui_image_corner" />
<LinearLayout
android:id="@+id/module_push_app_icon_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/module_push_ui_app_icon_leftMargin"
android:layout_marginTop="@dimen/module_push_ui_app_icon_topMargin"
android:gravity="center_vertical"
android:minHeight="@dimen/module_push_ui_app_icon_size"
app:layout_constraintRight_toLeftOf="@+id/module_push_progress_bar_frame"
app:layout_constraintLeft_toRightOf="@+id/module_push_image"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginTop="@dimen/module_push_ui_app_icon_goneTopMargin">
<ImageView
android:id="@+id/module_push_app_icon"
android:layout_width="@dimen/module_push_ui_app_icon_size"
android:layout_height="@dimen/module_push_ui_app_icon_size"
android:layout_marginRight="@dimen/module_push_title_margin_start"
android:background="@drawable/module_push_ui_ic_message2" />
<TextView
android:id="@+id/module_push_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical|left"
android:maxWidth="@dimen/module_push_title_mix_width"
android:singleLine="true"
android:maxLength="25"
tools:text="标题标题标题标题标题标题标题"
android:textColor="@android:color/white"
android:textSize="@dimen/module_push_ui_title_textSize"
app:layout_constrainedWidth="true" />
</LinearLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="@dimen/module_push_ui_content_marginTop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="@id/module_push_app_icon_title"
app:layout_constraintRight_toRightOf="@id/module_push_progress_bar_frame"
app:layout_constraintTop_toBottomOf="@id/module_push_app_icon_title">
<TextView
android:id="@+id/module_push_content"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:maxLines="3"
android:layout_above="@+id/module_push_buttons"
android:ellipsize="end"
android:textColor="@android:color/white"
android:textSize="@dimen/module_push_ui_title_text_size"
tools:text="文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内" />
<LinearLayout
android:id="@+id/module_push_buttons"
android:layout_width="match_parent"
android:layout_height="@dimen/module_push_button_height"
android:layout_alignParentBottom="true"
android:layout_marginTop="@dimen/module_push_button_margin_top"
android:layout_marginBottom="@dimen/module_push_button_margin_bottom"
android:gravity="center"
android:weightSum="2">
<TextView
android:id="@+id/module_push_button_left"
android:layout_width="@dimen/module_push_button_width"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/module_push_left_button"
android:ellipsize="end"
android:gravity="center"
android:enabled="true"
android:lines="1"
android:maxWidth="@dimen/module_push_button_maxWidth"
android:maxLength="6"
android:text="查看"
android:textColor="#FFFFFF"
android:textSize="@dimen/module_push_title_text_size"
android:textStyle="bold" />
<TextView
android:id="@+id/module_push_button_right"
android:layout_width="@dimen/module_push_button_width"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/module_push_button_right_marLeft"
android:layout_weight="1"
android:background="@drawable/module_push_right_button"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:maxWidth="@dimen/module_push_button_maxWidth"
android:maxLength="6"
android:text="忽略"
android:textColor="#FFFFFF"
android:textSize="@dimen/module_push_title_text_size"
android:textStyle="bold" />
</LinearLayout>
</RelativeLayout>
<FrameLayout
android:id="@+id/module_push_progress_bar_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/module_push_progress_bar_frame_marginTop"
android:layout_marginEnd="@dimen/module_push_progress_bar_frame_marginEnd"
android:background="@drawable/module_push_decrease_timer_bkg"
android:paddingLeft="@dimen/module_push_progress_bar_frame_padding"
android:paddingRight="@dimen/module_push_progress_bar_frame_padding"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/module_push_timer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#80FFFFFF"
android:textSize="@dimen/module_push_ui_timer_textSize"
tools:text="11s" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/module_push_ui_width_vertical"
android:layout_height="wrap_content"
android:background="@drawable/module_push_item_background"
android:maxHeight="@dimen/module_push_item_maxHeight_vertical"
android:orientation="vertical"
android:paddingLeft="@dimen/module_push_ui_app_icon_leftMargin_vertical"
android:paddingRight="@dimen/module_push_ui_app_icon_leftMargin_vertical">
<LinearLayout
android:id="@+id/module_push_content_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/module_push_content_paddingBottom_vertical">
<RelativeLayout
android:id="@+id/module_push_app_icon_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/module_push_ui_app_icon_topMargin_vertical"
android:layout_marginBottom="@dimen/module_push_image_marginTop_vertical"
android:minHeight="@dimen/module_push_ui_app_icon_size">
<ImageView
android:id="@+id/module_push_app_icon"
android:layout_width="@dimen/module_push_ui_app_icon_size"
android:layout_height="@dimen/module_push_ui_app_icon_size"
android:layout_marginRight="@dimen/module_push_title_margin_start"
tools:background="@drawable/module_push_ui_ic_message2" />
<TextView
android:id="@+id/module_push_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/module_push_app_icon"
android:ellipsize="end"
android:gravity="center_vertical|left"
android:maxWidth="@dimen/module_push_title_mix_width"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="@dimen/module_push_ui_title_textSize"
tools:text="push title" />
<FrameLayout
android:id="@+id/module_push_progress_bar_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@drawable/module_push_decrease_timer_bkg"
android:paddingLeft="@dimen/module_push_progress_bar_frame_padding"
android:paddingRight="@dimen/module_push_progress_bar_frame_padding">
<TextView
android:id="@+id/module_push_timer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#80FFFFFF"
android:textSize="@dimen/module_push_ui_timer_textSize"
tools:text="11s" />
</FrameLayout>
</RelativeLayout>
<com.mogo.module.push.view.roundimage.RoundedImageView
android:id="@+id/module_push_image"
android:layout_width="@dimen/module_push_ui_image_width_vertical"
android:layout_height="@dimen/module_push_ui_image_height_vertical"
android:layout_gravity="center"
android:background="#333333"
android:layout_marginBottom="@dimen/module_push_ui_content_marginTop_vertical"
android:scaleType="fitXY"
app:riv_corner_radius="@dimen/module_push_ui_image_corner" />
<TextView
android:id="@+id/module_push_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/module_push_button_margin_top"
android:ellipsize="end"
android:gravity="center"
android:maxLines="4"
android:textColor="@android:color/white"
android:textSize="@dimen/module_push_ui_title_text_size"
tools:text="文本内容文本内文本内容文本内文本内容文本内文本内容文本内文本内容文本内文本内容文本内文本内容文本内文本内容文本内文本内容文本内" />
</LinearLayout>
<LinearLayout
android:id="@+id/module_push_buttons"
android:layout_width="match_parent"
android:layout_height="@dimen/module_push_button_height"
android:layout_gravity="bottom"
android:layout_marginBottom="@dimen/module_push_button_margin_bottom"
android:gravity="center">
<TextView
android:id="@+id/module_push_button_left"
android:layout_width="@dimen/module_push_button_width"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/module_push_left_button"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:maxWidth="@dimen/module_push_button_maxWidth"
android:maxLength="6"
android:text="查看"
android:textColor="#FFFFFF"
android:textSize="@dimen/module_push_title_text_size"
android:textStyle="bold" />
<TextView
android:id="@+id/module_push_button_right"
android:layout_width="@dimen/module_push_button_width"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/module_push_button_right_marLeft"
android:layout_weight="1"
android:background="@drawable/module_push_right_button"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:maxWidth="@dimen/module_push_button_maxWidth"
android:maxLength="6"
android:text="忽略"
android:textColor="#FFFFFF"
android:textSize="@dimen/module_push_title_text_size"
android:textStyle="bold" />
</LinearLayout>
</FrameLayout>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/module_push_activity_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/module_push_activity_title_margin_top"
android:text="历史消息2"
android:textColor="#FFFFFFFF"
android:textSize="@dimen/module_push_activity_title_text_size" />
<ImageView
android:id="@+id/module_push_activity_close"
android:layout_width="@dimen/module_push_margin_start"
android:layout_height="@dimen/module_push_margin_start"
android:layout_gravity="end"
android:layout_marginTop="@dimen/module_push_activity_close_margin_top"
android:layout_marginEnd="@dimen/module_push_activity_close_margin_end"
android:padding="@dimen/module_push_activity_close_padding"
android:src="@drawable/module_push_close" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/module_push_activity_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/module_push_activity_recycler_view_margin_top"
android:layout_marginEnd="@dimen/module_push_activity_close_margin_end" />
<TextView
android:id="@+id/module_push_activity_not_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="暂无消息"
android:textColor="@android:color/white"
android:textSize="@dimen/module_push_activity_not_data_text_size" />
<TextView
android:id="@+id/module_push_activity_clear"
android:layout_width="@dimen/module_push_content_only_height"
android:layout_height="@dimen/module_push_button_height"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="@dimen/module_push_activity_clear_margin_bottom"
android:background="@drawable/module_push_activity_clear_bg"
android:gravity="center"
android:text="清空历史消息"
android:textColor="@android:color/white"
android:textSize="@dimen/module_push_title_text_size"
android:visibility="gone" />
</FrameLayout>

View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<com.mogo.module.push.view.SwipeItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="@dimen/module_push_content_only_line_space">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="@dimen/module_push_message_item_height">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/module_push_item_click"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/module_push_activity_close_margin_end"
android:background="@drawable/module_push_recycler_item_background">
<com.mogo.service.imageloader.MogoImageView
android:id="@+id/module_push_item_app_icon"
android:layout_width="@dimen/module_push_message_app_icon_size"
android:layout_height="@dimen/module_push_message_app_icon_size"
android:layout_marginStart="@dimen/module_push_message_margin_start"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/module_push_item_title"
android:layout_width="@dimen/module_push_item_content_width"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/module_push_image_margin_top"
android:layout_marginTop="@dimen/module_push_item_title_margin_top"
android:layout_marginBottom="@dimen/module_push_item_title_margin_bottom"
android:ellipsize="end"
android:gravity="left"
android:maxLines="1"
android:text="push title"
android:textColor="@android:color/white"
android:textSize="@dimen/module_push_title_text_size"
app:layout_constraintBottom_toTopOf="@+id/module_push_item_content"
app:layout_constraintLeft_toRightOf="@+id/module_push_item_app_icon"
app:layout_constraintWidth_max="@dimen/module_push_item_content_width"
app:layout_goneMarginBottom="@dimen/module_push_item_title_gone_margin_bottom" />
<TextView
android:id="@+id/module_push_item_content"
android:layout_width="@dimen/module_push_item_content_width"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/module_push_item_content_margin_end"
android:layout_marginBottom="@dimen/module_push_item_content_margin_bottom"
android:ellipsize="end"
android:gravity="left"
android:maxLines="1"
android:text="发现系统新版本共140.3M。部分功能优化,建议下载升级。"
android:textColor="#80FFFFFF"
android:textSize="@dimen/module_push_item_content_text_size"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/module_push_item_title" />
<com.mogo.service.imageloader.MogoImageView
android:id="@+id/module_push_item_image"
android:layout_width="@dimen/module_push_message_item_image_size"
android:layout_height="@dimen/module_push_message_item_image_size"
android:layout_marginEnd="@dimen/module_push_message_item_image_margin_end"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:miv_radius="@dimen/module_push_item_image_radius"
app:miv_shape="round" />
<TextView
android:id="@+id/module_push_item_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/module_push_image_margin_top"
android:layout_marginEnd="@dimen/module_push_image_margin_top"
android:gravity="right"
android:maxLines="1"
android:text="3:20"
android:textColor="#80FFFFFF"
android:textSize="@dimen/module_push_massage_time_text_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/module_push_button_margin_top">
<TextView
android:id="@+id/module_push_item_delete"
android:layout_width="@dimen/module_push_message_item_height"
android:layout_height="@dimen/module_push_message_item_height"
android:layout_marginStart="@dimen/module_push_button_margin_top"
android:background="@drawable/module_push_recycler_item_background"
android:gravity="center"
android:text="清除"
android:textColor="@android:color/white"
android:textSize="@dimen/module_push_title_text_size" />
</FrameLayout>
</com.mogo.module.push.view.SwipeItemLayout>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<dimen name="module_push_margin_top">16px</dimen>
<dimen name="module_push_size">352px</dimen>
<dimen name="module_push_margin_start">32px</dimen>
<dimen name="module_push_app_icon_size">32px</dimen>
<dimen name="module_push_app_icon_margin_start">16.5px</dimen>
<dimen name="module_push_title_margin_start">12px</dimen>
<dimen name="module_push_title_margin_top">20px</dimen>
<dimen name="module_push_title_text_size">18px</dimen>
<dimen name="module_push_title_mix_width">210px</dimen>
<dimen name="module_push_timer_margin_end">13px</dimen>
<dimen name="module_push_timer_text_size">15px</dimen>
<dimen name="module_push_timer_margin_top">18px</dimen>
<dimen name="module_push_image_width">320px</dimen>
<dimen name="module_push_image_height">180px</dimen>
<dimen name="module_push_image_margin_top">21px</dimen>
<dimen name="module_push_content_only_width">320px</dimen>
<dimen name="module_push_content_only_height">160px</dimen>
<dimen name="module_push_content_only_line_space">9px</dimen>
<dimen name="module_push_content_only_padding">20px</dimen>
<dimen name="module_push_button_width">0px</dimen>
<dimen name="module_push_button_height">48px</dimen>
<dimen name="module_push_button_margin_top">10px</dimen>
<dimen name="module_push_button_margin_bottom">14px</dimen>
<dimen name="module_push_activity_title_margin_top">35px</dimen>
<dimen name="module_push_activity_title_text_size">20px</dimen>
<dimen name="module_push_activity_close_margin_top">29px</dimen>
<dimen name="module_push_activity_close_margin_end">90px</dimen>
<dimen name="module_push_activity_close_padding">5px</dimen>
<dimen name="module_push_activity_recycler_view_margin_top">92px</dimen>
<dimen name="module_push_activity_not_data_text_size">38px</dimen>
<dimen name="module_push_activity_clear_margin_bottom">36px</dimen>
<dimen name="module_push_message_item_height">106px</dimen>
<dimen name="module_push_message_app_icon_size">64px</dimen>
<dimen name="module_push_message_margin_start">24px</dimen>
<dimen name="module_push_item_title_margin_top">25px</dimen>
<dimen name="module_push_item_title_gone_margin_bottom">44px</dimen>
<dimen name="module_push_item_title_margin_bottom">2px</dimen>
<dimen name="module_push_item_content_margin_end">20px</dimen>
<dimen name="module_push_item_content_margin_bottom">27px</dimen>
<dimen name="module_push_item_content_text_size">16px</dimen>
<dimen name="module_push_message_item_image_size">64px</dimen>
<dimen name="module_push_message_item_image_margin_end">100px</dimen>
<dimen name="module_push_massage_time_text_size">14px</dimen>
<dimen name="module_push_image_margin_bottom">22px</dimen>
<dimen name="module_push_button_radius">27px</dimen>
<dimen name="module_push_timer_inner_radius">14px</dimen>
<dimen name="module_push_timer_thickness">1.5px</dimen>
<dimen name="module_push_clear_bg_radius">24px</dimen>
<dimen name="module_push_image_radius">10px</dimen>
<dimen name="module_push_item_image_radius">4px</dimen>
<dimen name="module_push_item_content_width">560px</dimen>
<dimen name="module_push_ui_height">194px</dimen>
<dimen name="module_push_ui_image_width">266px</dimen>
<dimen name="module_push_ui_image_height">178px</dimen>
<dimen name="module_push_ui_image_marLeft">8px</dimen>
<dimen name="module_push_ui_app_icon_leftMargin">12px</dimen>
<dimen name="module_push_ui_app_icon_topMargin">16px</dimen>
<dimen name="module_push_ui_app_icon_goneTopMargin">19px</dimen>
<dimen name="module_push_ui_app_icon_size">30px</dimen>
<dimen name="module_push_ui_title_textSize">16px</dimen>
<dimen name="module_push_ui_decrease_timer_corner">8px</dimen>
<dimen name="module_push_progress_bar_frame_marginTop">17px</dimen>
<dimen name="module_push_progress_bar_frame_marginEnd">19px</dimen>
<dimen name="module_push_ui_timer_textSize">16px</dimen>
<dimen name="module_push_progress_bar_frame_padding">11px</dimen>
<dimen name="module_push_ui_content_marginTop">6px</dimen>
<dimen name="module_push_ui_title_text_size">18px</dimen>
<dimen name="module_push_ui_button_radius">10px</dimen>
<dimen name="module_push_ui_bkg_corner">17px</dimen>
<dimen name="module_push_button_right_marLeft">10px</dimen>
<dimen name="module_push_ui_image_corner">8px</dimen>
<dimen name="module_push_button_maxWidth">242px</dimen>
<dimen name="module_push_ui_height_vertical">270px</dimen>
<dimen name="module_push_ui_width_vertical">374px</dimen>
<dimen name="module_push_ui_app_icon_leftMargin_vertical">24px</dimen>
<dimen name="module_push_ui_app_icon_topMargin_vertical">19px</dimen>
<dimen name="module_push_image_marginTop_vertical">8px</dimen>
<dimen name="module_push_ui_image_width_vertical">328px</dimen>
<dimen name="module_push_ui_image_height_vertical">164px</dimen>
<dimen name="module_push_ui_content_marginTop_vertical">15px</dimen>
<dimen name="module_push_image_qr_size_vertical">150px</dimen>
<dimen name="module_push_window_x">20px</dimen>
<dimen name="module_push_window_y">0px</dimen>
<dimen name="module_push_item_minHeight_vertical">310px</dimen>
<dimen name="module_push_item_maxHeight_vertical">350px</dimen>
<dimen name="module_push_content_paddingBottom_vertical">60px</dimen>
</resources>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<dimen name="module_push_margin_top">16px</dimen>
<dimen name="module_push_size">352px</dimen>
<dimen name="module_push_margin_start">32px</dimen>
<dimen name="module_push_app_icon_size">32px</dimen>
<dimen name="module_push_app_icon_margin_start">16.5px</dimen>
<dimen name="module_push_title_margin_start">12px</dimen>
<dimen name="module_push_title_margin_top">20px</dimen>
<dimen name="module_push_title_text_size">18px</dimen>
<dimen name="module_push_title_mix_width">210px</dimen>
<dimen name="module_push_timer_margin_end">13px</dimen>
<dimen name="module_push_timer_text_size">15px</dimen>
<dimen name="module_push_timer_margin_top">18px</dimen>
<dimen name="module_push_image_width">320px</dimen>
<dimen name="module_push_image_height">180px</dimen>
<dimen name="module_push_image_margin_top">21px</dimen>
<dimen name="module_push_content_only_width">320px</dimen>
<dimen name="module_push_content_only_height">160px</dimen>
<dimen name="module_push_content_only_line_space">9px</dimen>
<dimen name="module_push_content_only_padding">20px</dimen>
<dimen name="module_push_button_width">0px</dimen>
<dimen name="module_push_button_height">48px</dimen>
<dimen name="module_push_button_margin_top">10px</dimen>
<dimen name="module_push_button_margin_bottom">14px</dimen>
<dimen name="module_push_activity_title_margin_top">35px</dimen>
<dimen name="module_push_activity_title_text_size">20px</dimen>
<dimen name="module_push_activity_close_margin_top">29px</dimen>
<dimen name="module_push_activity_close_margin_end">90px</dimen>
<dimen name="module_push_activity_close_padding">5px</dimen>
<dimen name="module_push_activity_recycler_view_margin_top">92px</dimen>
<dimen name="module_push_activity_not_data_text_size">38px</dimen>
<dimen name="module_push_activity_clear_margin_bottom">36px</dimen>
<dimen name="module_push_message_item_height">106px</dimen>
<dimen name="module_push_message_app_icon_size">64px</dimen>
<dimen name="module_push_message_margin_start">24px</dimen>
<dimen name="module_push_item_title_margin_top">25px</dimen>
<dimen name="module_push_item_title_gone_margin_bottom">44px</dimen>
<dimen name="module_push_item_title_margin_bottom">2px</dimen>
<dimen name="module_push_item_content_margin_end">20px</dimen>
<dimen name="module_push_item_content_margin_bottom">27px</dimen>
<dimen name="module_push_item_content_text_size">16px</dimen>
<dimen name="module_push_message_item_image_size">64px</dimen>
<dimen name="module_push_message_item_image_margin_end">100px</dimen>
<dimen name="module_push_massage_time_text_size">14px</dimen>
<dimen name="module_push_image_margin_bottom">22px</dimen>
<dimen name="module_push_button_radius">27px</dimen>
<dimen name="module_push_timer_inner_radius">14px</dimen>
<dimen name="module_push_timer_thickness">1.5px</dimen>
<dimen name="module_push_clear_bg_radius">24px</dimen>
<dimen name="module_push_image_radius">10px</dimen>
<dimen name="module_push_item_image_radius">4px</dimen>
<dimen name="module_push_item_content_width">560px</dimen>
<dimen name="module_push_ui_height">194px</dimen>
<dimen name="module_push_ui_image_width">266px</dimen>
<dimen name="module_push_ui_image_height">178px</dimen>
<dimen name="module_push_ui_image_marLeft">8px</dimen>
<dimen name="module_push_ui_app_icon_leftMargin">12px</dimen>
<dimen name="module_push_ui_app_icon_topMargin">16px</dimen>
<dimen name="module_push_ui_app_icon_goneTopMargin">19px</dimen>
<dimen name="module_push_ui_app_icon_size">30px</dimen>
<dimen name="module_push_ui_title_textSize">16px</dimen>
<dimen name="module_push_ui_decrease_timer_corner">8px</dimen>
<dimen name="module_push_progress_bar_frame_marginTop">17px</dimen>
<dimen name="module_push_progress_bar_frame_marginEnd">19px</dimen>
<dimen name="module_push_ui_timer_textSize">16px</dimen>
<dimen name="module_push_progress_bar_frame_padding">11px</dimen>
<dimen name="module_push_ui_content_marginTop">6px</dimen>
<dimen name="module_push_ui_title_text_size">18px</dimen>
<dimen name="module_push_ui_button_radius">10px</dimen>
<dimen name="module_push_ui_bkg_corner">17px</dimen>
<dimen name="module_push_button_right_marLeft">10px</dimen>
<dimen name="module_push_ui_image_corner">8px</dimen>
<dimen name="module_push_button_maxWidth">242px</dimen>
<dimen name="module_push_ui_height_vertical">270px</dimen>
<dimen name="module_push_ui_width_vertical">374px</dimen>
<dimen name="module_push_ui_app_icon_leftMargin_vertical">24px</dimen>
<dimen name="module_push_ui_app_icon_topMargin_vertical">19px</dimen>
<dimen name="module_push_image_marginTop_vertical">8px</dimen>
<dimen name="module_push_ui_image_width_vertical">328px</dimen>
<dimen name="module_push_ui_image_height_vertical">164px</dimen>
<dimen name="module_push_ui_content_marginTop_vertical">15px</dimen>
<dimen name="module_push_image_qr_size_vertical">150px</dimen>
<dimen name="module_push_window_x">20px</dimen>
<dimen name="module_push_window_y">0px</dimen>
<dimen name="module_push_item_minHeight_vertical">310px</dimen>
<dimen name="module_push_item_maxHeight_vertical">350px</dimen>
<dimen name="module_push_content_paddingBottom_vertical">60px</dimen>
</resources>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<dimen name="module_push_margin_top">30px</dimen>
<dimen name="module_push_size">660px</dimen>
<dimen name="module_push_margin_start">60px</dimen>
<dimen name="module_push_app_icon_size">60px</dimen>
<dimen name="module_push_app_icon_margin_start">31px</dimen>
<dimen name="module_push_title_margin_start">22px</dimen>
<dimen name="module_push_title_margin_top">38px</dimen>
<dimen name="module_push_title_text_size">32px</dimen>
<dimen name="module_push_title_mix_width">394px</dimen>
<dimen name="module_push_timer_margin_end">24px</dimen>
<dimen name="module_push_timer_text_size">28px</dimen>
<dimen name="module_push_timer_margin_top">34px</dimen>
<dimen name="module_push_image_width">600px</dimen>
<dimen name="module_push_image_height">338px</dimen>
<dimen name="module_push_image_margin_top">40px</dimen>
<dimen name="module_push_content_only_width">605px</dimen>
<dimen name="module_push_content_only_height">300px</dimen>
<dimen name="module_push_content_only_line_space">16px</dimen>
<dimen name="module_push_content_only_padding">53px</dimen>
<dimen name="module_push_button_width">0px</dimen>
<dimen name="module_push_button_height">90px</dimen>
<dimen name="module_push_button_margin_top">20px</dimen>
<dimen name="module_push_button_margin_bottom">26px</dimen>
<dimen name="module_push_activity_title_margin_top">66px</dimen>
<dimen name="module_push_activity_title_text_size">38px</dimen>
<dimen name="module_push_activity_close_margin_top">54px</dimen>
<dimen name="module_push_activity_close_margin_end">160px</dimen>
<dimen name="module_push_activity_close_padding">10px</dimen>
<dimen name="module_push_activity_recycler_view_margin_top">173px</dimen>
<dimen name="module_push_activity_not_data_text_size">72px</dimen>
<dimen name="module_push_activity_clear_margin_bottom">68px</dimen>
<dimen name="module_push_message_item_height">200px</dimen>
<dimen name="module_push_message_app_icon_size">120px</dimen>
<dimen name="module_push_message_margin_start">50px</dimen>
<dimen name="module_push_item_title_margin_top">43px</dimen>
<dimen name="module_push_item_title_margin_bottom">6px</dimen>
<dimen name="module_push_item_title_gone_margin_bottom">84px</dimen>
<dimen name="module_push_item_content_margin_end">40px</dimen>
<dimen name="module_push_item_content_margin_bottom">52px</dimen>
<dimen name="module_push_item_content_text_size">30px</dimen>
<dimen name="module_push_message_item_image_size">120px</dimen>
<dimen name="module_push_message_item_image_margin_end">180px</dimen>
<dimen name="module_push_massage_time_text_size">34px</dimen>
<dimen name="module_push_image_margin_bottom">42px</dimen>
<dimen name="module_push_button_radius">51px</dimen>
<dimen name="module_push_timer_inner_radius">27px</dimen>
<dimen name="module_push_timer_thickness">3px</dimen>
<dimen name="module_push_clear_bg_radius">45px</dimen>
<dimen name="module_push_image_radius">20px</dimen>
<dimen name="module_push_item_image_radius">8px</dimen>
<dimen name="module_push_item_content_width">1000px</dimen>
<dimen name="module_push_ui_height">350px</dimen>
<dimen name="module_push_ui_image_width">480px</dimen>
<dimen name="module_push_ui_image_height">320px</dimen>
<dimen name="module_push_ui_image_marLeft">17px</dimen>
<dimen name="module_push_ui_app_icon_leftMargin">21px</dimen>
<dimen name="module_push_ui_app_icon_topMargin">30px</dimen>
<dimen name="module_push_ui_app_icon_goneTopMargin">33px</dimen>
<dimen name="module_push_ui_app_icon_size">50px</dimen>
<dimen name="module_push_ui_title_textSize">30px</dimen>
<dimen name="module_push_ui_decrease_timer_corner">14px</dimen>
<dimen name="module_push_progress_bar_frame_marginTop">30px</dimen>
<dimen name="module_push_progress_bar_frame_marginEnd">27px</dimen>
<dimen name="module_push_ui_timer_textSize">30px</dimen>
<dimen name="module_push_progress_bar_frame_padding">21px</dimen>
<dimen name="module_push_ui_content_marginTop">11px</dimen>
<dimen name="module_push_ui_title_text_size">34px</dimen>
<dimen name="module_push_ui_button_radius">20px</dimen>
<dimen name="module_push_ui_bkg_corner">30px</dimen>
<dimen name="module_push_button_right_marLeft">20px</dimen>
<dimen name="module_push_ui_image_corner">15px</dimen>
<dimen name="module_push_button_maxWidth">242px</dimen>
<dimen name="module_push_ui_height_vertical">486px</dimen>
<dimen name="module_push_ui_width_vertical">700px</dimen>
<dimen name="module_push_ui_app_icon_leftMargin_vertical">30px</dimen>
<dimen name="module_push_ui_app_icon_topMargin_vertical">33px</dimen>
<dimen name="module_push_image_marginTop_vertical">16px</dimen>
<dimen name="module_push_ui_image_width_vertical">640px</dimen>
<dimen name="module_push_ui_image_height_vertical">296px</dimen>
<dimen name="module_push_ui_content_marginTop_vertical">30px</dimen>
<dimen name="module_push_image_qr_size_vertical">250px</dimen>
<dimen name="module_push_window_x">20px</dimen>
<dimen name="module_push_window_y">0px</dimen>
<dimen name="module_push_item_minHeight_vertical">618px</dimen>
<dimen name="module_push_item_maxHeight_vertical">350px</dimen>
<dimen name="module_push_content_paddingBottom_vertical">120px</dimen>
</resources>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RoundedImageView">
<attr name="riv_corner_radius" format="dimension" />
<attr name="riv_corner_radius_top_left" format="dimension" />
<attr name="riv_corner_radius_top_right" format="dimension" />
<attr name="riv_corner_radius_bottom_left" format="dimension" />
<attr name="riv_corner_radius_bottom_right" format="dimension" />
<attr name="riv_border_width" format="dimension" />
<attr name="riv_border_color" format="color" />
<attr name="riv_mutate_background" format="boolean" />
<attr name="riv_oval" format="boolean" />
<attr name="android:scaleType" />
<attr name="riv_tile_Mode">
<enum name="clamp" value="0" />
<enum name="repeat" value="1" />
<enum name="mirror" value="2" />
</attr>
<attr name="riv_tile_Mode_x">
<enum name="clamp" value="0" />
<enum name="repeat" value="1" />
<enum name="mirror" value="2" />
</attr>
<attr name="riv_tile_Mode_y">
<enum name="clamp" value="0" />
<enum name="repeat" value="1" />
<enum name="mirror" value="2" />
</attr>
</declare-styleable>
</resources>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<dimen name="module_push_margin_top">16px</dimen>
<dimen name="module_push_size">352px</dimen>
<dimen name="module_push_margin_start">32px</dimen>
<dimen name="module_push_app_icon_size">32px</dimen>
<dimen name="module_push_app_icon_margin_start">16.5px</dimen>
<dimen name="module_push_title_margin_start">12px</dimen>
<dimen name="module_push_title_margin_top">20px</dimen>
<dimen name="module_push_title_text_size">18px</dimen>
<dimen name="module_push_title_mix_width">210px</dimen>
<dimen name="module_push_timer_margin_end">13px</dimen>
<dimen name="module_push_timer_text_size">15px</dimen>
<dimen name="module_push_timer_margin_top">18px</dimen>
<dimen name="module_push_image_width">320px</dimen>
<dimen name="module_push_image_height">180px</dimen>
<dimen name="module_push_image_margin_top">21px</dimen>
<dimen name="module_push_content_only_width">320px</dimen>
<dimen name="module_push_content_only_height">160px</dimen>
<dimen name="module_push_content_only_line_space">9px</dimen>
<dimen name="module_push_content_only_padding">20px</dimen>
<dimen name="module_push_button_width">0px</dimen>
<dimen name="module_push_button_height">48px</dimen>
<dimen name="module_push_button_margin_top">10px</dimen>
<dimen name="module_push_button_margin_bottom">14px</dimen>
<dimen name="module_push_activity_title_margin_top">35px</dimen>
<dimen name="module_push_activity_title_text_size">20px</dimen>
<dimen name="module_push_activity_close_margin_top">29px</dimen>
<dimen name="module_push_activity_close_margin_end">90px</dimen>
<dimen name="module_push_activity_close_padding">5px</dimen>
<dimen name="module_push_activity_recycler_view_margin_top">92px</dimen>
<dimen name="module_push_activity_not_data_text_size">38px</dimen>
<dimen name="module_push_activity_clear_margin_bottom">36px</dimen>
<dimen name="module_push_message_item_height">106px</dimen>
<dimen name="module_push_message_app_icon_size">64px</dimen>
<dimen name="module_push_message_margin_start">24px</dimen>
<dimen name="module_push_item_title_margin_top">25px</dimen>
<dimen name="module_push_item_title_gone_margin_bottom">44px</dimen>
<dimen name="module_push_item_title_margin_bottom">2px</dimen>
<dimen name="module_push_item_content_margin_end">20px</dimen>
<dimen name="module_push_item_content_margin_bottom">27px</dimen>
<dimen name="module_push_item_content_text_size">16px</dimen>
<dimen name="module_push_message_item_image_size">64px</dimen>
<dimen name="module_push_message_item_image_margin_end">100px</dimen>
<dimen name="module_push_massage_time_text_size">14px</dimen>
<dimen name="module_push_image_margin_bottom">22px</dimen>
<dimen name="module_push_button_radius">27px</dimen>
<dimen name="module_push_timer_inner_radius">14px</dimen>
<dimen name="module_push_timer_thickness">1.5px</dimen>
<dimen name="module_push_clear_bg_radius">24px</dimen>
<dimen name="module_push_image_radius">10px</dimen>
<dimen name="module_push_item_image_radius">4px</dimen>
<dimen name="module_push_item_content_width">560px</dimen>
<dimen name="module_push_ui_height">194px</dimen>
<dimen name="module_push_ui_image_width">266px</dimen>
<dimen name="module_push_ui_image_height">178px</dimen>
<dimen name="module_push_ui_image_marLeft">8px</dimen>
<dimen name="module_push_ui_app_icon_leftMargin">12px</dimen>
<dimen name="module_push_ui_app_icon_topMargin">16px</dimen>
<dimen name="module_push_ui_app_icon_goneTopMargin">19px</dimen>
<dimen name="module_push_ui_app_icon_size">30px</dimen>
<dimen name="module_push_ui_title_textSize">16px</dimen>
<dimen name="module_push_ui_decrease_timer_corner">8px</dimen>
<dimen name="module_push_progress_bar_frame_marginTop">17px</dimen>
<dimen name="module_push_progress_bar_frame_marginEnd">19px</dimen>
<dimen name="module_push_ui_timer_textSize">16px</dimen>
<dimen name="module_push_progress_bar_frame_padding">11px</dimen>
<dimen name="module_push_ui_content_marginTop">6px</dimen>
<dimen name="module_push_ui_title_text_size">18px</dimen>
<dimen name="module_push_ui_button_radius">10px</dimen>
<dimen name="module_push_ui_bkg_corner">17px</dimen>
<dimen name="module_push_button_right_marLeft">10px</dimen>
<dimen name="module_push_ui_image_corner">8px</dimen>
<dimen name="module_push_button_maxWidth">242px</dimen>
<dimen name="module_push_ui_height_vertical">270px</dimen>
<dimen name="module_push_ui_width_vertical">374px</dimen>
<dimen name="module_push_ui_app_icon_leftMargin_vertical">24px</dimen>
<dimen name="module_push_ui_app_icon_topMargin_vertical">19px</dimen>
<dimen name="module_push_image_marginTop_vertical">8px</dimen>
<dimen name="module_push_ui_image_width_vertical">328px</dimen>
<dimen name="module_push_ui_image_height_vertical">164px</dimen>
<dimen name="module_push_ui_content_marginTop_vertical">15px</dimen>
<dimen name="module_push_image_qr_size_vertical">150px</dimen>
<dimen name="module_push_window_x">20px</dimen>
<dimen name="module_push_window_y">0px</dimen>
<dimen name="module_push_item_minHeight_vertical">310px</dimen>
<dimen name="module_push_item_maxHeight_vertical">350px</dimen>
<dimen name="module_push_content_paddingBottom_vertical">60px</dimen>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">mogo-module-push</string>
</resources>

View File

@@ -0,0 +1,9 @@
<resources>
<style name="ModulePushMessageTheme" parent="Theme.AppCompat.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
<item name="android:windowBackground">@drawable/module_push_message_activity_background</item>
</style>
</resources>

View File

@@ -22,13 +22,11 @@ include ':modules:mogo-module-share'
include ':modules:mogo-module-service'
include ':modules:mogo-module-back'
include ':modules:mogo-module-authorize'
//include ':modules:mogo-module-guide'
include ':libraries:map-amap'
include ':libraries:map-custom'
include ':libraries:mogo-map-api'
include ':modules:mogo-module-apps'
include ':modules:mogo-module-extensions'
//include ':foudations:mogo-connection'
include ':modules:mogo-module-gps-simulator'
include ':modules:mogo-module-gps-simulator-debug'
include ':modules:mogo-module-gps-simulator-noop'
@@ -37,3 +35,7 @@ include ':modules:mogo-module-media'
include ':modules:mogo-module-v2x'
include ':main-extensions:mogo-module-main-independent'
include ':main-extensions:mogo-module-main-launcher'
include ':modules:mogo-module-push'
include ':modules:mogo-module-push-base'
include ':modules:mogo-module-push-noop'

View File

@@ -64,4 +64,10 @@ if [ $? -ne 0 ]; then exit; fi
if [ $? -ne 0 ]; then exit; fi
./gradlew :foudations:mogo-base-services-apk:clean :foudations:mogo-base-services-apk:uploadArchives
if [ $? -ne 0 ]; then exit; fi
./gradlew :foudations:mogo-base-services-sdk:clean :foudations:mogo-base-services-sdk:uploadArchives
./gradlew :foudations:mogo-base-services-sdk:clean :foudations:mogo-base-services-sdk:uploadArchives
if [ $? -ne 0 ]; then exit; fi
./gradlew :modules:mogo-module-push-base:clean :modules:mogo-module-push-base:uploadArchives
if [ $? -ne 0 ]; then exit; fi
./gradlew :modules:mogo-module-push-noop:clean :modules:mogo-module-push-noop:uploadArchives
if [ $? -ne 0 ]; then exit; fi
./gradlew :modules:mogo-module-push:clean :modules:mogo-module-push:uploadArchives