diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt new file mode 100644 index 0000000..bb3262d --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt @@ -0,0 +1,40 @@ +package com.casic.br.view + +import com.casic.br.R +import com.casic.br.extensions.initLayoutImmersionBar +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import kotlinx.android.synthetic.main.activity_add_device.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceActivity : KotlinBaseActivity() { + + + override fun initLayoutView(): Int = R.layout.activity_add_device + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + iotDevice.setOnClickListener { + + } + + notIotDevice.setOnClickListener { + navigatePageTo() + } + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt new file mode 100644 index 0000000..bb3262d --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt @@ -0,0 +1,40 @@ +package com.casic.br.view + +import com.casic.br.R +import com.casic.br.extensions.initLayoutImmersionBar +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import kotlinx.android.synthetic.main.activity_add_device.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceActivity : KotlinBaseActivity() { + + + override fun initLayoutView(): Int = R.layout.activity_add_device + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + iotDevice.setOnClickListener { + + } + + notIotDevice.setOnClickListener { + navigatePageTo() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt new file mode 100644 index 0000000..eb552e4 --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt @@ -0,0 +1,89 @@ +package com.casic.br.view + +import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.viewpager.widget.ViewPager +import com.casic.br.R +import com.casic.br.adapter.ViewPagerAdapter +import com.casic.br.extensions.initLayoutImmersionBar +import com.casic.br.fragment.add.DeviceTypeFragment +import com.casic.br.fragment.add.NearbyDeviceFragment +import com.casic.br.fragment.add.ScanDeviceFragment +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import kotlinx.android.synthetic.main.activity_add_device_tab.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceTabActivity : KotlinBaseActivity() { + + private var menuItem: MenuItem? = null + private var fragmentPages: ArrayList = ArrayList() + + init { + fragmentPages.add(ScanDeviceFragment()) + fragmentPages.add(DeviceTypeFragment()) + fragmentPages.add(NearbyDeviceFragment()) + } + + override fun initLayoutView(): Int = R.layout.activity_add_device_tab + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "扫码添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + bottomNavigation.setOnItemSelectedListener { menuItem -> + when (menuItem.itemId) { + R.id.nav_scan -> { + addDeviceViewPager.currentItem = 0 + titleView.text = "扫码添加设备" + } + R.id.nav_type -> { + addDeviceViewPager.currentItem = 1 + titleView.text = "按型号添加设备" + } + R.id.nav_nearby -> { + addDeviceViewPager.currentItem = 2 + titleView.text = "附近设备" + } + } + false + } + addDeviceViewPager.adapter = ViewPagerAdapter(fragmentPages, supportFragmentManager) + addDeviceViewPager.offscreenPageLimit = fragmentPages.size //缓存页数 + addDeviceViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + + } + + override fun onPageSelected(position: Int) { + if (menuItem != null) { + menuItem!!.isChecked = false + } else { + bottomNavigation.menu.getItem(0).isChecked = false + } + menuItem = bottomNavigation.menu.getItem(position) + menuItem!!.isChecked = true + } + }) + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt new file mode 100644 index 0000000..bb3262d --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt @@ -0,0 +1,40 @@ +package com.casic.br.view + +import com.casic.br.R +import com.casic.br.extensions.initLayoutImmersionBar +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import kotlinx.android.synthetic.main.activity_add_device.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceActivity : KotlinBaseActivity() { + + + override fun initLayoutView(): Int = R.layout.activity_add_device + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + iotDevice.setOnClickListener { + + } + + notIotDevice.setOnClickListener { + navigatePageTo() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt new file mode 100644 index 0000000..eb552e4 --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt @@ -0,0 +1,89 @@ +package com.casic.br.view + +import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.viewpager.widget.ViewPager +import com.casic.br.R +import com.casic.br.adapter.ViewPagerAdapter +import com.casic.br.extensions.initLayoutImmersionBar +import com.casic.br.fragment.add.DeviceTypeFragment +import com.casic.br.fragment.add.NearbyDeviceFragment +import com.casic.br.fragment.add.ScanDeviceFragment +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import kotlinx.android.synthetic.main.activity_add_device_tab.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceTabActivity : KotlinBaseActivity() { + + private var menuItem: MenuItem? = null + private var fragmentPages: ArrayList = ArrayList() + + init { + fragmentPages.add(ScanDeviceFragment()) + fragmentPages.add(DeviceTypeFragment()) + fragmentPages.add(NearbyDeviceFragment()) + } + + override fun initLayoutView(): Int = R.layout.activity_add_device_tab + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "扫码添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + bottomNavigation.setOnItemSelectedListener { menuItem -> + when (menuItem.itemId) { + R.id.nav_scan -> { + addDeviceViewPager.currentItem = 0 + titleView.text = "扫码添加设备" + } + R.id.nav_type -> { + addDeviceViewPager.currentItem = 1 + titleView.text = "按型号添加设备" + } + R.id.nav_nearby -> { + addDeviceViewPager.currentItem = 2 + titleView.text = "附近设备" + } + } + false + } + addDeviceViewPager.adapter = ViewPagerAdapter(fragmentPages, supportFragmentManager) + addDeviceViewPager.offscreenPageLimit = fragmentPages.size //缓存页数 + addDeviceViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + + } + + override fun onPageSelected(position: Int) { + if (menuItem != null) { + menuItem!!.isChecked = false + } else { + bottomNavigation.menu.getItem(0).isChecked = false + } + menuItem = bottomNavigation.menu.getItem(position) + menuItem!!.isChecked = true + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt new file mode 100644 index 0000000..56f44dc --- /dev/null +++ b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt @@ -0,0 +1,258 @@ +package com.casic.br.widgets + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.text.TextPaint +import android.util.AttributeSet +import android.util.Log +import android.view.View +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dp2px +import com.pengxh.kt.lite.extensions.sp2px +import java.util.* + +/** + * @description: 呼叫设备水波纹扩散动画控件 + * @author: Pengxh + * @email: 290677893@qq.com + * @date: 2020年9月17日14:24:45 + */ +class WaterRippleView constructor(context: Context, attrs: AttributeSet) : View(context, attrs) { + private val kTag = "WaterRippleView" + + //中心圆半径 + private val radius: Int + + //每次圆递增间距,值越大,扩散速度越快 + private val distance: Int + + //最大扩散距离,值越小,扩散效果越明显 + private val maxDistance: Int + private val textSize: Int + private val textColor: Int + private val spreadColor: Int + private val centerColor: Int + + //图片资源 + private val imageResourceId: Int + private val spreadRadius: MutableList = ArrayList() //扩散圆层级数,元素为扩散的距离 + private val alphas: MutableList = ArrayList() //对应每层圆的透明度 + + //圆心x + private var centerX = 0f + + //圆心y + private var centerY = 0f + + //扩散延迟间隔,越大扩散越慢 + private var animDuration = 50 + private var isStart = false + private var text = "呼叫设备" + private val textPaint by lazy { TextPaint() } + private val imagePaint by lazy { Paint() } + + //中心圆paint + private val centerPaint by lazy { Paint() } + + //扩散圆paint + private val spreadPaint by lazy { Paint() } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.WaterRippleView) + centerColor = a.getColor( + R.styleable.WaterRippleView_ripple_centerColor, + R.color.mainThemeColor.convertColor(context) + ) + spreadColor = a.getColor( + R.styleable.WaterRippleView_ripple_spreadColor, + R.color.mainThemeColor.convertColor(context) + ) + animDuration = a.getInteger(R.styleable.WaterRippleView_ripple_animDuration, animDuration) + radius = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_radius, 50f.dp2px(context) + ) + distance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_distance, 3f.dp2px(context) + ) + maxDistance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_maxDistance, 30f.dp2px(context) + ) + textSize = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_textSize, 16f.sp2px(context) + ) + text = a.getString(R.styleable.WaterRippleView_ripple_text).toString() + textColor = a.getColor( + R.styleable.WaterRippleView_ripple_textColor, R.color.white.convertColor(context) + ) + imageResourceId = a.getResourceId(R.styleable.WaterRippleView_ripple_image, R.mipmap.hujiao) + a.recycle() + + //最开始不透明且扩散距离为0 + alphas.add(255) + spreadRadius.add(0) + //初始化画笔 + initPaint() + if (isStart) { + Log.d(kTag, "onClick: 重复启动动画") + } else { + start() + } + } + + private fun initPaint() { + //中心圆画笔 + centerPaint.color = centerColor + centerPaint.isAntiAlias = true + + //扩散圆圈画笔 + spreadPaint.isAntiAlias = true + spreadPaint.alpha = 255 + spreadPaint.color = spreadColor + + //中心圆文字画笔 + textPaint.color = textColor + textPaint.isAntiAlias = true + textPaint.textSize = textSize.toFloat() + + //中心圆上面图片画笔 + imagePaint.isAntiAlias = true + imagePaint.alpha = 255 + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + //圆心位置 + centerX = (w shr 1).toFloat() + centerY = (h shr 1).toFloat() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec) + val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec) + val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec) + // 获取宽 + val mWidth: Int = if (widthSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + widthSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 获取高 + val mHeight: Int = if (heightSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + heightSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 设置该view的宽高 + setMeasuredDimension(mWidth, mHeight) + } + + @SuppressLint("DrawAllocation") + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (isStart) { + for (i in spreadRadius.indices) { + var alpha = alphas[i] + spreadPaint.alpha = alpha + val width = spreadRadius[i] + //绘制扩散的圆 + canvas.drawCircle(centerX, centerY, (radius + width).toFloat(), spreadPaint) + //每次扩散圆半径递增,圆透明度递减 + if (alpha > 0 && width < (maxDistance + radius).toFloat().dp2px(context)) { + alpha = (alpha - distance).coerceAtLeast(0) + alphas[i] = alpha + spreadRadius[i] = width + distance + } + } + //当最外层扩散圆半径达到最大半径时添加新扩散圆 + val maxRadius = spreadRadius[spreadRadius.size - 1] + if (maxRadius > maxDistance) { + spreadRadius.add(0) + alphas.add(255) + } + //超过5个扩散圆,删除最先绘制的圆,即最外层的圆 + if (spreadRadius.size >= 8) { + alphas.removeAt(0) + spreadRadius.removeAt(0) + } + postInvalidateDelayed(animDuration.toLong()) + } + //中间的圆 + canvas.drawCircle(centerX, centerY, radius.toFloat(), centerPaint) + + //绘制文字 + val textRect = Rect() + textPaint.getTextBounds(text, 0, text.length, textRect) + val textWidth = textRect.width() + val textHeight = textRect.height() + //计算文字左下角坐标 + val textX = centerX - (textWidth shr 1) + val textY = centerY + 4 * textHeight / 3 //让文字靠下点,所以Y坐标大一点 + canvas.drawText(text, textX, textY, textPaint) + + //绘制图片 + val bitmap = BitmapFactory.decodeResource(resources, imageResourceId) //实例化 + + /** + * 方法一:此方法局限性大,对于图片要求较高,必须是尺寸合适的图片,否则会显示异常 + */ + val imageWidth = bitmap.width + val imageHeight = bitmap.height + //计算图片左上角坐标 + val imageX = centerX - (imageWidth shr 1) + val imageY = centerY - 4 * imageHeight / 3 //让图片靠上点,所以Y坐标小一点 + canvas.drawBitmap(bitmap, imageX, imageY, imagePaint) + /** + * 方法二:此方法是为将要绘制的图片划定显示区域,可以缓解图片尺寸导致显示异常问题 + */ +// RectF rectF = new RectF(centerX - (textWidth >> 2), centerY - (textHeight * 2), +// centerX + (textWidth >> 2), centerY - (textHeight >> 1)); +// Log.d(kTag, "height: " + rectF.height() + " ==== width: " + rectF.width()); +// canvas.drawBitmap(bitmap, null, rectF, imagePaint); + /** + * 方法三名:采用矩阵方式实现,这可彻底解决图片尺寸导致显示异常的问题 + * + * Matrix matrix=new Matrix(); + */ + } + + /** + * 启动动画 + */ + fun start() { + Log.d(kTag, "start: 启动动画") + text = "正在搜索" + isStart = true + postInvalidate() + } + + /** + * 停止动画 + */ + fun stop() { + Log.d(kTag, "stop: 停止动画") + text = "停止搜索" + isStart = false + postInvalidate() + } + + private var startListener: OnAnimationStartListener? = null + + interface OnAnimationStartListener { + fun onStart(view: WaterRippleView?) + } + + fun setOnAnimationStartListener(listener: OnAnimationStartListener?) { + startListener = listener + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt new file mode 100644 index 0000000..bb3262d --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt @@ -0,0 +1,40 @@ +package com.casic.br.view + +import com.casic.br.R +import com.casic.br.extensions.initLayoutImmersionBar +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import kotlinx.android.synthetic.main.activity_add_device.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceActivity : KotlinBaseActivity() { + + + override fun initLayoutView(): Int = R.layout.activity_add_device + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + iotDevice.setOnClickListener { + + } + + notIotDevice.setOnClickListener { + navigatePageTo() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt new file mode 100644 index 0000000..eb552e4 --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt @@ -0,0 +1,89 @@ +package com.casic.br.view + +import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.viewpager.widget.ViewPager +import com.casic.br.R +import com.casic.br.adapter.ViewPagerAdapter +import com.casic.br.extensions.initLayoutImmersionBar +import com.casic.br.fragment.add.DeviceTypeFragment +import com.casic.br.fragment.add.NearbyDeviceFragment +import com.casic.br.fragment.add.ScanDeviceFragment +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import kotlinx.android.synthetic.main.activity_add_device_tab.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceTabActivity : KotlinBaseActivity() { + + private var menuItem: MenuItem? = null + private var fragmentPages: ArrayList = ArrayList() + + init { + fragmentPages.add(ScanDeviceFragment()) + fragmentPages.add(DeviceTypeFragment()) + fragmentPages.add(NearbyDeviceFragment()) + } + + override fun initLayoutView(): Int = R.layout.activity_add_device_tab + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "扫码添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + bottomNavigation.setOnItemSelectedListener { menuItem -> + when (menuItem.itemId) { + R.id.nav_scan -> { + addDeviceViewPager.currentItem = 0 + titleView.text = "扫码添加设备" + } + R.id.nav_type -> { + addDeviceViewPager.currentItem = 1 + titleView.text = "按型号添加设备" + } + R.id.nav_nearby -> { + addDeviceViewPager.currentItem = 2 + titleView.text = "附近设备" + } + } + false + } + addDeviceViewPager.adapter = ViewPagerAdapter(fragmentPages, supportFragmentManager) + addDeviceViewPager.offscreenPageLimit = fragmentPages.size //缓存页数 + addDeviceViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + + } + + override fun onPageSelected(position: Int) { + if (menuItem != null) { + menuItem!!.isChecked = false + } else { + bottomNavigation.menu.getItem(0).isChecked = false + } + menuItem = bottomNavigation.menu.getItem(position) + menuItem!!.isChecked = true + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt new file mode 100644 index 0000000..56f44dc --- /dev/null +++ b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt @@ -0,0 +1,258 @@ +package com.casic.br.widgets + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.text.TextPaint +import android.util.AttributeSet +import android.util.Log +import android.view.View +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dp2px +import com.pengxh.kt.lite.extensions.sp2px +import java.util.* + +/** + * @description: 呼叫设备水波纹扩散动画控件 + * @author: Pengxh + * @email: 290677893@qq.com + * @date: 2020年9月17日14:24:45 + */ +class WaterRippleView constructor(context: Context, attrs: AttributeSet) : View(context, attrs) { + private val kTag = "WaterRippleView" + + //中心圆半径 + private val radius: Int + + //每次圆递增间距,值越大,扩散速度越快 + private val distance: Int + + //最大扩散距离,值越小,扩散效果越明显 + private val maxDistance: Int + private val textSize: Int + private val textColor: Int + private val spreadColor: Int + private val centerColor: Int + + //图片资源 + private val imageResourceId: Int + private val spreadRadius: MutableList = ArrayList() //扩散圆层级数,元素为扩散的距离 + private val alphas: MutableList = ArrayList() //对应每层圆的透明度 + + //圆心x + private var centerX = 0f + + //圆心y + private var centerY = 0f + + //扩散延迟间隔,越大扩散越慢 + private var animDuration = 50 + private var isStart = false + private var text = "呼叫设备" + private val textPaint by lazy { TextPaint() } + private val imagePaint by lazy { Paint() } + + //中心圆paint + private val centerPaint by lazy { Paint() } + + //扩散圆paint + private val spreadPaint by lazy { Paint() } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.WaterRippleView) + centerColor = a.getColor( + R.styleable.WaterRippleView_ripple_centerColor, + R.color.mainThemeColor.convertColor(context) + ) + spreadColor = a.getColor( + R.styleable.WaterRippleView_ripple_spreadColor, + R.color.mainThemeColor.convertColor(context) + ) + animDuration = a.getInteger(R.styleable.WaterRippleView_ripple_animDuration, animDuration) + radius = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_radius, 50f.dp2px(context) + ) + distance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_distance, 3f.dp2px(context) + ) + maxDistance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_maxDistance, 30f.dp2px(context) + ) + textSize = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_textSize, 16f.sp2px(context) + ) + text = a.getString(R.styleable.WaterRippleView_ripple_text).toString() + textColor = a.getColor( + R.styleable.WaterRippleView_ripple_textColor, R.color.white.convertColor(context) + ) + imageResourceId = a.getResourceId(R.styleable.WaterRippleView_ripple_image, R.mipmap.hujiao) + a.recycle() + + //最开始不透明且扩散距离为0 + alphas.add(255) + spreadRadius.add(0) + //初始化画笔 + initPaint() + if (isStart) { + Log.d(kTag, "onClick: 重复启动动画") + } else { + start() + } + } + + private fun initPaint() { + //中心圆画笔 + centerPaint.color = centerColor + centerPaint.isAntiAlias = true + + //扩散圆圈画笔 + spreadPaint.isAntiAlias = true + spreadPaint.alpha = 255 + spreadPaint.color = spreadColor + + //中心圆文字画笔 + textPaint.color = textColor + textPaint.isAntiAlias = true + textPaint.textSize = textSize.toFloat() + + //中心圆上面图片画笔 + imagePaint.isAntiAlias = true + imagePaint.alpha = 255 + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + //圆心位置 + centerX = (w shr 1).toFloat() + centerY = (h shr 1).toFloat() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec) + val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec) + val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec) + // 获取宽 + val mWidth: Int = if (widthSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + widthSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 获取高 + val mHeight: Int = if (heightSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + heightSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 设置该view的宽高 + setMeasuredDimension(mWidth, mHeight) + } + + @SuppressLint("DrawAllocation") + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (isStart) { + for (i in spreadRadius.indices) { + var alpha = alphas[i] + spreadPaint.alpha = alpha + val width = spreadRadius[i] + //绘制扩散的圆 + canvas.drawCircle(centerX, centerY, (radius + width).toFloat(), spreadPaint) + //每次扩散圆半径递增,圆透明度递减 + if (alpha > 0 && width < (maxDistance + radius).toFloat().dp2px(context)) { + alpha = (alpha - distance).coerceAtLeast(0) + alphas[i] = alpha + spreadRadius[i] = width + distance + } + } + //当最外层扩散圆半径达到最大半径时添加新扩散圆 + val maxRadius = spreadRadius[spreadRadius.size - 1] + if (maxRadius > maxDistance) { + spreadRadius.add(0) + alphas.add(255) + } + //超过5个扩散圆,删除最先绘制的圆,即最外层的圆 + if (spreadRadius.size >= 8) { + alphas.removeAt(0) + spreadRadius.removeAt(0) + } + postInvalidateDelayed(animDuration.toLong()) + } + //中间的圆 + canvas.drawCircle(centerX, centerY, radius.toFloat(), centerPaint) + + //绘制文字 + val textRect = Rect() + textPaint.getTextBounds(text, 0, text.length, textRect) + val textWidth = textRect.width() + val textHeight = textRect.height() + //计算文字左下角坐标 + val textX = centerX - (textWidth shr 1) + val textY = centerY + 4 * textHeight / 3 //让文字靠下点,所以Y坐标大一点 + canvas.drawText(text, textX, textY, textPaint) + + //绘制图片 + val bitmap = BitmapFactory.decodeResource(resources, imageResourceId) //实例化 + + /** + * 方法一:此方法局限性大,对于图片要求较高,必须是尺寸合适的图片,否则会显示异常 + */ + val imageWidth = bitmap.width + val imageHeight = bitmap.height + //计算图片左上角坐标 + val imageX = centerX - (imageWidth shr 1) + val imageY = centerY - 4 * imageHeight / 3 //让图片靠上点,所以Y坐标小一点 + canvas.drawBitmap(bitmap, imageX, imageY, imagePaint) + /** + * 方法二:此方法是为将要绘制的图片划定显示区域,可以缓解图片尺寸导致显示异常问题 + */ +// RectF rectF = new RectF(centerX - (textWidth >> 2), centerY - (textHeight * 2), +// centerX + (textWidth >> 2), centerY - (textHeight >> 1)); +// Log.d(kTag, "height: " + rectF.height() + " ==== width: " + rectF.width()); +// canvas.drawBitmap(bitmap, null, rectF, imagePaint); + /** + * 方法三名:采用矩阵方式实现,这可彻底解决图片尺寸导致显示异常的问题 + * + * Matrix matrix=new Matrix(); + */ + } + + /** + * 启动动画 + */ + fun start() { + Log.d(kTag, "start: 启动动画") + text = "正在搜索" + isStart = true + postInvalidate() + } + + /** + * 停止动画 + */ + fun stop() { + Log.d(kTag, "stop: 停止动画") + text = "停止搜索" + isStart = false + postInvalidate() + } + + private var startListener: OnAnimationStartListener? = null + + interface OnAnimationStartListener { + fun onStart(view: WaterRippleView?) + } + + fun setOnAnimationStartListener(listener: OnAnimationStartListener?) { + startListener = listener + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml new file mode 100644 index 0000000..53f6af9 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt new file mode 100644 index 0000000..bb3262d --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt @@ -0,0 +1,40 @@ +package com.casic.br.view + +import com.casic.br.R +import com.casic.br.extensions.initLayoutImmersionBar +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import kotlinx.android.synthetic.main.activity_add_device.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceActivity : KotlinBaseActivity() { + + + override fun initLayoutView(): Int = R.layout.activity_add_device + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + iotDevice.setOnClickListener { + + } + + notIotDevice.setOnClickListener { + navigatePageTo() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt new file mode 100644 index 0000000..eb552e4 --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt @@ -0,0 +1,89 @@ +package com.casic.br.view + +import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.viewpager.widget.ViewPager +import com.casic.br.R +import com.casic.br.adapter.ViewPagerAdapter +import com.casic.br.extensions.initLayoutImmersionBar +import com.casic.br.fragment.add.DeviceTypeFragment +import com.casic.br.fragment.add.NearbyDeviceFragment +import com.casic.br.fragment.add.ScanDeviceFragment +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import kotlinx.android.synthetic.main.activity_add_device_tab.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceTabActivity : KotlinBaseActivity() { + + private var menuItem: MenuItem? = null + private var fragmentPages: ArrayList = ArrayList() + + init { + fragmentPages.add(ScanDeviceFragment()) + fragmentPages.add(DeviceTypeFragment()) + fragmentPages.add(NearbyDeviceFragment()) + } + + override fun initLayoutView(): Int = R.layout.activity_add_device_tab + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "扫码添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + bottomNavigation.setOnItemSelectedListener { menuItem -> + when (menuItem.itemId) { + R.id.nav_scan -> { + addDeviceViewPager.currentItem = 0 + titleView.text = "扫码添加设备" + } + R.id.nav_type -> { + addDeviceViewPager.currentItem = 1 + titleView.text = "按型号添加设备" + } + R.id.nav_nearby -> { + addDeviceViewPager.currentItem = 2 + titleView.text = "附近设备" + } + } + false + } + addDeviceViewPager.adapter = ViewPagerAdapter(fragmentPages, supportFragmentManager) + addDeviceViewPager.offscreenPageLimit = fragmentPages.size //缓存页数 + addDeviceViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + + } + + override fun onPageSelected(position: Int) { + if (menuItem != null) { + menuItem!!.isChecked = false + } else { + bottomNavigation.menu.getItem(0).isChecked = false + } + menuItem = bottomNavigation.menu.getItem(position) + menuItem!!.isChecked = true + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt new file mode 100644 index 0000000..56f44dc --- /dev/null +++ b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt @@ -0,0 +1,258 @@ +package com.casic.br.widgets + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.text.TextPaint +import android.util.AttributeSet +import android.util.Log +import android.view.View +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dp2px +import com.pengxh.kt.lite.extensions.sp2px +import java.util.* + +/** + * @description: 呼叫设备水波纹扩散动画控件 + * @author: Pengxh + * @email: 290677893@qq.com + * @date: 2020年9月17日14:24:45 + */ +class WaterRippleView constructor(context: Context, attrs: AttributeSet) : View(context, attrs) { + private val kTag = "WaterRippleView" + + //中心圆半径 + private val radius: Int + + //每次圆递增间距,值越大,扩散速度越快 + private val distance: Int + + //最大扩散距离,值越小,扩散效果越明显 + private val maxDistance: Int + private val textSize: Int + private val textColor: Int + private val spreadColor: Int + private val centerColor: Int + + //图片资源 + private val imageResourceId: Int + private val spreadRadius: MutableList = ArrayList() //扩散圆层级数,元素为扩散的距离 + private val alphas: MutableList = ArrayList() //对应每层圆的透明度 + + //圆心x + private var centerX = 0f + + //圆心y + private var centerY = 0f + + //扩散延迟间隔,越大扩散越慢 + private var animDuration = 50 + private var isStart = false + private var text = "呼叫设备" + private val textPaint by lazy { TextPaint() } + private val imagePaint by lazy { Paint() } + + //中心圆paint + private val centerPaint by lazy { Paint() } + + //扩散圆paint + private val spreadPaint by lazy { Paint() } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.WaterRippleView) + centerColor = a.getColor( + R.styleable.WaterRippleView_ripple_centerColor, + R.color.mainThemeColor.convertColor(context) + ) + spreadColor = a.getColor( + R.styleable.WaterRippleView_ripple_spreadColor, + R.color.mainThemeColor.convertColor(context) + ) + animDuration = a.getInteger(R.styleable.WaterRippleView_ripple_animDuration, animDuration) + radius = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_radius, 50f.dp2px(context) + ) + distance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_distance, 3f.dp2px(context) + ) + maxDistance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_maxDistance, 30f.dp2px(context) + ) + textSize = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_textSize, 16f.sp2px(context) + ) + text = a.getString(R.styleable.WaterRippleView_ripple_text).toString() + textColor = a.getColor( + R.styleable.WaterRippleView_ripple_textColor, R.color.white.convertColor(context) + ) + imageResourceId = a.getResourceId(R.styleable.WaterRippleView_ripple_image, R.mipmap.hujiao) + a.recycle() + + //最开始不透明且扩散距离为0 + alphas.add(255) + spreadRadius.add(0) + //初始化画笔 + initPaint() + if (isStart) { + Log.d(kTag, "onClick: 重复启动动画") + } else { + start() + } + } + + private fun initPaint() { + //中心圆画笔 + centerPaint.color = centerColor + centerPaint.isAntiAlias = true + + //扩散圆圈画笔 + spreadPaint.isAntiAlias = true + spreadPaint.alpha = 255 + spreadPaint.color = spreadColor + + //中心圆文字画笔 + textPaint.color = textColor + textPaint.isAntiAlias = true + textPaint.textSize = textSize.toFloat() + + //中心圆上面图片画笔 + imagePaint.isAntiAlias = true + imagePaint.alpha = 255 + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + //圆心位置 + centerX = (w shr 1).toFloat() + centerY = (h shr 1).toFloat() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec) + val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec) + val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec) + // 获取宽 + val mWidth: Int = if (widthSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + widthSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 获取高 + val mHeight: Int = if (heightSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + heightSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 设置该view的宽高 + setMeasuredDimension(mWidth, mHeight) + } + + @SuppressLint("DrawAllocation") + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (isStart) { + for (i in spreadRadius.indices) { + var alpha = alphas[i] + spreadPaint.alpha = alpha + val width = spreadRadius[i] + //绘制扩散的圆 + canvas.drawCircle(centerX, centerY, (radius + width).toFloat(), spreadPaint) + //每次扩散圆半径递增,圆透明度递减 + if (alpha > 0 && width < (maxDistance + radius).toFloat().dp2px(context)) { + alpha = (alpha - distance).coerceAtLeast(0) + alphas[i] = alpha + spreadRadius[i] = width + distance + } + } + //当最外层扩散圆半径达到最大半径时添加新扩散圆 + val maxRadius = spreadRadius[spreadRadius.size - 1] + if (maxRadius > maxDistance) { + spreadRadius.add(0) + alphas.add(255) + } + //超过5个扩散圆,删除最先绘制的圆,即最外层的圆 + if (spreadRadius.size >= 8) { + alphas.removeAt(0) + spreadRadius.removeAt(0) + } + postInvalidateDelayed(animDuration.toLong()) + } + //中间的圆 + canvas.drawCircle(centerX, centerY, radius.toFloat(), centerPaint) + + //绘制文字 + val textRect = Rect() + textPaint.getTextBounds(text, 0, text.length, textRect) + val textWidth = textRect.width() + val textHeight = textRect.height() + //计算文字左下角坐标 + val textX = centerX - (textWidth shr 1) + val textY = centerY + 4 * textHeight / 3 //让文字靠下点,所以Y坐标大一点 + canvas.drawText(text, textX, textY, textPaint) + + //绘制图片 + val bitmap = BitmapFactory.decodeResource(resources, imageResourceId) //实例化 + + /** + * 方法一:此方法局限性大,对于图片要求较高,必须是尺寸合适的图片,否则会显示异常 + */ + val imageWidth = bitmap.width + val imageHeight = bitmap.height + //计算图片左上角坐标 + val imageX = centerX - (imageWidth shr 1) + val imageY = centerY - 4 * imageHeight / 3 //让图片靠上点,所以Y坐标小一点 + canvas.drawBitmap(bitmap, imageX, imageY, imagePaint) + /** + * 方法二:此方法是为将要绘制的图片划定显示区域,可以缓解图片尺寸导致显示异常问题 + */ +// RectF rectF = new RectF(centerX - (textWidth >> 2), centerY - (textHeight * 2), +// centerX + (textWidth >> 2), centerY - (textHeight >> 1)); +// Log.d(kTag, "height: " + rectF.height() + " ==== width: " + rectF.width()); +// canvas.drawBitmap(bitmap, null, rectF, imagePaint); + /** + * 方法三名:采用矩阵方式实现,这可彻底解决图片尺寸导致显示异常的问题 + * + * Matrix matrix=new Matrix(); + */ + } + + /** + * 启动动画 + */ + fun start() { + Log.d(kTag, "start: 启动动画") + text = "正在搜索" + isStart = true + postInvalidate() + } + + /** + * 停止动画 + */ + fun stop() { + Log.d(kTag, "stop: 停止动画") + text = "停止搜索" + isStart = false + postInvalidate() + } + + private var startListener: OnAnimationStartListener? = null + + interface OnAnimationStartListener { + fun onStart(view: WaterRippleView?) + } + + fun setOnAnimationStartListener(listener: OnAnimationStartListener?) { + startListener = listener + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml new file mode 100644 index 0000000..53f6af9 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml new file mode 100644 index 0000000..8b7466a --- /dev/null +++ b/app/src/main/res/layout/activity_add_device.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt new file mode 100644 index 0000000..bb3262d --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt @@ -0,0 +1,40 @@ +package com.casic.br.view + +import com.casic.br.R +import com.casic.br.extensions.initLayoutImmersionBar +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import kotlinx.android.synthetic.main.activity_add_device.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceActivity : KotlinBaseActivity() { + + + override fun initLayoutView(): Int = R.layout.activity_add_device + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + iotDevice.setOnClickListener { + + } + + notIotDevice.setOnClickListener { + navigatePageTo() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt new file mode 100644 index 0000000..eb552e4 --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt @@ -0,0 +1,89 @@ +package com.casic.br.view + +import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.viewpager.widget.ViewPager +import com.casic.br.R +import com.casic.br.adapter.ViewPagerAdapter +import com.casic.br.extensions.initLayoutImmersionBar +import com.casic.br.fragment.add.DeviceTypeFragment +import com.casic.br.fragment.add.NearbyDeviceFragment +import com.casic.br.fragment.add.ScanDeviceFragment +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import kotlinx.android.synthetic.main.activity_add_device_tab.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceTabActivity : KotlinBaseActivity() { + + private var menuItem: MenuItem? = null + private var fragmentPages: ArrayList = ArrayList() + + init { + fragmentPages.add(ScanDeviceFragment()) + fragmentPages.add(DeviceTypeFragment()) + fragmentPages.add(NearbyDeviceFragment()) + } + + override fun initLayoutView(): Int = R.layout.activity_add_device_tab + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "扫码添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + bottomNavigation.setOnItemSelectedListener { menuItem -> + when (menuItem.itemId) { + R.id.nav_scan -> { + addDeviceViewPager.currentItem = 0 + titleView.text = "扫码添加设备" + } + R.id.nav_type -> { + addDeviceViewPager.currentItem = 1 + titleView.text = "按型号添加设备" + } + R.id.nav_nearby -> { + addDeviceViewPager.currentItem = 2 + titleView.text = "附近设备" + } + } + false + } + addDeviceViewPager.adapter = ViewPagerAdapter(fragmentPages, supportFragmentManager) + addDeviceViewPager.offscreenPageLimit = fragmentPages.size //缓存页数 + addDeviceViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + + } + + override fun onPageSelected(position: Int) { + if (menuItem != null) { + menuItem!!.isChecked = false + } else { + bottomNavigation.menu.getItem(0).isChecked = false + } + menuItem = bottomNavigation.menu.getItem(position) + menuItem!!.isChecked = true + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt new file mode 100644 index 0000000..56f44dc --- /dev/null +++ b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt @@ -0,0 +1,258 @@ +package com.casic.br.widgets + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.text.TextPaint +import android.util.AttributeSet +import android.util.Log +import android.view.View +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dp2px +import com.pengxh.kt.lite.extensions.sp2px +import java.util.* + +/** + * @description: 呼叫设备水波纹扩散动画控件 + * @author: Pengxh + * @email: 290677893@qq.com + * @date: 2020年9月17日14:24:45 + */ +class WaterRippleView constructor(context: Context, attrs: AttributeSet) : View(context, attrs) { + private val kTag = "WaterRippleView" + + //中心圆半径 + private val radius: Int + + //每次圆递增间距,值越大,扩散速度越快 + private val distance: Int + + //最大扩散距离,值越小,扩散效果越明显 + private val maxDistance: Int + private val textSize: Int + private val textColor: Int + private val spreadColor: Int + private val centerColor: Int + + //图片资源 + private val imageResourceId: Int + private val spreadRadius: MutableList = ArrayList() //扩散圆层级数,元素为扩散的距离 + private val alphas: MutableList = ArrayList() //对应每层圆的透明度 + + //圆心x + private var centerX = 0f + + //圆心y + private var centerY = 0f + + //扩散延迟间隔,越大扩散越慢 + private var animDuration = 50 + private var isStart = false + private var text = "呼叫设备" + private val textPaint by lazy { TextPaint() } + private val imagePaint by lazy { Paint() } + + //中心圆paint + private val centerPaint by lazy { Paint() } + + //扩散圆paint + private val spreadPaint by lazy { Paint() } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.WaterRippleView) + centerColor = a.getColor( + R.styleable.WaterRippleView_ripple_centerColor, + R.color.mainThemeColor.convertColor(context) + ) + spreadColor = a.getColor( + R.styleable.WaterRippleView_ripple_spreadColor, + R.color.mainThemeColor.convertColor(context) + ) + animDuration = a.getInteger(R.styleable.WaterRippleView_ripple_animDuration, animDuration) + radius = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_radius, 50f.dp2px(context) + ) + distance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_distance, 3f.dp2px(context) + ) + maxDistance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_maxDistance, 30f.dp2px(context) + ) + textSize = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_textSize, 16f.sp2px(context) + ) + text = a.getString(R.styleable.WaterRippleView_ripple_text).toString() + textColor = a.getColor( + R.styleable.WaterRippleView_ripple_textColor, R.color.white.convertColor(context) + ) + imageResourceId = a.getResourceId(R.styleable.WaterRippleView_ripple_image, R.mipmap.hujiao) + a.recycle() + + //最开始不透明且扩散距离为0 + alphas.add(255) + spreadRadius.add(0) + //初始化画笔 + initPaint() + if (isStart) { + Log.d(kTag, "onClick: 重复启动动画") + } else { + start() + } + } + + private fun initPaint() { + //中心圆画笔 + centerPaint.color = centerColor + centerPaint.isAntiAlias = true + + //扩散圆圈画笔 + spreadPaint.isAntiAlias = true + spreadPaint.alpha = 255 + spreadPaint.color = spreadColor + + //中心圆文字画笔 + textPaint.color = textColor + textPaint.isAntiAlias = true + textPaint.textSize = textSize.toFloat() + + //中心圆上面图片画笔 + imagePaint.isAntiAlias = true + imagePaint.alpha = 255 + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + //圆心位置 + centerX = (w shr 1).toFloat() + centerY = (h shr 1).toFloat() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec) + val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec) + val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec) + // 获取宽 + val mWidth: Int = if (widthSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + widthSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 获取高 + val mHeight: Int = if (heightSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + heightSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 设置该view的宽高 + setMeasuredDimension(mWidth, mHeight) + } + + @SuppressLint("DrawAllocation") + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (isStart) { + for (i in spreadRadius.indices) { + var alpha = alphas[i] + spreadPaint.alpha = alpha + val width = spreadRadius[i] + //绘制扩散的圆 + canvas.drawCircle(centerX, centerY, (radius + width).toFloat(), spreadPaint) + //每次扩散圆半径递增,圆透明度递减 + if (alpha > 0 && width < (maxDistance + radius).toFloat().dp2px(context)) { + alpha = (alpha - distance).coerceAtLeast(0) + alphas[i] = alpha + spreadRadius[i] = width + distance + } + } + //当最外层扩散圆半径达到最大半径时添加新扩散圆 + val maxRadius = spreadRadius[spreadRadius.size - 1] + if (maxRadius > maxDistance) { + spreadRadius.add(0) + alphas.add(255) + } + //超过5个扩散圆,删除最先绘制的圆,即最外层的圆 + if (spreadRadius.size >= 8) { + alphas.removeAt(0) + spreadRadius.removeAt(0) + } + postInvalidateDelayed(animDuration.toLong()) + } + //中间的圆 + canvas.drawCircle(centerX, centerY, radius.toFloat(), centerPaint) + + //绘制文字 + val textRect = Rect() + textPaint.getTextBounds(text, 0, text.length, textRect) + val textWidth = textRect.width() + val textHeight = textRect.height() + //计算文字左下角坐标 + val textX = centerX - (textWidth shr 1) + val textY = centerY + 4 * textHeight / 3 //让文字靠下点,所以Y坐标大一点 + canvas.drawText(text, textX, textY, textPaint) + + //绘制图片 + val bitmap = BitmapFactory.decodeResource(resources, imageResourceId) //实例化 + + /** + * 方法一:此方法局限性大,对于图片要求较高,必须是尺寸合适的图片,否则会显示异常 + */ + val imageWidth = bitmap.width + val imageHeight = bitmap.height + //计算图片左上角坐标 + val imageX = centerX - (imageWidth shr 1) + val imageY = centerY - 4 * imageHeight / 3 //让图片靠上点,所以Y坐标小一点 + canvas.drawBitmap(bitmap, imageX, imageY, imagePaint) + /** + * 方法二:此方法是为将要绘制的图片划定显示区域,可以缓解图片尺寸导致显示异常问题 + */ +// RectF rectF = new RectF(centerX - (textWidth >> 2), centerY - (textHeight * 2), +// centerX + (textWidth >> 2), centerY - (textHeight >> 1)); +// Log.d(kTag, "height: " + rectF.height() + " ==== width: " + rectF.width()); +// canvas.drawBitmap(bitmap, null, rectF, imagePaint); + /** + * 方法三名:采用矩阵方式实现,这可彻底解决图片尺寸导致显示异常的问题 + * + * Matrix matrix=new Matrix(); + */ + } + + /** + * 启动动画 + */ + fun start() { + Log.d(kTag, "start: 启动动画") + text = "正在搜索" + isStart = true + postInvalidate() + } + + /** + * 停止动画 + */ + fun stop() { + Log.d(kTag, "stop: 停止动画") + text = "停止搜索" + isStart = false + postInvalidate() + } + + private var startListener: OnAnimationStartListener? = null + + interface OnAnimationStartListener { + fun onStart(view: WaterRippleView?) + } + + fun setOnAnimationStartListener(listener: OnAnimationStartListener?) { + startListener = listener + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml new file mode 100644 index 0000000..53f6af9 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml new file mode 100644 index 0000000..8b7466a --- /dev/null +++ b/app/src/main/res/layout/activity_add_device.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device_tab.xml b/app/src/main/res/layout/activity_add_device_tab.xml new file mode 100644 index 0000000..44c2c9c --- /dev/null +++ b/app/src/main/res/layout/activity_add_device_tab.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt new file mode 100644 index 0000000..bb3262d --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt @@ -0,0 +1,40 @@ +package com.casic.br.view + +import com.casic.br.R +import com.casic.br.extensions.initLayoutImmersionBar +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import kotlinx.android.synthetic.main.activity_add_device.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceActivity : KotlinBaseActivity() { + + + override fun initLayoutView(): Int = R.layout.activity_add_device + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + iotDevice.setOnClickListener { + + } + + notIotDevice.setOnClickListener { + navigatePageTo() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt new file mode 100644 index 0000000..eb552e4 --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt @@ -0,0 +1,89 @@ +package com.casic.br.view + +import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.viewpager.widget.ViewPager +import com.casic.br.R +import com.casic.br.adapter.ViewPagerAdapter +import com.casic.br.extensions.initLayoutImmersionBar +import com.casic.br.fragment.add.DeviceTypeFragment +import com.casic.br.fragment.add.NearbyDeviceFragment +import com.casic.br.fragment.add.ScanDeviceFragment +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import kotlinx.android.synthetic.main.activity_add_device_tab.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceTabActivity : KotlinBaseActivity() { + + private var menuItem: MenuItem? = null + private var fragmentPages: ArrayList = ArrayList() + + init { + fragmentPages.add(ScanDeviceFragment()) + fragmentPages.add(DeviceTypeFragment()) + fragmentPages.add(NearbyDeviceFragment()) + } + + override fun initLayoutView(): Int = R.layout.activity_add_device_tab + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "扫码添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + bottomNavigation.setOnItemSelectedListener { menuItem -> + when (menuItem.itemId) { + R.id.nav_scan -> { + addDeviceViewPager.currentItem = 0 + titleView.text = "扫码添加设备" + } + R.id.nav_type -> { + addDeviceViewPager.currentItem = 1 + titleView.text = "按型号添加设备" + } + R.id.nav_nearby -> { + addDeviceViewPager.currentItem = 2 + titleView.text = "附近设备" + } + } + false + } + addDeviceViewPager.adapter = ViewPagerAdapter(fragmentPages, supportFragmentManager) + addDeviceViewPager.offscreenPageLimit = fragmentPages.size //缓存页数 + addDeviceViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + + } + + override fun onPageSelected(position: Int) { + if (menuItem != null) { + menuItem!!.isChecked = false + } else { + bottomNavigation.menu.getItem(0).isChecked = false + } + menuItem = bottomNavigation.menu.getItem(position) + menuItem!!.isChecked = true + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt new file mode 100644 index 0000000..56f44dc --- /dev/null +++ b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt @@ -0,0 +1,258 @@ +package com.casic.br.widgets + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.text.TextPaint +import android.util.AttributeSet +import android.util.Log +import android.view.View +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dp2px +import com.pengxh.kt.lite.extensions.sp2px +import java.util.* + +/** + * @description: 呼叫设备水波纹扩散动画控件 + * @author: Pengxh + * @email: 290677893@qq.com + * @date: 2020年9月17日14:24:45 + */ +class WaterRippleView constructor(context: Context, attrs: AttributeSet) : View(context, attrs) { + private val kTag = "WaterRippleView" + + //中心圆半径 + private val radius: Int + + //每次圆递增间距,值越大,扩散速度越快 + private val distance: Int + + //最大扩散距离,值越小,扩散效果越明显 + private val maxDistance: Int + private val textSize: Int + private val textColor: Int + private val spreadColor: Int + private val centerColor: Int + + //图片资源 + private val imageResourceId: Int + private val spreadRadius: MutableList = ArrayList() //扩散圆层级数,元素为扩散的距离 + private val alphas: MutableList = ArrayList() //对应每层圆的透明度 + + //圆心x + private var centerX = 0f + + //圆心y + private var centerY = 0f + + //扩散延迟间隔,越大扩散越慢 + private var animDuration = 50 + private var isStart = false + private var text = "呼叫设备" + private val textPaint by lazy { TextPaint() } + private val imagePaint by lazy { Paint() } + + //中心圆paint + private val centerPaint by lazy { Paint() } + + //扩散圆paint + private val spreadPaint by lazy { Paint() } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.WaterRippleView) + centerColor = a.getColor( + R.styleable.WaterRippleView_ripple_centerColor, + R.color.mainThemeColor.convertColor(context) + ) + spreadColor = a.getColor( + R.styleable.WaterRippleView_ripple_spreadColor, + R.color.mainThemeColor.convertColor(context) + ) + animDuration = a.getInteger(R.styleable.WaterRippleView_ripple_animDuration, animDuration) + radius = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_radius, 50f.dp2px(context) + ) + distance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_distance, 3f.dp2px(context) + ) + maxDistance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_maxDistance, 30f.dp2px(context) + ) + textSize = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_textSize, 16f.sp2px(context) + ) + text = a.getString(R.styleable.WaterRippleView_ripple_text).toString() + textColor = a.getColor( + R.styleable.WaterRippleView_ripple_textColor, R.color.white.convertColor(context) + ) + imageResourceId = a.getResourceId(R.styleable.WaterRippleView_ripple_image, R.mipmap.hujiao) + a.recycle() + + //最开始不透明且扩散距离为0 + alphas.add(255) + spreadRadius.add(0) + //初始化画笔 + initPaint() + if (isStart) { + Log.d(kTag, "onClick: 重复启动动画") + } else { + start() + } + } + + private fun initPaint() { + //中心圆画笔 + centerPaint.color = centerColor + centerPaint.isAntiAlias = true + + //扩散圆圈画笔 + spreadPaint.isAntiAlias = true + spreadPaint.alpha = 255 + spreadPaint.color = spreadColor + + //中心圆文字画笔 + textPaint.color = textColor + textPaint.isAntiAlias = true + textPaint.textSize = textSize.toFloat() + + //中心圆上面图片画笔 + imagePaint.isAntiAlias = true + imagePaint.alpha = 255 + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + //圆心位置 + centerX = (w shr 1).toFloat() + centerY = (h shr 1).toFloat() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec) + val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec) + val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec) + // 获取宽 + val mWidth: Int = if (widthSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + widthSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 获取高 + val mHeight: Int = if (heightSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + heightSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 设置该view的宽高 + setMeasuredDimension(mWidth, mHeight) + } + + @SuppressLint("DrawAllocation") + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (isStart) { + for (i in spreadRadius.indices) { + var alpha = alphas[i] + spreadPaint.alpha = alpha + val width = spreadRadius[i] + //绘制扩散的圆 + canvas.drawCircle(centerX, centerY, (radius + width).toFloat(), spreadPaint) + //每次扩散圆半径递增,圆透明度递减 + if (alpha > 0 && width < (maxDistance + radius).toFloat().dp2px(context)) { + alpha = (alpha - distance).coerceAtLeast(0) + alphas[i] = alpha + spreadRadius[i] = width + distance + } + } + //当最外层扩散圆半径达到最大半径时添加新扩散圆 + val maxRadius = spreadRadius[spreadRadius.size - 1] + if (maxRadius > maxDistance) { + spreadRadius.add(0) + alphas.add(255) + } + //超过5个扩散圆,删除最先绘制的圆,即最外层的圆 + if (spreadRadius.size >= 8) { + alphas.removeAt(0) + spreadRadius.removeAt(0) + } + postInvalidateDelayed(animDuration.toLong()) + } + //中间的圆 + canvas.drawCircle(centerX, centerY, radius.toFloat(), centerPaint) + + //绘制文字 + val textRect = Rect() + textPaint.getTextBounds(text, 0, text.length, textRect) + val textWidth = textRect.width() + val textHeight = textRect.height() + //计算文字左下角坐标 + val textX = centerX - (textWidth shr 1) + val textY = centerY + 4 * textHeight / 3 //让文字靠下点,所以Y坐标大一点 + canvas.drawText(text, textX, textY, textPaint) + + //绘制图片 + val bitmap = BitmapFactory.decodeResource(resources, imageResourceId) //实例化 + + /** + * 方法一:此方法局限性大,对于图片要求较高,必须是尺寸合适的图片,否则会显示异常 + */ + val imageWidth = bitmap.width + val imageHeight = bitmap.height + //计算图片左上角坐标 + val imageX = centerX - (imageWidth shr 1) + val imageY = centerY - 4 * imageHeight / 3 //让图片靠上点,所以Y坐标小一点 + canvas.drawBitmap(bitmap, imageX, imageY, imagePaint) + /** + * 方法二:此方法是为将要绘制的图片划定显示区域,可以缓解图片尺寸导致显示异常问题 + */ +// RectF rectF = new RectF(centerX - (textWidth >> 2), centerY - (textHeight * 2), +// centerX + (textWidth >> 2), centerY - (textHeight >> 1)); +// Log.d(kTag, "height: " + rectF.height() + " ==== width: " + rectF.width()); +// canvas.drawBitmap(bitmap, null, rectF, imagePaint); + /** + * 方法三名:采用矩阵方式实现,这可彻底解决图片尺寸导致显示异常的问题 + * + * Matrix matrix=new Matrix(); + */ + } + + /** + * 启动动画 + */ + fun start() { + Log.d(kTag, "start: 启动动画") + text = "正在搜索" + isStart = true + postInvalidate() + } + + /** + * 停止动画 + */ + fun stop() { + Log.d(kTag, "stop: 停止动画") + text = "停止搜索" + isStart = false + postInvalidate() + } + + private var startListener: OnAnimationStartListener? = null + + interface OnAnimationStartListener { + fun onStart(view: WaterRippleView?) + } + + fun setOnAnimationStartListener(listener: OnAnimationStartListener?) { + startListener = listener + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml new file mode 100644 index 0000000..53f6af9 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml new file mode 100644 index 0000000..8b7466a --- /dev/null +++ b/app/src/main/res/layout/activity_add_device.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device_tab.xml b/app/src/main/res/layout/activity_add_device_tab.xml new file mode 100644 index 0000000..44c2c9c --- /dev/null +++ b/app/src/main/res/layout/activity_add_device_tab.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 1ae1069..e2916e4 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -15,7 +15,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt new file mode 100644 index 0000000..bb3262d --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt @@ -0,0 +1,40 @@ +package com.casic.br.view + +import com.casic.br.R +import com.casic.br.extensions.initLayoutImmersionBar +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import kotlinx.android.synthetic.main.activity_add_device.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceActivity : KotlinBaseActivity() { + + + override fun initLayoutView(): Int = R.layout.activity_add_device + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + iotDevice.setOnClickListener { + + } + + notIotDevice.setOnClickListener { + navigatePageTo() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt new file mode 100644 index 0000000..eb552e4 --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt @@ -0,0 +1,89 @@ +package com.casic.br.view + +import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.viewpager.widget.ViewPager +import com.casic.br.R +import com.casic.br.adapter.ViewPagerAdapter +import com.casic.br.extensions.initLayoutImmersionBar +import com.casic.br.fragment.add.DeviceTypeFragment +import com.casic.br.fragment.add.NearbyDeviceFragment +import com.casic.br.fragment.add.ScanDeviceFragment +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import kotlinx.android.synthetic.main.activity_add_device_tab.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceTabActivity : KotlinBaseActivity() { + + private var menuItem: MenuItem? = null + private var fragmentPages: ArrayList = ArrayList() + + init { + fragmentPages.add(ScanDeviceFragment()) + fragmentPages.add(DeviceTypeFragment()) + fragmentPages.add(NearbyDeviceFragment()) + } + + override fun initLayoutView(): Int = R.layout.activity_add_device_tab + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "扫码添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + bottomNavigation.setOnItemSelectedListener { menuItem -> + when (menuItem.itemId) { + R.id.nav_scan -> { + addDeviceViewPager.currentItem = 0 + titleView.text = "扫码添加设备" + } + R.id.nav_type -> { + addDeviceViewPager.currentItem = 1 + titleView.text = "按型号添加设备" + } + R.id.nav_nearby -> { + addDeviceViewPager.currentItem = 2 + titleView.text = "附近设备" + } + } + false + } + addDeviceViewPager.adapter = ViewPagerAdapter(fragmentPages, supportFragmentManager) + addDeviceViewPager.offscreenPageLimit = fragmentPages.size //缓存页数 + addDeviceViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + + } + + override fun onPageSelected(position: Int) { + if (menuItem != null) { + menuItem!!.isChecked = false + } else { + bottomNavigation.menu.getItem(0).isChecked = false + } + menuItem = bottomNavigation.menu.getItem(position) + menuItem!!.isChecked = true + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt new file mode 100644 index 0000000..56f44dc --- /dev/null +++ b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt @@ -0,0 +1,258 @@ +package com.casic.br.widgets + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.text.TextPaint +import android.util.AttributeSet +import android.util.Log +import android.view.View +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dp2px +import com.pengxh.kt.lite.extensions.sp2px +import java.util.* + +/** + * @description: 呼叫设备水波纹扩散动画控件 + * @author: Pengxh + * @email: 290677893@qq.com + * @date: 2020年9月17日14:24:45 + */ +class WaterRippleView constructor(context: Context, attrs: AttributeSet) : View(context, attrs) { + private val kTag = "WaterRippleView" + + //中心圆半径 + private val radius: Int + + //每次圆递增间距,值越大,扩散速度越快 + private val distance: Int + + //最大扩散距离,值越小,扩散效果越明显 + private val maxDistance: Int + private val textSize: Int + private val textColor: Int + private val spreadColor: Int + private val centerColor: Int + + //图片资源 + private val imageResourceId: Int + private val spreadRadius: MutableList = ArrayList() //扩散圆层级数,元素为扩散的距离 + private val alphas: MutableList = ArrayList() //对应每层圆的透明度 + + //圆心x + private var centerX = 0f + + //圆心y + private var centerY = 0f + + //扩散延迟间隔,越大扩散越慢 + private var animDuration = 50 + private var isStart = false + private var text = "呼叫设备" + private val textPaint by lazy { TextPaint() } + private val imagePaint by lazy { Paint() } + + //中心圆paint + private val centerPaint by lazy { Paint() } + + //扩散圆paint + private val spreadPaint by lazy { Paint() } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.WaterRippleView) + centerColor = a.getColor( + R.styleable.WaterRippleView_ripple_centerColor, + R.color.mainThemeColor.convertColor(context) + ) + spreadColor = a.getColor( + R.styleable.WaterRippleView_ripple_spreadColor, + R.color.mainThemeColor.convertColor(context) + ) + animDuration = a.getInteger(R.styleable.WaterRippleView_ripple_animDuration, animDuration) + radius = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_radius, 50f.dp2px(context) + ) + distance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_distance, 3f.dp2px(context) + ) + maxDistance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_maxDistance, 30f.dp2px(context) + ) + textSize = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_textSize, 16f.sp2px(context) + ) + text = a.getString(R.styleable.WaterRippleView_ripple_text).toString() + textColor = a.getColor( + R.styleable.WaterRippleView_ripple_textColor, R.color.white.convertColor(context) + ) + imageResourceId = a.getResourceId(R.styleable.WaterRippleView_ripple_image, R.mipmap.hujiao) + a.recycle() + + //最开始不透明且扩散距离为0 + alphas.add(255) + spreadRadius.add(0) + //初始化画笔 + initPaint() + if (isStart) { + Log.d(kTag, "onClick: 重复启动动画") + } else { + start() + } + } + + private fun initPaint() { + //中心圆画笔 + centerPaint.color = centerColor + centerPaint.isAntiAlias = true + + //扩散圆圈画笔 + spreadPaint.isAntiAlias = true + spreadPaint.alpha = 255 + spreadPaint.color = spreadColor + + //中心圆文字画笔 + textPaint.color = textColor + textPaint.isAntiAlias = true + textPaint.textSize = textSize.toFloat() + + //中心圆上面图片画笔 + imagePaint.isAntiAlias = true + imagePaint.alpha = 255 + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + //圆心位置 + centerX = (w shr 1).toFloat() + centerY = (h shr 1).toFloat() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec) + val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec) + val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec) + // 获取宽 + val mWidth: Int = if (widthSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + widthSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 获取高 + val mHeight: Int = if (heightSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + heightSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 设置该view的宽高 + setMeasuredDimension(mWidth, mHeight) + } + + @SuppressLint("DrawAllocation") + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (isStart) { + for (i in spreadRadius.indices) { + var alpha = alphas[i] + spreadPaint.alpha = alpha + val width = spreadRadius[i] + //绘制扩散的圆 + canvas.drawCircle(centerX, centerY, (radius + width).toFloat(), spreadPaint) + //每次扩散圆半径递增,圆透明度递减 + if (alpha > 0 && width < (maxDistance + radius).toFloat().dp2px(context)) { + alpha = (alpha - distance).coerceAtLeast(0) + alphas[i] = alpha + spreadRadius[i] = width + distance + } + } + //当最外层扩散圆半径达到最大半径时添加新扩散圆 + val maxRadius = spreadRadius[spreadRadius.size - 1] + if (maxRadius > maxDistance) { + spreadRadius.add(0) + alphas.add(255) + } + //超过5个扩散圆,删除最先绘制的圆,即最外层的圆 + if (spreadRadius.size >= 8) { + alphas.removeAt(0) + spreadRadius.removeAt(0) + } + postInvalidateDelayed(animDuration.toLong()) + } + //中间的圆 + canvas.drawCircle(centerX, centerY, radius.toFloat(), centerPaint) + + //绘制文字 + val textRect = Rect() + textPaint.getTextBounds(text, 0, text.length, textRect) + val textWidth = textRect.width() + val textHeight = textRect.height() + //计算文字左下角坐标 + val textX = centerX - (textWidth shr 1) + val textY = centerY + 4 * textHeight / 3 //让文字靠下点,所以Y坐标大一点 + canvas.drawText(text, textX, textY, textPaint) + + //绘制图片 + val bitmap = BitmapFactory.decodeResource(resources, imageResourceId) //实例化 + + /** + * 方法一:此方法局限性大,对于图片要求较高,必须是尺寸合适的图片,否则会显示异常 + */ + val imageWidth = bitmap.width + val imageHeight = bitmap.height + //计算图片左上角坐标 + val imageX = centerX - (imageWidth shr 1) + val imageY = centerY - 4 * imageHeight / 3 //让图片靠上点,所以Y坐标小一点 + canvas.drawBitmap(bitmap, imageX, imageY, imagePaint) + /** + * 方法二:此方法是为将要绘制的图片划定显示区域,可以缓解图片尺寸导致显示异常问题 + */ +// RectF rectF = new RectF(centerX - (textWidth >> 2), centerY - (textHeight * 2), +// centerX + (textWidth >> 2), centerY - (textHeight >> 1)); +// Log.d(kTag, "height: " + rectF.height() + " ==== width: " + rectF.width()); +// canvas.drawBitmap(bitmap, null, rectF, imagePaint); + /** + * 方法三名:采用矩阵方式实现,这可彻底解决图片尺寸导致显示异常的问题 + * + * Matrix matrix=new Matrix(); + */ + } + + /** + * 启动动画 + */ + fun start() { + Log.d(kTag, "start: 启动动画") + text = "正在搜索" + isStart = true + postInvalidate() + } + + /** + * 停止动画 + */ + fun stop() { + Log.d(kTag, "stop: 停止动画") + text = "停止搜索" + isStart = false + postInvalidate() + } + + private var startListener: OnAnimationStartListener? = null + + interface OnAnimationStartListener { + fun onStart(view: WaterRippleView?) + } + + fun setOnAnimationStartListener(listener: OnAnimationStartListener?) { + startListener = listener + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml new file mode 100644 index 0000000..53f6af9 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml new file mode 100644 index 0000000..8b7466a --- /dev/null +++ b/app/src/main/res/layout/activity_add_device.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device_tab.xml b/app/src/main/res/layout/activity_add_device_tab.xml new file mode 100644 index 0000000..44c2c9c --- /dev/null +++ b/app/src/main/res/layout/activity_add_device_tab.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 1ae1069..e2916e4 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -15,7 +15,6 @@ + app:menu="@menu/main_bottom_nav_menu" /> \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt new file mode 100644 index 0000000..bb3262d --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt @@ -0,0 +1,40 @@ +package com.casic.br.view + +import com.casic.br.R +import com.casic.br.extensions.initLayoutImmersionBar +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import kotlinx.android.synthetic.main.activity_add_device.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceActivity : KotlinBaseActivity() { + + + override fun initLayoutView(): Int = R.layout.activity_add_device + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + iotDevice.setOnClickListener { + + } + + notIotDevice.setOnClickListener { + navigatePageTo() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt new file mode 100644 index 0000000..eb552e4 --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt @@ -0,0 +1,89 @@ +package com.casic.br.view + +import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.viewpager.widget.ViewPager +import com.casic.br.R +import com.casic.br.adapter.ViewPagerAdapter +import com.casic.br.extensions.initLayoutImmersionBar +import com.casic.br.fragment.add.DeviceTypeFragment +import com.casic.br.fragment.add.NearbyDeviceFragment +import com.casic.br.fragment.add.ScanDeviceFragment +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import kotlinx.android.synthetic.main.activity_add_device_tab.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceTabActivity : KotlinBaseActivity() { + + private var menuItem: MenuItem? = null + private var fragmentPages: ArrayList = ArrayList() + + init { + fragmentPages.add(ScanDeviceFragment()) + fragmentPages.add(DeviceTypeFragment()) + fragmentPages.add(NearbyDeviceFragment()) + } + + override fun initLayoutView(): Int = R.layout.activity_add_device_tab + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "扫码添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + bottomNavigation.setOnItemSelectedListener { menuItem -> + when (menuItem.itemId) { + R.id.nav_scan -> { + addDeviceViewPager.currentItem = 0 + titleView.text = "扫码添加设备" + } + R.id.nav_type -> { + addDeviceViewPager.currentItem = 1 + titleView.text = "按型号添加设备" + } + R.id.nav_nearby -> { + addDeviceViewPager.currentItem = 2 + titleView.text = "附近设备" + } + } + false + } + addDeviceViewPager.adapter = ViewPagerAdapter(fragmentPages, supportFragmentManager) + addDeviceViewPager.offscreenPageLimit = fragmentPages.size //缓存页数 + addDeviceViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + + } + + override fun onPageSelected(position: Int) { + if (menuItem != null) { + menuItem!!.isChecked = false + } else { + bottomNavigation.menu.getItem(0).isChecked = false + } + menuItem = bottomNavigation.menu.getItem(position) + menuItem!!.isChecked = true + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt new file mode 100644 index 0000000..56f44dc --- /dev/null +++ b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt @@ -0,0 +1,258 @@ +package com.casic.br.widgets + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.text.TextPaint +import android.util.AttributeSet +import android.util.Log +import android.view.View +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dp2px +import com.pengxh.kt.lite.extensions.sp2px +import java.util.* + +/** + * @description: 呼叫设备水波纹扩散动画控件 + * @author: Pengxh + * @email: 290677893@qq.com + * @date: 2020年9月17日14:24:45 + */ +class WaterRippleView constructor(context: Context, attrs: AttributeSet) : View(context, attrs) { + private val kTag = "WaterRippleView" + + //中心圆半径 + private val radius: Int + + //每次圆递增间距,值越大,扩散速度越快 + private val distance: Int + + //最大扩散距离,值越小,扩散效果越明显 + private val maxDistance: Int + private val textSize: Int + private val textColor: Int + private val spreadColor: Int + private val centerColor: Int + + //图片资源 + private val imageResourceId: Int + private val spreadRadius: MutableList = ArrayList() //扩散圆层级数,元素为扩散的距离 + private val alphas: MutableList = ArrayList() //对应每层圆的透明度 + + //圆心x + private var centerX = 0f + + //圆心y + private var centerY = 0f + + //扩散延迟间隔,越大扩散越慢 + private var animDuration = 50 + private var isStart = false + private var text = "呼叫设备" + private val textPaint by lazy { TextPaint() } + private val imagePaint by lazy { Paint() } + + //中心圆paint + private val centerPaint by lazy { Paint() } + + //扩散圆paint + private val spreadPaint by lazy { Paint() } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.WaterRippleView) + centerColor = a.getColor( + R.styleable.WaterRippleView_ripple_centerColor, + R.color.mainThemeColor.convertColor(context) + ) + spreadColor = a.getColor( + R.styleable.WaterRippleView_ripple_spreadColor, + R.color.mainThemeColor.convertColor(context) + ) + animDuration = a.getInteger(R.styleable.WaterRippleView_ripple_animDuration, animDuration) + radius = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_radius, 50f.dp2px(context) + ) + distance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_distance, 3f.dp2px(context) + ) + maxDistance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_maxDistance, 30f.dp2px(context) + ) + textSize = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_textSize, 16f.sp2px(context) + ) + text = a.getString(R.styleable.WaterRippleView_ripple_text).toString() + textColor = a.getColor( + R.styleable.WaterRippleView_ripple_textColor, R.color.white.convertColor(context) + ) + imageResourceId = a.getResourceId(R.styleable.WaterRippleView_ripple_image, R.mipmap.hujiao) + a.recycle() + + //最开始不透明且扩散距离为0 + alphas.add(255) + spreadRadius.add(0) + //初始化画笔 + initPaint() + if (isStart) { + Log.d(kTag, "onClick: 重复启动动画") + } else { + start() + } + } + + private fun initPaint() { + //中心圆画笔 + centerPaint.color = centerColor + centerPaint.isAntiAlias = true + + //扩散圆圈画笔 + spreadPaint.isAntiAlias = true + spreadPaint.alpha = 255 + spreadPaint.color = spreadColor + + //中心圆文字画笔 + textPaint.color = textColor + textPaint.isAntiAlias = true + textPaint.textSize = textSize.toFloat() + + //中心圆上面图片画笔 + imagePaint.isAntiAlias = true + imagePaint.alpha = 255 + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + //圆心位置 + centerX = (w shr 1).toFloat() + centerY = (h shr 1).toFloat() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec) + val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec) + val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec) + // 获取宽 + val mWidth: Int = if (widthSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + widthSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 获取高 + val mHeight: Int = if (heightSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + heightSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 设置该view的宽高 + setMeasuredDimension(mWidth, mHeight) + } + + @SuppressLint("DrawAllocation") + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (isStart) { + for (i in spreadRadius.indices) { + var alpha = alphas[i] + spreadPaint.alpha = alpha + val width = spreadRadius[i] + //绘制扩散的圆 + canvas.drawCircle(centerX, centerY, (radius + width).toFloat(), spreadPaint) + //每次扩散圆半径递增,圆透明度递减 + if (alpha > 0 && width < (maxDistance + radius).toFloat().dp2px(context)) { + alpha = (alpha - distance).coerceAtLeast(0) + alphas[i] = alpha + spreadRadius[i] = width + distance + } + } + //当最外层扩散圆半径达到最大半径时添加新扩散圆 + val maxRadius = spreadRadius[spreadRadius.size - 1] + if (maxRadius > maxDistance) { + spreadRadius.add(0) + alphas.add(255) + } + //超过5个扩散圆,删除最先绘制的圆,即最外层的圆 + if (spreadRadius.size >= 8) { + alphas.removeAt(0) + spreadRadius.removeAt(0) + } + postInvalidateDelayed(animDuration.toLong()) + } + //中间的圆 + canvas.drawCircle(centerX, centerY, radius.toFloat(), centerPaint) + + //绘制文字 + val textRect = Rect() + textPaint.getTextBounds(text, 0, text.length, textRect) + val textWidth = textRect.width() + val textHeight = textRect.height() + //计算文字左下角坐标 + val textX = centerX - (textWidth shr 1) + val textY = centerY + 4 * textHeight / 3 //让文字靠下点,所以Y坐标大一点 + canvas.drawText(text, textX, textY, textPaint) + + //绘制图片 + val bitmap = BitmapFactory.decodeResource(resources, imageResourceId) //实例化 + + /** + * 方法一:此方法局限性大,对于图片要求较高,必须是尺寸合适的图片,否则会显示异常 + */ + val imageWidth = bitmap.width + val imageHeight = bitmap.height + //计算图片左上角坐标 + val imageX = centerX - (imageWidth shr 1) + val imageY = centerY - 4 * imageHeight / 3 //让图片靠上点,所以Y坐标小一点 + canvas.drawBitmap(bitmap, imageX, imageY, imagePaint) + /** + * 方法二:此方法是为将要绘制的图片划定显示区域,可以缓解图片尺寸导致显示异常问题 + */ +// RectF rectF = new RectF(centerX - (textWidth >> 2), centerY - (textHeight * 2), +// centerX + (textWidth >> 2), centerY - (textHeight >> 1)); +// Log.d(kTag, "height: " + rectF.height() + " ==== width: " + rectF.width()); +// canvas.drawBitmap(bitmap, null, rectF, imagePaint); + /** + * 方法三名:采用矩阵方式实现,这可彻底解决图片尺寸导致显示异常的问题 + * + * Matrix matrix=new Matrix(); + */ + } + + /** + * 启动动画 + */ + fun start() { + Log.d(kTag, "start: 启动动画") + text = "正在搜索" + isStart = true + postInvalidate() + } + + /** + * 停止动画 + */ + fun stop() { + Log.d(kTag, "stop: 停止动画") + text = "停止搜索" + isStart = false + postInvalidate() + } + + private var startListener: OnAnimationStartListener? = null + + interface OnAnimationStartListener { + fun onStart(view: WaterRippleView?) + } + + fun setOnAnimationStartListener(listener: OnAnimationStartListener?) { + startListener = listener + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml new file mode 100644 index 0000000..53f6af9 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml new file mode 100644 index 0000000..8b7466a --- /dev/null +++ b/app/src/main/res/layout/activity_add_device.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device_tab.xml b/app/src/main/res/layout/activity_add_device_tab.xml new file mode 100644 index 0000000..44c2c9c --- /dev/null +++ b/app/src/main/res/layout/activity_add_device_tab.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 1ae1069..e2916e4 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -15,7 +15,6 @@ + app:menu="@menu/main_bottom_nav_menu" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_add_device_nearby.xml b/app/src/main/res/layout/fragment_add_device_nearby.xml new file mode 100644 index 0000000..6731e69 --- /dev/null +++ b/app/src/main/res/layout/fragment_add_device_nearby.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e5171b0..f3a2e9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,10 @@ targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录 daoPackage 'com.casic.br.greendao'//设置DaoMaster、DaoSession、Dao包名 } + + packagingOptions { + pickFirst 'lib/*/libc++_shared.so' // 多个 AAR(Android Library)文件中存在此 .so 文件,请选择第一个 + } } dependencies { @@ -102,4 +106,8 @@ implementation 'io.github.youth5201314:banner:2.2.2' //SegmentTabLayout implementation 'io.github.h07000223:flycoTabLayout:3.0.0' + // App SDK 最新稳定安卓版: + implementation 'com.alibaba:fastjson:1.1.67.android' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9' + implementation 'com.tuya.smart:tuyasmart:4.0.0' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c2e256f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +#fastJson +-keep class com.alibaba.fastjson.**{*;} +-dontwarn com.alibaba.fastjson.** + +#mqtt +-keep class com.tuya.smart.mqttclient.mqttv3.** { *; } +-dontwarn com.tuya.smart.mqttclient.mqttv3.** + +#OkHttp3 +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +-keep class com.tuya.**{*;} +-dontwarn com.tuya.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b67d659..db94db8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,15 +18,16 @@ + android:usesCleartextTraffic="true" + tools:replace="android:allowBackup,android:supportsRtl"> @@ -43,6 +45,8 @@ + + @@ -53,6 +57,13 @@ - + + + + \ No newline at end of file diff --git a/app/src/main/assets/t_s.bmp b/app/src/main/assets/t_s.bmp new file mode 100644 index 0000000..eb7c604 --- /dev/null +++ b/app/src/main/assets/t_s.bmp Binary files differ diff --git a/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt new file mode 100644 index 0000000..317eba5 --- /dev/null +++ b/app/src/main/java/com/casic/br/adapter/DeviceTypeAdapter.kt @@ -0,0 +1,62 @@ +package com.casic.br.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor + +/** + * 设备分类列表Adapter + * */ +class DeviceTypeAdapter( + private val context: Context, private val titles: Array +) : RecyclerView.Adapter() { + + private var layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var mPosition = 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder( + layoutInflater.inflate(R.layout.item_device_type_rv_l, parent, false) + ) + + override fun onBindViewHolder( + holder: ItemViewHolder, @SuppressLint("RecyclerView") position: Int + ) { + holder.textView.text = titles[position] + + // 设置默认选择第一个 + if (mPosition != position) { + holder.textView.setTextColor(R.color.mainTextColor.convertColor(context)) + } else if (mPosition == position) { + holder.textView.setTextColor(R.color.mainThemeColor.convertColor(context)) + } + + holder.itemView.setOnClickListener { + clickListener?.onItemClick(position) + // 选中点击的位置 + mPosition = position + notifyDataSetChanged() + } + } + + override fun getItemCount(): Int = titles.size + + private var clickListener: OnTypeItemClickListener? = null + + interface OnTypeItemClickListener { + fun onItemClick(position: Int) + } + + fun setOnTypeItemClickListener(clickListener: OnTypeItemClickListener?) { + this.clickListener = clickListener + } + + class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/base/BaseApplication.kt b/app/src/main/java/com/casic/br/base/BaseApplication.kt index d3767fb..bf8285c 100644 --- a/app/src/main/java/com/casic/br/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/br/base/BaseApplication.kt @@ -1,13 +1,23 @@ package com.casic.br.base import android.app.Application +import android.util.Log import com.casic.br.greendao.DaoMaster import com.casic.br.greendao.DaoSession +import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.SaveKeyValues +import com.tuya.smart.android.user.api.ILoginCallback +import com.tuya.smart.android.user.bean.User +import com.tuya.smart.home.sdk.TuyaHomeSdk +import com.tuya.smart.home.sdk.bean.HomeBean +import com.tuya.smart.home.sdk.callback.ITuyaHomeResultCallback import kotlin.properties.Delegates + class BaseApplication : Application() { + private val kTag = "BaseApplication" + companion object { private var instance: BaseApplication by Delegates.notNull() @@ -20,11 +30,46 @@ super.onCreate() instance = this SaveKeyValues.initSharedPreferences(this) + initTuya() val devOpenHelper = DaoMaster.DevOpenHelper(this, "SmartKitchen.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() } + private fun initTuya() { + TuyaHomeSdk.init(this) + TuyaHomeSdk.setDebugMode(true) + /** + * 家庭是智能生活 App SDK 开发下实际场景的最大单位。 + * IoT 设备的添加、编辑、移除、状态变化的监听基于家庭下。 + * 用户账号下创建任意多个家庭。 + * */ + //TODO 必须登录 + TuyaHomeSdk.getUserInstance() + .loginWithPhonePassword("86", "18310581916", "123456", object : ILoginCallback { + override fun onSuccess(user: User?) { + Log.d(kTag, "Login success:" + user!!.username) + } + + override fun onError(code: String, error: String) { + Log.e(kTag, "code: " + code + "error:" + error) + } + }) + + + TuyaHomeSdk.getHomeManagerInstance() + .createHome("北燃生活体验馆", 116.486394, 39.885734, "北燃生活体验馆", arrayListOf(), object : + ITuyaHomeResultCallback { + override fun onSuccess(bean: HomeBean?) { + Log.d(kTag, bean!!.toJson()) + } + + override fun onError(errorCode: String?, errorMsg: String?) { + Log.e(kTag, "errorCode: $errorCode ---- $errorMsg") + } + }) + } + fun obtainDaoSession(): DaoSession { return daoSession } diff --git a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt index f5e4dec..cc20c54 100644 --- a/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt +++ b/app/src/main/java/com/casic/br/fragment/DevicePageFragment.kt @@ -7,6 +7,7 @@ import com.casic.br.extensions.initLayoutImmersionBar import com.casic.br.model.AddedDeviceModel import com.casic.br.model.RecommendModel +import com.casic.br.view.AddDeviceActivity import com.casic.br.view.AllDeviceActivity import com.casic.br.view.device.FireplaceActivity import com.casic.br.view.device.RangeHoodActivity @@ -53,7 +54,7 @@ addedDeviceAdapter.setOnItemClickListener(object : AddedDeviceAdapter.OnItemClickListener { override fun onAddDeviceClick() { - + requireContext().navigatePageTo() } override fun onItemClick(position: Int) { diff --git a/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt new file mode 100644 index 0000000..2c6d74a --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/DeviceTypeFragment.kt @@ -0,0 +1,39 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.adapter.DeviceTypeAdapter +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_type.* + +class DeviceTypeFragment : KotlinBaseFragment() { + + private val kTag = "DeviceTypeFragment" + private val deviceTypes = arrayOf("热水器", "油烟机", "厨灶具", "其他产品") + + override fun initLayoutView(): Int = R.layout.fragment_add_device_type + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + val deviceTypeAdapter = + DeviceTypeAdapter(requireContext(), deviceTypes) + deviceTypeRecyclerView.adapter = deviceTypeAdapter + deviceTypeAdapter.setOnTypeItemClickListener(object : + DeviceTypeAdapter.OnTypeItemClickListener { + override fun onItemClick(position: Int) { + Log.d(kTag, "onItemClick: ${deviceTypes[position]}") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt new file mode 100644 index 0000000..e2b403b --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/NearbyDeviceFragment.kt @@ -0,0 +1,70 @@ +package com.casic.br.fragment.add + +import android.util.Log +import com.casic.br.R +import com.casic.br.widgets.WaterRippleView +import com.pengxh.kt.lite.base.KotlinBaseFragment +import kotlinx.android.synthetic.main.fragment_add_device_nearby.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NearbyDeviceFragment : KotlinBaseFragment() { + + private val kTag = "NearbyDeviceFragment" + private var isRunning = true + private lateinit var singleThreadExecutor: ExecutorService + + override fun initLayoutView(): Int = R.layout.fragment_add_device_nearby + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + //只有一个核心线程,当被占用时,其他的任务需要进入队列等待 + singleThreadExecutor = Executors.newSingleThreadExecutor() + } + + override fun initEvent() { + waterRippleView.setOnAnimationStartListener(object : + WaterRippleView.OnAnimationStartListener { + override fun onStart(view: WaterRippleView?) { + view?.start() + //开启线程搜索设备 + Log.d(kTag, "onStart: 开始线程") + singleThreadExecutor.execute(searchRunnable) + isRunning = true + } + }) + } + + private val searchRunnable = Runnable { + while (true) { + try { + if (!isRunning) { + Log.d(kTag, "run: 设备搜索线程休眠中...") + Thread.sleep(Long.MAX_VALUE) + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(kTag, "run: 设备搜索线程运行中...") + try { + //搜索设备 + Thread.sleep(1000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + override fun onPause() { + super.onPause() + isRunning = false + waterRippleView.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt new file mode 100644 index 0000000..14c7411 --- /dev/null +++ b/app/src/main/java/com/casic/br/fragment/add/ScanDeviceFragment.kt @@ -0,0 +1,25 @@ +package com.casic.br.fragment.add + +import com.casic.br.R +import com.pengxh.kt.lite.base.KotlinBaseFragment + +class ScanDeviceFragment : KotlinBaseFragment() { + + override fun initLayoutView(): Int = R.layout.fragment_add_device_scan + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt new file mode 100644 index 0000000..bb3262d --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceActivity.kt @@ -0,0 +1,40 @@ +package com.casic.br.view + +import com.casic.br.R +import com.casic.br.extensions.initLayoutImmersionBar +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import kotlinx.android.synthetic.main.activity_add_device.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceActivity : KotlinBaseActivity() { + + + override fun initLayoutView(): Int = R.layout.activity_add_device + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + iotDevice.setOnClickListener { + + } + + notIotDevice.setOnClickListener { + navigatePageTo() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt new file mode 100644 index 0000000..eb552e4 --- /dev/null +++ b/app/src/main/java/com/casic/br/view/AddDeviceTabActivity.kt @@ -0,0 +1,89 @@ +package com.casic.br.view + +import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.viewpager.widget.ViewPager +import com.casic.br.R +import com.casic.br.adapter.ViewPagerAdapter +import com.casic.br.extensions.initLayoutImmersionBar +import com.casic.br.fragment.add.DeviceTypeFragment +import com.casic.br.fragment.add.NearbyDeviceFragment +import com.casic.br.fragment.add.ScanDeviceFragment +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import kotlinx.android.synthetic.main.activity_add_device_tab.* +import kotlinx.android.synthetic.main.include_left_back_title.* + +class AddDeviceTabActivity : KotlinBaseActivity() { + + private var menuItem: MenuItem? = null + private var fragmentPages: ArrayList = ArrayList() + + init { + fragmentPages.add(ScanDeviceFragment()) + fragmentPages.add(DeviceTypeFragment()) + fragmentPages.add(NearbyDeviceFragment()) + } + + override fun initLayoutView(): Int = R.layout.activity_add_device_tab + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + initLayoutImmersionBar(rootView) + leftBackView.setOnClickListener { finish() } + titleView.text = "扫码添加设备" + } + + override fun initData() { + + } + + override fun initEvent() { + bottomNavigation.setOnItemSelectedListener { menuItem -> + when (menuItem.itemId) { + R.id.nav_scan -> { + addDeviceViewPager.currentItem = 0 + titleView.text = "扫码添加设备" + } + R.id.nav_type -> { + addDeviceViewPager.currentItem = 1 + titleView.text = "按型号添加设备" + } + R.id.nav_nearby -> { + addDeviceViewPager.currentItem = 2 + titleView.text = "附近设备" + } + } + false + } + addDeviceViewPager.adapter = ViewPagerAdapter(fragmentPages, supportFragmentManager) + addDeviceViewPager.offscreenPageLimit = fragmentPages.size //缓存页数 + addDeviceViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + + } + + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + + } + + override fun onPageSelected(position: Int) { + if (menuItem != null) { + menuItem!!.isChecked = false + } else { + bottomNavigation.menu.getItem(0).isChecked = false + } + menuItem = bottomNavigation.menu.getItem(position) + menuItem!!.isChecked = true + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt new file mode 100644 index 0000000..56f44dc --- /dev/null +++ b/app/src/main/java/com/casic/br/widgets/WaterRippleView.kt @@ -0,0 +1,258 @@ +package com.casic.br.widgets + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.text.TextPaint +import android.util.AttributeSet +import android.util.Log +import android.view.View +import com.casic.br.R +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dp2px +import com.pengxh.kt.lite.extensions.sp2px +import java.util.* + +/** + * @description: 呼叫设备水波纹扩散动画控件 + * @author: Pengxh + * @email: 290677893@qq.com + * @date: 2020年9月17日14:24:45 + */ +class WaterRippleView constructor(context: Context, attrs: AttributeSet) : View(context, attrs) { + private val kTag = "WaterRippleView" + + //中心圆半径 + private val radius: Int + + //每次圆递增间距,值越大,扩散速度越快 + private val distance: Int + + //最大扩散距离,值越小,扩散效果越明显 + private val maxDistance: Int + private val textSize: Int + private val textColor: Int + private val spreadColor: Int + private val centerColor: Int + + //图片资源 + private val imageResourceId: Int + private val spreadRadius: MutableList = ArrayList() //扩散圆层级数,元素为扩散的距离 + private val alphas: MutableList = ArrayList() //对应每层圆的透明度 + + //圆心x + private var centerX = 0f + + //圆心y + private var centerY = 0f + + //扩散延迟间隔,越大扩散越慢 + private var animDuration = 50 + private var isStart = false + private var text = "呼叫设备" + private val textPaint by lazy { TextPaint() } + private val imagePaint by lazy { Paint() } + + //中心圆paint + private val centerPaint by lazy { Paint() } + + //扩散圆paint + private val spreadPaint by lazy { Paint() } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.WaterRippleView) + centerColor = a.getColor( + R.styleable.WaterRippleView_ripple_centerColor, + R.color.mainThemeColor.convertColor(context) + ) + spreadColor = a.getColor( + R.styleable.WaterRippleView_ripple_spreadColor, + R.color.mainThemeColor.convertColor(context) + ) + animDuration = a.getInteger(R.styleable.WaterRippleView_ripple_animDuration, animDuration) + radius = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_radius, 50f.dp2px(context) + ) + distance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_distance, 3f.dp2px(context) + ) + maxDistance = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_maxDistance, 30f.dp2px(context) + ) + textSize = a.getDimensionPixelOffset( + R.styleable.WaterRippleView_ripple_textSize, 16f.sp2px(context) + ) + text = a.getString(R.styleable.WaterRippleView_ripple_text).toString() + textColor = a.getColor( + R.styleable.WaterRippleView_ripple_textColor, R.color.white.convertColor(context) + ) + imageResourceId = a.getResourceId(R.styleable.WaterRippleView_ripple_image, R.mipmap.hujiao) + a.recycle() + + //最开始不透明且扩散距离为0 + alphas.add(255) + spreadRadius.add(0) + //初始化画笔 + initPaint() + if (isStart) { + Log.d(kTag, "onClick: 重复启动动画") + } else { + start() + } + } + + private fun initPaint() { + //中心圆画笔 + centerPaint.color = centerColor + centerPaint.isAntiAlias = true + + //扩散圆圈画笔 + spreadPaint.isAntiAlias = true + spreadPaint.alpha = 255 + spreadPaint.color = spreadColor + + //中心圆文字画笔 + textPaint.color = textColor + textPaint.isAntiAlias = true + textPaint.textSize = textSize.toFloat() + + //中心圆上面图片画笔 + imagePaint.isAntiAlias = true + imagePaint.alpha = 255 + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + //圆心位置 + centerX = (w shr 1).toFloat() + centerY = (h shr 1).toFloat() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec) + val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec) + val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec) + // 获取宽 + val mWidth: Int = if (widthSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + widthSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 获取高 + val mHeight: Int = if (heightSpecMode == MeasureSpec.EXACTLY) { + // match_parent/精确值 + heightSpecSize + } else { + // wrap_content + (2 * (maxDistance + radius)).toFloat().dp2px(context) + } + // 设置该view的宽高 + setMeasuredDimension(mWidth, mHeight) + } + + @SuppressLint("DrawAllocation") + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (isStart) { + for (i in spreadRadius.indices) { + var alpha = alphas[i] + spreadPaint.alpha = alpha + val width = spreadRadius[i] + //绘制扩散的圆 + canvas.drawCircle(centerX, centerY, (radius + width).toFloat(), spreadPaint) + //每次扩散圆半径递增,圆透明度递减 + if (alpha > 0 && width < (maxDistance + radius).toFloat().dp2px(context)) { + alpha = (alpha - distance).coerceAtLeast(0) + alphas[i] = alpha + spreadRadius[i] = width + distance + } + } + //当最外层扩散圆半径达到最大半径时添加新扩散圆 + val maxRadius = spreadRadius[spreadRadius.size - 1] + if (maxRadius > maxDistance) { + spreadRadius.add(0) + alphas.add(255) + } + //超过5个扩散圆,删除最先绘制的圆,即最外层的圆 + if (spreadRadius.size >= 8) { + alphas.removeAt(0) + spreadRadius.removeAt(0) + } + postInvalidateDelayed(animDuration.toLong()) + } + //中间的圆 + canvas.drawCircle(centerX, centerY, radius.toFloat(), centerPaint) + + //绘制文字 + val textRect = Rect() + textPaint.getTextBounds(text, 0, text.length, textRect) + val textWidth = textRect.width() + val textHeight = textRect.height() + //计算文字左下角坐标 + val textX = centerX - (textWidth shr 1) + val textY = centerY + 4 * textHeight / 3 //让文字靠下点,所以Y坐标大一点 + canvas.drawText(text, textX, textY, textPaint) + + //绘制图片 + val bitmap = BitmapFactory.decodeResource(resources, imageResourceId) //实例化 + + /** + * 方法一:此方法局限性大,对于图片要求较高,必须是尺寸合适的图片,否则会显示异常 + */ + val imageWidth = bitmap.width + val imageHeight = bitmap.height + //计算图片左上角坐标 + val imageX = centerX - (imageWidth shr 1) + val imageY = centerY - 4 * imageHeight / 3 //让图片靠上点,所以Y坐标小一点 + canvas.drawBitmap(bitmap, imageX, imageY, imagePaint) + /** + * 方法二:此方法是为将要绘制的图片划定显示区域,可以缓解图片尺寸导致显示异常问题 + */ +// RectF rectF = new RectF(centerX - (textWidth >> 2), centerY - (textHeight * 2), +// centerX + (textWidth >> 2), centerY - (textHeight >> 1)); +// Log.d(kTag, "height: " + rectF.height() + " ==== width: " + rectF.width()); +// canvas.drawBitmap(bitmap, null, rectF, imagePaint); + /** + * 方法三名:采用矩阵方式实现,这可彻底解决图片尺寸导致显示异常的问题 + * + * Matrix matrix=new Matrix(); + */ + } + + /** + * 启动动画 + */ + fun start() { + Log.d(kTag, "start: 启动动画") + text = "正在搜索" + isStart = true + postInvalidate() + } + + /** + * 停止动画 + */ + fun stop() { + Log.d(kTag, "stop: 停止动画") + text = "停止搜索" + isStart = false + postInvalidate() + } + + private var startListener: OnAnimationStartListener? = null + + interface OnAnimationStartListener { + fun onStart(view: WaterRippleView?) + } + + fun setOnAnimationStartListener(listener: OnAnimationStartListener?) { + startListener = listener + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml new file mode 100644 index 0000000..53f6af9 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml new file mode 100644 index 0000000..8b7466a --- /dev/null +++ b/app/src/main/res/layout/activity_add_device.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device_tab.xml b/app/src/main/res/layout/activity_add_device_tab.xml new file mode 100644 index 0000000..44c2c9c --- /dev/null +++ b/app/src/main/res/layout/activity_add_device_tab.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 1ae1069..e2916e4 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -15,7 +15,6 @@ + app:menu="@menu/main_bottom_nav_menu" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_add_device_nearby.xml b/app/src/main/res/layout/fragment_add_device_nearby.xml new file mode 100644 index 0000000..6731e69 --- /dev/null +++ b/app/src/main/res/layout/fragment_add_device_nearby.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_add_device_scan.xml b/app/src/main/res/layout/fragment_add_device_scan.xml new file mode 100644 index 0000000..ba1566f --- /dev/null +++ b/app/src/main/res/layout/fragment_add_device_scan.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + +