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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_device_type.xml b/app/src/main/res/layout/fragment_add_device_type.xml
new file mode 100644
index 0000000..b9762d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_device_type.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_device_type.xml b/app/src/main/res/layout/fragment_add_device_type.xml
new file mode 100644
index 0000000..b9762d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_device_type.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index f6102b8..a90f2df 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -34,7 +34,7 @@
android:gravity="center_vertical"
android:paddingHorizontal="@dimen/dp_10"
android:text="登录"
- android:textColor="#0094FF"
+ android:textColor="@color/mainThemeColor"
android:textSize="@dimen/sp_14" />
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_device_type.xml b/app/src/main/res/layout/fragment_add_device_type.xml
new file mode 100644
index 0000000..b9762d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_device_type.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index f6102b8..a90f2df 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -34,7 +34,7 @@
android:gravity="center_vertical"
android:paddingHorizontal="@dimen/dp_10"
android:text="登录"
- android:textColor="#0094FF"
+ android:textColor="@color/mainThemeColor"
android:textSize="@dimen/sp_14" />
diff --git a/app/src/main/res/layout/include_left_back_title.xml b/app/src/main/res/layout/include_left_back_title.xml
new file mode 100644
index 0000000..e07f189
--- /dev/null
+++ b/app/src/main/res/layout/include_left_back_title.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_device_type.xml b/app/src/main/res/layout/fragment_add_device_type.xml
new file mode 100644
index 0000000..b9762d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_device_type.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index f6102b8..a90f2df 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -34,7 +34,7 @@
android:gravity="center_vertical"
android:paddingHorizontal="@dimen/dp_10"
android:text="登录"
- android:textColor="#0094FF"
+ android:textColor="@color/mainThemeColor"
android:textSize="@dimen/sp_14" />
diff --git a/app/src/main/res/layout/include_left_back_title.xml b/app/src/main/res/layout/include_left_back_title.xml
new file mode 100644
index 0000000..e07f189
--- /dev/null
+++ b/app/src/main/res/layout/include_left_back_title.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_device_type_rv_l.xml b/app/src/main/res/layout/item_device_type_rv_l.xml
new file mode 100644
index 0000000..23150ce
--- /dev/null
+++ b/app/src/main/res/layout/item_device_type_rv_l.xml
@@ -0,0 +1,10 @@
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_device_type.xml b/app/src/main/res/layout/fragment_add_device_type.xml
new file mode 100644
index 0000000..b9762d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_device_type.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index f6102b8..a90f2df 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -34,7 +34,7 @@
android:gravity="center_vertical"
android:paddingHorizontal="@dimen/dp_10"
android:text="登录"
- android:textColor="#0094FF"
+ android:textColor="@color/mainThemeColor"
android:textSize="@dimen/sp_14" />
diff --git a/app/src/main/res/layout/include_left_back_title.xml b/app/src/main/res/layout/include_left_back_title.xml
new file mode 100644
index 0000000..e07f189
--- /dev/null
+++ b/app/src/main/res/layout/include_left_back_title.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_device_type_rv_l.xml b/app/src/main/res/layout/item_device_type_rv_l.xml
new file mode 100644
index 0000000..23150ce
--- /dev/null
+++ b/app/src/main/res/layout/item_device_type_rv_l.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/add_device_bottom_nav_menu.xml b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
new file mode 100644
index 0000000..e392fcf
--- /dev/null
+++ b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
@@ -0,0 +1,26 @@
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_device_type.xml b/app/src/main/res/layout/fragment_add_device_type.xml
new file mode 100644
index 0000000..b9762d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_device_type.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index f6102b8..a90f2df 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -34,7 +34,7 @@
android:gravity="center_vertical"
android:paddingHorizontal="@dimen/dp_10"
android:text="登录"
- android:textColor="#0094FF"
+ android:textColor="@color/mainThemeColor"
android:textSize="@dimen/sp_14" />
diff --git a/app/src/main/res/layout/include_left_back_title.xml b/app/src/main/res/layout/include_left_back_title.xml
new file mode 100644
index 0000000..e07f189
--- /dev/null
+++ b/app/src/main/res/layout/include_left_back_title.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_device_type_rv_l.xml b/app/src/main/res/layout/item_device_type_rv_l.xml
new file mode 100644
index 0000000..23150ce
--- /dev/null
+++ b/app/src/main/res/layout/item_device_type_rv_l.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/add_device_bottom_nav_menu.xml b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
new file mode 100644
index 0000000..e392fcf
--- /dev/null
+++ b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
@@ -0,0 +1,26 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
deleted file mode 100644
index 65c6f43..0000000
--- a/app/src/main/res/menu/bottom_nav_menu.xml
+++ /dev/null
@@ -1,45 +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/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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_device_type.xml b/app/src/main/res/layout/fragment_add_device_type.xml
new file mode 100644
index 0000000..b9762d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_device_type.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index f6102b8..a90f2df 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -34,7 +34,7 @@
android:gravity="center_vertical"
android:paddingHorizontal="@dimen/dp_10"
android:text="登录"
- android:textColor="#0094FF"
+ android:textColor="@color/mainThemeColor"
android:textSize="@dimen/sp_14" />
diff --git a/app/src/main/res/layout/include_left_back_title.xml b/app/src/main/res/layout/include_left_back_title.xml
new file mode 100644
index 0000000..e07f189
--- /dev/null
+++ b/app/src/main/res/layout/include_left_back_title.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_device_type_rv_l.xml b/app/src/main/res/layout/item_device_type_rv_l.xml
new file mode 100644
index 0000000..23150ce
--- /dev/null
+++ b/app/src/main/res/layout/item_device_type_rv_l.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/add_device_bottom_nav_menu.xml b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
new file mode 100644
index 0000000..e392fcf
--- /dev/null
+++ b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
@@ -0,0 +1,26 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
deleted file mode 100644
index 65c6f43..0000000
--- a/app/src/main/res/menu/bottom_nav_menu.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/main_bottom_nav_menu.xml b/app/src/main/res/menu/main_bottom_nav_menu.xml
new file mode 100644
index 0000000..65c6f43
--- /dev/null
+++ b/app/src/main/res/menu/main_bottom_nav_menu.xml
@@ -0,0 +1,45 @@
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_device_type.xml b/app/src/main/res/layout/fragment_add_device_type.xml
new file mode 100644
index 0000000..b9762d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_device_type.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index f6102b8..a90f2df 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -34,7 +34,7 @@
android:gravity="center_vertical"
android:paddingHorizontal="@dimen/dp_10"
android:text="登录"
- android:textColor="#0094FF"
+ android:textColor="@color/mainThemeColor"
android:textSize="@dimen/sp_14" />
diff --git a/app/src/main/res/layout/include_left_back_title.xml b/app/src/main/res/layout/include_left_back_title.xml
new file mode 100644
index 0000000..e07f189
--- /dev/null
+++ b/app/src/main/res/layout/include_left_back_title.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_device_type_rv_l.xml b/app/src/main/res/layout/item_device_type_rv_l.xml
new file mode 100644
index 0000000..23150ce
--- /dev/null
+++ b/app/src/main/res/layout/item_device_type_rv_l.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/add_device_bottom_nav_menu.xml b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
new file mode 100644
index 0000000..e392fcf
--- /dev/null
+++ b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
@@ -0,0 +1,26 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
deleted file mode 100644
index 65c6f43..0000000
--- a/app/src/main/res/menu/bottom_nav_menu.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/main_bottom_nav_menu.xml b/app/src/main/res/menu/main_bottom_nav_menu.xml
new file mode 100644
index 0000000..65c6f43
--- /dev/null
+++ b/app/src/main/res/menu/main_bottom_nav_menu.xml
@@ -0,0 +1,45 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-xxhdpi/hujiao.png b/app/src/main/res/mipmap-xxhdpi/hujiao.png
new file mode 100644
index 0000000..31496bc
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/hujiao.png
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/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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_device_type.xml b/app/src/main/res/layout/fragment_add_device_type.xml
new file mode 100644
index 0000000..b9762d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_device_type.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index f6102b8..a90f2df 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -34,7 +34,7 @@
android:gravity="center_vertical"
android:paddingHorizontal="@dimen/dp_10"
android:text="登录"
- android:textColor="#0094FF"
+ android:textColor="@color/mainThemeColor"
android:textSize="@dimen/sp_14" />
diff --git a/app/src/main/res/layout/include_left_back_title.xml b/app/src/main/res/layout/include_left_back_title.xml
new file mode 100644
index 0000000..e07f189
--- /dev/null
+++ b/app/src/main/res/layout/include_left_back_title.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_device_type_rv_l.xml b/app/src/main/res/layout/item_device_type_rv_l.xml
new file mode 100644
index 0000000..23150ce
--- /dev/null
+++ b/app/src/main/res/layout/item_device_type_rv_l.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/add_device_bottom_nav_menu.xml b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
new file mode 100644
index 0000000..e392fcf
--- /dev/null
+++ b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
@@ -0,0 +1,26 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
deleted file mode 100644
index 65c6f43..0000000
--- a/app/src/main/res/menu/bottom_nav_menu.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/main_bottom_nav_menu.xml b/app/src/main/res/menu/main_bottom_nav_menu.xml
new file mode 100644
index 0000000..65c6f43
--- /dev/null
+++ b/app/src/main/res/menu/main_bottom_nav_menu.xml
@@ -0,0 +1,45 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-xxhdpi/hujiao.png b/app/src/main/res/mipmap-xxhdpi/hujiao.png
new file mode 100644
index 0000000..31496bc
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/hujiao.png
Binary files differ
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..8d72869
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_device_type.xml b/app/src/main/res/layout/fragment_add_device_type.xml
new file mode 100644
index 0000000..b9762d4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_device_type.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index f6102b8..a90f2df 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -34,7 +34,7 @@
android:gravity="center_vertical"
android:paddingHorizontal="@dimen/dp_10"
android:text="登录"
- android:textColor="#0094FF"
+ android:textColor="@color/mainThemeColor"
android:textSize="@dimen/sp_14" />
diff --git a/app/src/main/res/layout/include_left_back_title.xml b/app/src/main/res/layout/include_left_back_title.xml
new file mode 100644
index 0000000..e07f189
--- /dev/null
+++ b/app/src/main/res/layout/include_left_back_title.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_device_type_rv_l.xml b/app/src/main/res/layout/item_device_type_rv_l.xml
new file mode 100644
index 0000000..23150ce
--- /dev/null
+++ b/app/src/main/res/layout/item_device_type_rv_l.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/add_device_bottom_nav_menu.xml b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
new file mode 100644
index 0000000..e392fcf
--- /dev/null
+++ b/app/src/main/res/menu/add_device_bottom_nav_menu.xml
@@ -0,0 +1,26 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
deleted file mode 100644
index 65c6f43..0000000
--- a/app/src/main/res/menu/bottom_nav_menu.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/main_bottom_nav_menu.xml b/app/src/main/res/menu/main_bottom_nav_menu.xml
new file mode 100644
index 0000000..65c6f43
--- /dev/null
+++ b/app/src/main/res/menu/main_bottom_nav_menu.xml
@@ -0,0 +1,45 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-xxhdpi/hujiao.png b/app/src/main/res/mipmap-xxhdpi/hujiao.png
new file mode 100644
index 0000000..31496bc
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/hujiao.png
Binary files differ
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..8d72869
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index ff2cac1..1a0a831 100644
--- a/build.gradle
+++ b/build.gradle
@@ -20,6 +20,8 @@
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
//依赖库
maven { url 'https://jitpack.io' }
+ //涂鸦仓库地址
+ maven { url "https://maven-other.tuya.com/repository/maven-commercial-releases/" }
google()
}
}