Commit 7b70e22c authored by vunguyencuong's avatar vunguyencuong

Invalid, no access

parents
# Tuya Defines
/src/main/assets/*.mist
google-services.json
/tysrc
/tyconfig
/tybuild
.DS_Store
/preconfig
# Built application files
*.apk
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
#IntelliJ IDEA
.idea/
*.iml
*.ipr
*.iws
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# gitignore
t_s.bmp
\ No newline at end of file
/build
\ No newline at end of file
plugins {
id 'com.android.application'
id 'kotlin-android'
}
if (new File("thingMapping.gradle").exists()) {
apply from : "thingMapping.gradle"
}
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
signingConfigs {
debug {
storeFile file('../app/key/tuya.jks')
storePassword '05052002'
keyAlias 'key_tuya'
keyPassword '05052002'
}
}
defaultConfig {
applicationId "com.example.smarthomecontrol"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.0"
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
manifestPlaceholders = [
TUYA_SMART_APPKEY: "${properties.getProperty("crvyt9phy3wr7md4jkcv")}",
TUYA_SMART_SECRET: "${properties.getProperty("qfuh5mp5k5ady4k3khgeywdppjhrgqvv")}",
]
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
signingConfig signingConfigs.debug
ndk {
abiFilters "armeabi-v7a", "arm64-v8a"
}
}
lintOptions {
abortOnError false
}
packagingOptions {
pickFirst 'lib/*/libc++_shared.so' // 多个aar存在此so,需要选择第一个
pickFirst 'lib/*/libgnustl_shared.so'//业务包需要
pickFirst 'lib/*/liblog.so'
pickFirst 'lib/*/libopenh264.so'
}
buildTypes {
debug {
debuggable true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
configurations.all {
exclude group: "com.thingclips.smart" ,module: 'thingsmart-modularCampAnno'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0-alpha04'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.alibaba:fastjson:1.1.67.android'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9'
implementation project(':base_res')
implementation project(":home")
implementation project(":device_config")
implementation project(":device_management")
implementation project(":ipc")
implementation project(":sweeper")
implementation 'cn.yipianfengye.android:zxing-library:2.2'
}
\ No newline at end of file
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.test007.demo",
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-debug.apk"
}
],
"elementType": "File"
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#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.**
#Okio
-keep class okio.** { *; }
-dontwarn okio.**
#Tuya
-keep class com.tuya.**{*;}
-dontwarn com.tuya.**
# Matter SDK
-keep class chip.** { *; }
-dontwarn chip.**
package com.tuya.appsdk.sample
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.tuya.appsdk.sample", appContext.packageName)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.tuya.appsdk.sample">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:name=".BaseApplication"
android:theme="@style/Theme.MaterialComponents.Light.NoActionBar"
tools:replace="android:allowBackup,android:supportsRtl">
<meta-data
android:name="THING_SMART_APPKEY"
android:value="crvyt9phy3wr7md4jkcv" />
<meta-data
android:name="THING_SMART_SECRET"
android:value="qfuh5mp5k5ady4k3khgeywdppjhrgqvv" />
<!-- Main -->
<activity
android:name=".main.MainSampleListActivity"
android:exported="false"
android:screenOrientation="portrait" />
<!-- User -->
<activity
android:name=".user.main.UserFuncActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".user.login.UserLoginActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".user.register.UserRegisterActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".user.resetPassword.UserResetPasswordActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".user.info.UserInfoActivity"
android:exported="false"
android:screenOrientation="portrait" />
</application>
</manifest>
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample
import android.app.Application
import com.tuya.smart.android.demo.camera.CameraUtils
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.optimus.sdk.ThingOptimusSdk
import com.uuzuche.lib_zxing.activity.ZXingLibrary
/**
* Base Application
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/6 11:50 AM
*/
class BaseApplication : Application() {
override fun onCreate() {
super.onCreate()
ThingHomeSdk.init(this)
ThingHomeSdk.setDebugMode(true)
ThingOptimusSdk.init(this)
ZXingLibrary.initDisplayOpinion(this)
CameraUtils.init(this)
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.main
import android.content.Intent
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.tuya.appsdk.sample.R
import com.tuya.appsdk.sample.device.config.main.DeviceConfigFuncWidget
import com.tuya.appsdk.sample.device.mgt.main.DeviceMgtFuncWidget
import com.tuya.appsdk.sample.home.main.HomeFuncWidget
import com.tuya.appsdk.sample.resource.HomeModel
import com.tuya.appsdk.sample.user.info.UserInfoActivity
import com.tuya.appsdk.sample.user.main.UserFuncActivity
import com.thingclips.smart.android.user.api.ILogoutCallback
import com.thingclips.smart.home.sdk.ThingHomeSdk
/**
* Sample Main List Page
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/8 5:41 PM
*/
class MainSampleListActivity : AppCompatActivity() {
lateinit var homeFuncWidget: HomeFuncWidget
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity_sample_list)
findViewById<TextView>(R.id.tvUserInfo).setOnClickListener {
// User Info
startActivity(Intent(this, UserInfoActivity::class.java))
}
findViewById<TextView>(R.id.tvLogout).setOnClickListener {
// Logout
ThingHomeSdk.getUserInstance().logout(object : ILogoutCallback {
override fun onSuccess() {
// Clear cache
HomeModel.INSTANCE.clear(this@MainSampleListActivity)
// Navigate to User Func Navigation Page
val intent = Intent(this@MainSampleListActivity, UserFuncActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
}
override fun onError(code: String?, error: String?) {
}
})
}
val llFunc: LinearLayout = findViewById(R.id.llFunc)
// Home Management
homeFuncWidget = HomeFuncWidget()
llFunc.addView(homeFuncWidget.render(this))
// Device Configuration Management
val deviceConfigFucWidget = DeviceConfigFuncWidget()
llFunc.addView(deviceConfigFucWidget.render(this))
// Device Management
val deviceMgtFuncWidget = DeviceMgtFuncWidget()
llFunc.addView(deviceMgtFuncWidget.render(this))
}
override fun onResume() {
super.onResume()
homeFuncWidget.refresh()
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.user.info
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.ListPopupWindow
import androidx.appcompat.widget.Toolbar
import com.tuya.appsdk.sample.R
import com.tuya.appsdk.sample.resource.HomeModel
import com.tuya.appsdk.sample.user.main.UserFuncActivity
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.sdk.ThingSdk
import com.thingclips.smart.sdk.api.IResultCallback
import com.thingclips.smart.sdk.enums.TempUnitEnum
import java.time.ZoneId
/**
* User Info Example
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/5 5:13 PM
*/
class UserInfoActivity : AppCompatActivity() {
lateinit var lat: String
lateinit var lon: String
lateinit var items: List<String>
@SuppressLint("WrongViewCast")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.user_activity_info)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
val user = ThingHomeSdk.getUserInstance().user
findViewById<TextView>(R.id.tvName).text = user?.nickName
findViewById<TextView>(R.id.tvPhone).text = user?.mobile
findViewById<TextView>(R.id.tvEmail).text = user?.email
findViewById<TextView>(R.id.tvCountryCode).text = user?.phoneCode
val tempUnit = findViewById<Button>(R.id.Temperature)
tempUnit.text = if (user?.tempUnit == 1) "°C" else "°F"
tempUnit.setOnClickListener {
val listPopupWindow = ListPopupWindow(
this,
null,
com.tuya.appsdk.sample.device.mgt.R.attr.listPopupWindowStyle
)
listPopupWindow.anchorView = tempUnit
val items = listOf("°C", "°F")
val adapter = ArrayAdapter(this, R.layout.device_mgt_item_dp_enum_popup_item, items)
listPopupWindow.setAdapter(adapter)
listPopupWindow.setOnItemClickListener { parent, view, position, id ->
ThingHomeSdk.getUserInstance().setTempUnit(
if (items[position] == "°C") TempUnitEnum.Celsius else TempUnitEnum.Fahrenheit, object : IResultCallback {
override fun onSuccess() {
tempUnit.text = items[position]
}
override fun onError(code: String?, error: String?) {
Toast.makeText(
this@UserInfoActivity,
" error->$error",
Toast.LENGTH_LONG
).show()
}
})
listPopupWindow.dismiss()
}
listPopupWindow.show()
}
findViewById<Button>(R.id.Updata).setOnClickListener {
val country = arrayOf(
this.getString(R.string.user_country_China),
this.getString(R.string.user_country_America),
this.getString(R.string.user_country_English),
this.getString(R.string.user_country_Australia),
this.getString(R.string.user_country_Japan),
this.getString(R.string.user_country_Egypt)
)
val builder = AlertDialog.Builder(this)
builder.setItems(
country,
DialogInterface.OnClickListener { dialog, which ->
when (which) {
1 -> {
lat = "116.20"
lon = "39.55"
}
2 -> {
lat = "-77.02"
lon = "39.91"
}
3 -> {
lat = "-0.05"
lon = "51.36"
}
4 -> {
lat = "139.46"
lon = "35.42"
}
5 -> {
lat = "31.14"
lon = "30.01"
}
}
ThingSdk.setLatAndLong(lat, lon)
Toast.makeText(
this@UserInfoActivity,
"success",
Toast.LENGTH_LONG
).show()
})
builder.create().show()
}
findViewById<Button>(R.id.deactive).setOnClickListener {
ThingHomeSdk.getUserInstance().cancelAccount(
object : IResultCallback {
override fun onSuccess() {
// Clear cache
HomeModel.INSTANCE.clear(this@UserInfoActivity)
// Navigate to User Func Navigation Page
val intent = Intent(this@UserInfoActivity, UserFuncActivity::class.java)
intent.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
}
override fun onError(code: String?, error: String?) {
Toast.makeText(
this@UserInfoActivity,
" error->$error",
Toast.LENGTH_LONG
).show()
}
})
}
val btTimeZone = findViewById<Button>(R.id.btTimeZone)
btTimeZone.text = user?.timezoneId
// Data can be issued by the cloud.
val listPopupWindow = ListPopupWindow(this, null, R.attr.listPopupWindowStyle)
listPopupWindow.anchorView = btTimeZone
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val availableZoneIds = ZoneId.getAvailableZoneIds()
items = availableZoneIds.toList()
} else {
items = arrayOf(
this.getString(R.string.user_time_America),
this.getString(R.string.user_time_Asia), this.getString(R.string.user_time_Etc)
).toList()
}
val arrayAdapter: ArrayAdapter<*> =
ArrayAdapter<Any?>(this, R.layout.user_activity_item_time_item, items)
listPopupWindow.setAdapter(arrayAdapter)
listPopupWindow.setOnItemClickListener { parent, view, position, id ->
val timezoneId = items[position]
ThingHomeSdk.getUserInstance().updateTimeZone(
timezoneId,
object : IResultCallback {
override fun onSuccess() {
Toast.makeText(
this@UserInfoActivity,
"success",
Toast.LENGTH_SHORT
).show()
btTimeZone.text = items[position]
}
override fun onError(code: String, error: String) {
Toast.makeText(
this@UserInfoActivity,
"error$error$timezoneId",
Toast.LENGTH_LONG
).show()
}
})
listPopupWindow.dismiss()
}
btTimeZone.setOnClickListener { listPopupWindow.show() }
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.user.login
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.tuya.appsdk.sample.R
import com.tuya.appsdk.sample.main.MainSampleListActivity
import com.tuya.appsdk.sample.user.resetPassword.UserResetPasswordActivity
import com.thingclips.smart.android.common.utils.ValidatorUtil
import com.thingclips.smart.android.user.api.ILoginCallback
import com.thingclips.smart.android.user.bean.User
import com.thingclips.smart.home.sdk.ThingHomeSdk
/**
* User Login Example
*
* @author qianqi <a href="mailto:developer@tuya.com">Contact me.</a>
* @since 2021/1/5 5:13 PM
*/
class UserLoginActivity : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.user_activity_login)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
findViewById<Button>(R.id.btnLogin).setOnClickListener(this)
findViewById<Button>(R.id.btnForget).setOnClickListener(this)
}
override fun onClick(v: View?) {
val strAccount = findViewById<EditText>(R.id.etAccount).text.toString()
val strCountryCode = findViewById<EditText>(R.id.etCountryCode).text.toString()
val strPassword = findViewById<EditText>(R.id.etPassword).text.toString()
v?.id?.let {
if (it == R.id.btnLogin) {
// Login with phone
val callback = object : ILoginCallback {
override fun onSuccess(user: User?) {
Toast.makeText(
this@UserLoginActivity,
"Login success",
Toast.LENGTH_LONG
).show()
startActivity(
Intent(
this@UserLoginActivity,
MainSampleListActivity::class.java
)
)
}
override fun onError(code: String?, error: String?) {
Toast.makeText(
this@UserLoginActivity,
"login error->$error",
Toast.LENGTH_LONG
).show()
}
}
if (ValidatorUtil.isEmail(strAccount)) {
ThingHomeSdk.getUserInstance()
.loginWithEmail(strCountryCode, strAccount, strPassword, callback)
} else {
ThingHomeSdk.getUserInstance()
.loginWithPhonePassword(strCountryCode, strAccount, strPassword, callback)
}
} else if (it == R.id.btnForget) {
startActivity(Intent(this, UserResetPasswordActivity::class.java))
}
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.user.main
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.tuya.appsdk.sample.R
import com.tuya.appsdk.sample.main.MainSampleListActivity
import com.tuya.appsdk.sample.user.login.UserLoginActivity
import com.tuya.appsdk.sample.user.register.UserRegisterActivity
import com.thingclips.smart.home.sdk.ThingHomeSdk
/**
* User Func Navigation Page
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/5 4:31 PM
*/
class UserFuncActivity : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// If login, then navigate to MainSampleList
if (ThingHomeSdk.getUserInstance().isLogin) {
startActivity(Intent(this, MainSampleListActivity::class.java))
finish()
}
setContentView(R.layout.user_activity_func)
findViewById<Button>(R.id.btnRegister).setOnClickListener(this)
findViewById<Button>(R.id.btnLogin).setOnClickListener(this)
try {
val pInfo: PackageInfo = packageManager.getPackageInfo(packageName, 0)
findViewById<TextView>(R.id.tvAppVersion).text =
String.format(getString(R.string.app_version_tips), pInfo.versionName)
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
}
override fun onClick(v: View?) {
v?.id?.let {
if (it == R.id.btnRegister) {
// Register
startActivity(Intent(this, UserRegisterActivity::class.java))
} else if (it == R.id.btnLogin) {
// Login
startActivity(Intent(this, UserLoginActivity::class.java))
}
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.user.register
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.tuya.appsdk.sample.R
import com.thingclips.smart.android.user.api.IRegisterCallback
import com.thingclips.smart.android.user.api.IValidateCallback
import com.thingclips.smart.android.user.bean.User
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.sdk.api.IResultCallback
import java.util.regex.Matcher
import java.util.regex.Pattern
/**
* User Register Example
*
* @author qianqi <a href="mailto:developer@tuya.com">Contact me.</a>
* @since 2021/1/5 5:13 PM
*/
class UserRegisterActivity : AppCompatActivity(), View.OnClickListener {
private val check =
"^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"
private val regex: Pattern = Pattern.compile(check)
private val mRegisterType = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.user_activity_register)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
findViewById<Button>(R.id.btnRegister).setOnClickListener(this)
findViewById<Button>(R.id.btnCode).setOnClickListener(this)
}
override fun onClick(v: View?) {
val strAccount = findViewById<EditText>(R.id.etAccount).text.toString()
val strCountryCode = findViewById<EditText>(R.id.etCountryCode).text.toString()
val strPassword = findViewById<EditText>(R.id.etPassword).text.toString()
val strCode = findViewById<EditText>(R.id.etCode).text.toString()
val matcher: Matcher = regex.matcher(strAccount)
val isEmail: Boolean = matcher.matches()
v?.id?.let {
if (it == R.id.btnRegister) {
val callback = object : IRegisterCallback {
override fun onSuccess(user: User?) {
Toast.makeText(
this@UserRegisterActivity,
"Register success",
Toast.LENGTH_LONG
).show()
}
override fun onError(code: String?, error: String?) {
Toast.makeText(
this@UserRegisterActivity,
"Register error->$error",
Toast.LENGTH_LONG
).show()
}
}
if (isEmail) {
// Register by email
ThingHomeSdk.getUserInstance().registerAccountWithEmail(
strCountryCode,
strAccount,
strPassword,
strCode,
callback
)
} else {
// Register by phone
ThingHomeSdk.getUserInstance().registerAccountWithPhone(
strCountryCode,
strAccount,
strPassword,
strCode,
callback
)
}
} else if (it == R.id.btnCode) {
// Get verification code code
ThingHomeSdk.getUserInstance().sendVerifyCodeWithUserName(
strAccount,
"",
strCountryCode,
mRegisterType,
object : IResultCallback {
override fun onSuccess() {
Toast.makeText(
this@UserRegisterActivity,
"Got validateCode",
Toast.LENGTH_LONG
).show()
}
override fun onError(code: String?, error: String?) {
Toast.makeText(
this@UserRegisterActivity,
"getValidateCode error->$error",
Toast.LENGTH_LONG
).show()
}
})
}
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.user.resetPassword
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.tuya.appsdk.sample.R
import com.thingclips.smart.android.user.api.IResetPasswordCallback
import com.thingclips.smart.android.user.api.IValidateCallback
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.sdk.api.IResultCallback
import java.util.regex.Matcher
import java.util.regex.Pattern
/**
* User Register Example
*
* @author qianqi <a href="mailto:developer@tuya.com">Contact me.</a>
* @since 2021/1/5 5:13 PM
*/
class UserResetPasswordActivity : AppCompatActivity(), View.OnClickListener {
private val check =
"^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"
private val regex: Pattern = Pattern.compile(check)
private val mResetPasswordType = 3
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.user_activity_reset_password)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
findViewById<Button>(R.id.btnReset).setOnClickListener(this)
findViewById<Button>(R.id.btnCode).setOnClickListener(this)
}
override fun onClick(v: View?) {
val strAccount = findViewById<EditText>(R.id.etAccount).text.toString()
val strCountryCode = findViewById<EditText>(R.id.etCountryCode).text.toString()
val strPassword = findViewById<EditText>(R.id.etPassword).text.toString()
val strCode = findViewById<EditText>(R.id.etCode).text.toString()
val matcher: Matcher = regex.matcher(strAccount)
val isEmail: Boolean = matcher.matches()
v?.id?.let {
if (it == R.id.btnReset) {
val callback = object : IResetPasswordCallback {
override fun onSuccess() {
Toast.makeText(
this@UserResetPasswordActivity,
"Register success",
Toast.LENGTH_LONG
).show()
}
override fun onError(code: String?, error: String?) {
Toast.makeText(
this@UserResetPasswordActivity,
"Register error->$error",
Toast.LENGTH_LONG
).show()
}
}
if (!isEmail) {
// Reset phone password
ThingHomeSdk.getUserInstance().resetPhonePassword(
strCountryCode,
strAccount,
strCode,
strPassword,
callback
)
} else {
// Reset email password
ThingHomeSdk.getUserInstance().resetEmailPassword(
strCountryCode,
strAccount,
strCode,
strPassword,
callback
)
}
} else if (it == R.id.btnCode) {
// Get verification code code
ThingHomeSdk.getUserInstance().sendVerifyCodeWithUserName(
strAccount,
"",
strCountryCode,
mResetPasswordType,
object : IResultCallback {
override fun onSuccess() {
Toast.makeText(
this@UserResetPasswordActivity,
"Got validateCode",
Toast.LENGTH_LONG
).show()
}
override fun onError(code: String?, error: String?) {
Toast.makeText(
this@UserResetPasswordActivity,
"getValidateCode error->$error",
Toast.LENGTH_LONG
).show()
}
})
}
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFEEEEEE">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingBottom="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:text="@string/main_logo"
android:textSize="30dp" />
</FrameLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="100dp">
<LinearLayout
android:id="@+id/llFunc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text="@string/user_management"
android:textSize="18dp" />
<TextView
android:id="@+id/tvUserInfo"
style="@style/item_func"
android:text="@string/user_info_title" />
<TextView
android:id="@+id/tvLogout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center"
android:text="@string/user_logout"
android:textColor="@color/red"
android:textSize="18dp" />
</LinearLayout>
</ScrollView>
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:layout_marginTop="50dp"
android:layout_marginLeft="20dp"
android:text="@string/user_guide_title" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:layout_gravity="bottom"
android:layout_marginBottom="40dp"
android:paddingBottom="20dp">
<Button
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="@string/user_login" />
<Button
android:id="@+id/btnRegister"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="@string/user_register" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="10dp"
android:id="@+id/tvAppVersion" />
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/user_info_title" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingBottom="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/user_name"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FF888888" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/user_phone_number"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<TextView
android:id="@+id/tvPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FF888888" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/user_email_address"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<TextView
android:id="@+id/tvEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FF888888" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/user_country_code"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<TextView
android:id="@+id/tvCountryCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FF888888" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/user_time_zone"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<Button
android:id="@+id/btTimeZone"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FF888888" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/user_unit_temperature"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<Button
android:id="@+id/Temperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FF888888" />
<Button
android:id="@+id/Updata"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="50dp"
android:text="@string/user_update_coordinate"
/>
<Button
android:id="@+id/deactive"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:text="@string/user_deactive_account" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceSubtitle1">
</TextView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/user_login" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_marginTop="20dp"
android:hint="@string/user_country_code"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etCountryCode"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/user_account_tips"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etAccount"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/user_password"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnForget"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="50dp"
android:text="@string/user_forget_password_tips" />
<Button
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:text="@string/user_login" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/user_register" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingBottom="20dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_marginTop="20dp"
android:hint="@string/user_country_code"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etCountryCode"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/user_account_tips"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etAccount"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/user_password"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/user_verification_code"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etCode"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnCode"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="@string/user_send_code" />
<Button
android:id="@+id/btnRegister"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="@string/user_register" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/user_reset_password" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingBottom="20dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_marginTop="20dp"
android:hint="@string/user_country_code"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etCountryCode"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/user_account_tips"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etAccount"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/user_password"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/user_verification_code"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etCode"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnCode"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="@string/user_send_code" />
<Button
android:id="@+id/btnReset"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="@string/user_reset_password" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Tuya SDK Kotlin</string>
<string name="user_guide_title">涂鸦SDK例子</string>
<string name="user_login">登录</string>
<string name="user_country_code">国家码</string>
<string name="user_account_tips">邮箱或手机号</string>
<string name="user_password">密码</string>
<string name="user_forget_password_tips">忘记密码?</string>
<string name="user_register">注册</string>
<string name="user_verification_code">验证码</string>
<string name="user_send_code">发送验证码</string>
<string name="user_reset_password">重置密码</string>
<string name="user_new_password">新密码</string>
<string name="user_info_title">用户信息</string>
<string name="user_name">用户名</string>
<string name="user_phone_number">手机</string>
<string name="user_email_address">邮箱</string>
<string name="user_time_zone">时区</string>
<string name="user_management">用户管理</string>
<string name="user_logout">登出</string>
<string name="app_version_tips">版本号: %s</string>
<string name="main_logo">涂鸦智能</string>
<string name="user_update_coordinate">更新坐标</string>
<string name="user_unit_temperature">温度单位</string>
<string name="user_deactive_account">停用账号</string>
<string name="user_country_China">中国</string>
<string name="user_country_America">美国</string>
<string name="user_country_English">英国</string>
<string name="user_country_Australia">澳大利亚</string>
<string name="user_country_Japan">日本</string>
<string name="user_country_Egypt">埃及</string>
<string name="user_time_Asia">亚洲/亚丁</string>
<string name="user_time_America">美国/库亚巴</string>
<string name="user_time_Etc">Etc/Gmt+9</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?><resources>
<string name="app_name">Tuya SDK Kotlin</string>
<string name="user_guide_title">Tuya SDK Sample</string>
<string name="user_login">Login</string>
<string name="user_country_code">Country Code</string>
<string name="user_account_tips">Email Address or Phone Number</string>
<string name="user_password">Password</string>
<string name="user_forget_password_tips">Forget Password?</string>
<string name="user_register">Register</string>
<string name="user_verification_code">Verification Code</string>
<string name="user_send_code">Send Verification Code</string>
<string name="user_reset_password">Reset Password</string>
<string name="user_new_password">Reset Password</string>
<string name="user_info_title">User Information</string>
<string name="user_name">User Name</string>
<string name="user_phone_number">Phone Number</string>
<string name="user_email_address">Email Address</string>
<string name="user_management">USER MANAGEMENT</string>
<string name="user_logout">Logout</string>
<string name="user_time_zone">Time Zone</string>
<string name="user_unit_temperature">Unit of Temperature</string>
<string name="app_version_tips">SDK Ver: %s</string>
<string name="main_logo">Tuya Smart</string>
<string name="user_update_coordinate">Updata Geographic Coordinate</string>
<string name="user_deactive_account">Deactive Account</string>
<string name="user_country_China">China</string>
<string name="user_country_America">America</string>
<string name="user_country_English">English</string>
<string name="user_country_Australia">Australia</string>
<string name="user_country_Japan">Japan</string>
<string name="user_country_Egypt">Egypt</string>
<string name="user_time_Asia">Asia/Aden</string>
<string name="user_time_America">America/Cuiaba</string>
<string name="user_time_Etc">Etc/Gmt+9"</string>
</resources>
package com.tuya.appsdk.sample
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
\ No newline at end of file
/build
\ No newline at end of file
plugins {
id 'com.android.library'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0-alpha04'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
api 'com.thingclips.smart:thingsmart:5.8.1'
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tuya.appsdk.sample.resource">
</manifest>
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.resource
import android.content.Context
/**
* Home Model Cache
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/20 3:01 PM
*/
enum class HomeModel {
INSTANCE;
companion object {
const val CURRENT_HOME_ID = "currentHomeId"
}
/**
* Set current home's homeId
*/
fun setCurrentHome(context: Context, homeId: Long) {
val sp = context.getSharedPreferences("HomeModel", Context.MODE_PRIVATE)
val editor = sp.edit()
editor.putLong(CURRENT_HOME_ID, homeId)
editor.apply()
}
/**
* Get current home's homeId
*/
fun getCurrentHome(context: Context): Long {
val sp = context.getSharedPreferences("HomeModel", Context.MODE_PRIVATE)
return sp.getLong(CURRENT_HOME_ID, 0)
}
/**
* check if current home set
*/
fun checkHomeId(context: Context): Boolean {
return HomeModel.INSTANCE.getCurrentHome(context) != 0L
}
fun clear(context: Context) {
val sp = context.getSharedPreferences("HomeModel", Context.MODE_PRIVATE)
val editor = sp.edit()
editor.clear()
editor.apply()
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="home_current_home_tips">请设置当前家庭</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="darkGrey">#a9a9a9</color>
<color name="white">#ffffff</color>
<color name="red">#C80F1F</color>
<color name="black">#FF000000</color>
<color name="green">#FF03DAC5</color>
<color name="gray">#888888</color>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="home_current_home_tips">Please set current home</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="item_func">
<item name="android:layout_height">48dp</item>
<item name="background">@color/white</item>
<item name="android:gravity">center_vertical</item>
<item name="android:paddingLeft">20dp</item>
<item name="android:textSize">18dp</item>
<item name="android:drawableRight">@drawable/ic_next</item>
<item name="android:paddingRight">10dp</item>
<item name="android:layout_width">match_parent</item>
<item name="android:textColor">@color/black</item>
</style>
<!--user information-->
<style name="user_information">
<item name="android:layout_height">48dp</item>
<item name="backgroundColor">#FFFFFFFF</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textSize">18dp</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:textColor">@color/black</item>
</style>
<!--line-->
<style name="line">
<item name="android:layout_height">1dp</item>
<item name="android:layout_width">match_parent</item>
<item name="android:background">@color/black</item>
<item name="android:layout_marginLeft">20dp</item>
<item name="android:layout_marginRight">10dp</item>
</style>
</resources>
\ No newline at end of file
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.6.20"
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
configurations.all {
exclude group: "com.thingclips.smart" ,module: 'thingsmart-modularCampAnno'
}
repositories {
maven { url 'https://maven-other.tuya.com/repository/maven-releases/' }
maven { url "https://maven-other.tuya.com/repository/maven-commercial-releases/" }
google()
jcenter()
maven { url 'https://jitpack.io' }
maven {url'https : //dl.bintray.com/jenly/maven ' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
\ No newline at end of file
/build
\ No newline at end of file
plugins {
id 'com.android.library'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0-alpha04'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.alibaba:fastjson:1.1.67.android'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9'
implementation project(':base_res')
implementation 'cn.yipianfengye.android:zxing-library:2.2'
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.example.networks
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.networks.test", appContext.packageName)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tuya.appsdk.sample.device.config">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
<application>
<activity
android:name=".ap.DeviceConfigAPActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".ez.DeviceConfigEZActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".ble.DeviceConfigBleActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".dual.DeviceConfigDualActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".zigbee.gateway.DeviceConfigZbGatewayActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".zigbee.sub.DeviceConfigZbSubDeviceActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".zigbee.sub.DeviceConfigChooseZbGatewayActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity android:name=".scan.DeviceConfigQrCodeDeviceActivity"
android:exported="false"
android:screenOrientation="portrait"/>
<activity android:name=".qrcode.QrCodeConfigActivity"
android:exported="false"
android:screenOrientation="portrait"/>
</application>
</manifest>
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.config.ap
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.resource.HomeModel
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.builder.ActivatorBuilder
import com.thingclips.smart.sdk.api.IThingActivator
import com.thingclips.smart.sdk.api.IThingActivatorGetToken
import com.thingclips.smart.sdk.api.IThingSmartActivatorListener
import com.thingclips.smart.sdk.bean.DeviceBean
import com.thingclips.smart.sdk.enums.ActivatorModelEnum
/**
* Device Configuration AP Mode Sample
*
* @author qianqi <a href="mailto:developer@tuya.com">Contact me.</a>
* @since 2021/1/5 5:13 PM
*/
class DeviceConfigAPActivity : AppCompatActivity(), View.OnClickListener {
companion object {
const val TAG = "DeviceConfigEZ"
}
lateinit var cpiLoading: CircularProgressIndicator
lateinit var btnSearch: Button
lateinit var mToken: String
private var mTuyaActivator: IThingActivator? = null
lateinit var strSsid: String
lateinit var strPassword: String
lateinit var mContentTv: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_config_activity)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
toolbar.title = getString(R.string.device_config_ap_title)
mContentTv=findViewById(R.id.content_tv)
mContentTv.text=getString(R.string.device_config_ap_description)
cpiLoading = findViewById(R.id.cpiLoading)
btnSearch = findViewById(R.id.btnSearch)
btnSearch.setOnClickListener(this)
}
override fun onClick(v: View?) {
strSsid = findViewById<EditText>(R.id.etSsid).text.toString()
strPassword = findViewById<EditText>(R.id.etPassword).text.toString()
v?.id?.let {
if (it == R.id.btnSearch) {
val homeId = HomeModel.INSTANCE.getCurrentHome(this)
// Get Network Configuration Token
ThingHomeSdk.getActivatorInstance().getActivatorToken(homeId,
object : IThingActivatorGetToken {
override fun onSuccess(token: String) {
mToken = token
// Start network configuration -- AP mode
onClickSetting()
//Stop configuration
// mTuyaActivator.stop()
//Exit the page to destroy some cache data and monitoring data.
// mTuyaActivator.onDestroy()
}
override fun onFailure(s: String, s1: String) {
}
})
}
}
}
override fun onPause() {
super.onPause()
}
override fun onRestart() {
super.onRestart()
//Show loading progress, disable btnSearch clickable
cpiLoading.visibility = View.VISIBLE
btnSearch.isClickable = false
cpiLoading.isIndeterminate = true
val builder = ActivatorBuilder()
.setSsid(strSsid)
.setContext(this)
.setPassword(strPassword)
.setActivatorModel(ActivatorModelEnum.THING_AP)
.setTimeOut(100)
.setToken(mToken)
.setListener(object : IThingSmartActivatorListener {
@Override
override fun onStep(step: String?, data: Any?) {
Log.i(TAG, "$step --> $data")
}
override fun onActiveSuccess(devResp: DeviceBean?) {
cpiLoading.visibility = View.GONE
Log.i(TAG, "Activate success")
Toast.makeText(
this@DeviceConfigAPActivity,
"Activate success",
Toast.LENGTH_LONG
).show()
finish()
}
override fun onError(
errorCode: String?,
errorMsg: String?
) {
cpiLoading.visibility = View.GONE
btnSearch.isClickable = true
Toast.makeText(
this@DeviceConfigAPActivity,
"Activate error-->$errorMsg",
Toast.LENGTH_LONG
).show()
}
}
)
mTuyaActivator =
ThingHomeSdk.getActivatorInstance().newActivator(builder)
//Start configuration
mTuyaActivator?.start()
}
/**
*
* wifi setting
*/
private fun onClickSetting() {
var wifiSettingsIntent = Intent("android.settings.WIFI_SETTINGS")
if (null == wifiSettingsIntent.resolveActivity(packageManager)) {
wifiSettingsIntent = Intent(Settings.ACTION_WIFI_SETTINGS)
}
if (null == wifiSettingsIntent.resolveActivity(packageManager)){
return
}
startActivity(wifiSettingsIntent)
}
override fun onDestroy() {
super.onDestroy()
mTuyaActivator?.onDestroy()
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.config.ble
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.resource.HomeModel
import com.thingclips.smart.android.ble.api.BleConfigType
import com.thingclips.smart.android.ble.api.IThingBleConfigListener
import com.thingclips.smart.android.ble.api.ScanType
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.sdk.bean.DeviceBean
/**
* Device Configuration Ble Low Energy Sample
*
* @author aiwen <a href="mailto:developer@tuya.com"/>
* @since 2/24/21 10:36 AM
*/
class DeviceConfigBleActivity : AppCompatActivity() {
companion object {
private const val TAG = "DeviceConfigBleActivity"
const val REQUEST_CODE = 1001
}
lateinit var cpiLoading: CircularProgressIndicator
lateinit var searchButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_config_info_hint_activity)
initToolbar()
initView()
checkPermission()
}
// You need to check permissions before using Bluetooth devices
private fun checkPermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
),
REQUEST_CODE
)
}
}
private fun initView() {
findViewById<TextView>(R.id.tv_hint_info).text = getString(R.string.device_config_ble_hint)
cpiLoading = findViewById(R.id.cpiLoading)
searchButton = findViewById(R.id.bt_search)
searchButton.setOnClickListener {
// Check Bluetooth is Opened
if (!ThingHomeSdk.getBleOperator().isBluetoothOpened) {
Toast.makeText(this, "Please turn on bluetooth", Toast.LENGTH_LONG).show()
return@setOnClickListener
}
scanSingleBleDevice()
}
}
private fun scanSingleBleDevice() {
val currentHomeId = HomeModel.INSTANCE.getCurrentHome(this)
setPbViewVisible(true)
// Scan Single Ble Device
ThingHomeSdk.getBleOperator().startLeScan(60 * 1000, ScanType.SINGLE) { bean ->
Log.i(TAG, "scanSingleBleDevice: deviceUUID=${bean.uuid}")
// Start configuration -- Single Ble Device
if (bean?.configType == BleConfigType.CONFIG_TYPE_SINGLE.type) {
ThingHomeSdk.getBleManager().startBleConfig(currentHomeId, bean.uuid, null,
object : IThingBleConfigListener {
override fun onSuccess(bean: DeviceBean?) {
setPbViewVisible(false)
Toast.makeText(
this@DeviceConfigBleActivity,
"Config Success",
Toast.LENGTH_LONG
).show()
finish()
}
override fun onFail(code: String?, msg: String?, handle: Any?) {
setPbViewVisible(false)
Toast.makeText(
this@DeviceConfigBleActivity,
"Config Failed",
Toast.LENGTH_LONG
).show()
}
})
}
}
}
private fun initToolbar() {
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
toolbar.setTitle(R.string.device_config_ble_title)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
REQUEST_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "onRequestPermissionsResult: agree")
} else {
finish()
Log.e(TAG, "onRequestPermissionsResult: denied")
}
}
}
}
private fun setPbViewVisible(isShow: Boolean) {
cpiLoading.visibility = if (isShow) View.VISIBLE else View.GONE
searchButton.isEnabled = !isShow
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.config.dual
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.textfield.TextInputEditText
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.device.config.ble.DeviceConfigBleActivity
import com.tuya.appsdk.sample.resource.HomeModel
import com.thingclips.smart.android.ble.api.BleConfigType
import com.thingclips.smart.android.ble.api.IThingBleConfigListener
import com.thingclips.smart.android.ble.api.ScanType
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.sdk.api.IThingActivatorGetToken
import com.thingclips.smart.sdk.bean.DeviceBean
/**
* Device Configuration Dual Device Sample
*
* @author aiwen <a href="mailto:developer@tuya.com"/>
* @since 2/24/21 11:08 AM
*/
class DeviceConfigDualActivity : AppCompatActivity() {
companion object {
private const val TAG = "DeviceConfigDualMode"
const val REQUEST_CODE = 1002
}
private val homeId: Long by lazy {
HomeModel.INSTANCE.getCurrentHome(this)
}
lateinit var etSsid: TextInputEditText
lateinit var etPassword: TextInputEditText
lateinit var btSearch: Button
lateinit var cpiLoading: CircularProgressIndicator
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_config_activity)
initToolbar()
initView()
checkPermission()
}
private fun initView() {
etSsid = findViewById(R.id.etSsid)
etPassword = findViewById(R.id.etPassword)
btSearch = findViewById(R.id.btnSearch)
cpiLoading = findViewById(R.id.cpiLoading)
btSearch.setOnClickListener {
val strSsid = etSsid.text.toString()
val strPassword = etPassword.text.toString()
if (strSsid.isEmpty() || strPassword.isEmpty()) {
return@setOnClickListener
}
if (!ThingHomeSdk.getBleOperator().isBluetoothOpened) {
Toast.makeText(this, "Please turn on bluetooth", Toast.LENGTH_LONG).show()
return@setOnClickListener
}
scanDualBleDevice()
}
}
private fun initToolbar() {
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
toolbar.setTitle(R.string.device_config_dual_title)
}
// You need to check permissions before using Bluetooth devices
private fun checkPermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
),
DeviceConfigBleActivity.REQUEST_CODE
)
}
}
private fun setPbViewVisible(isShow: Boolean) {
cpiLoading.visibility = if (isShow) View.VISIBLE else View.GONE
btSearch.isEnabled = !isShow
}
// Scan Ble Device
private fun scanDualBleDevice() {
setPbViewVisible(true)
ThingHomeSdk.getBleOperator().startLeScan(60 * 1000, ScanType.SINGLE) { bean ->
Log.i(TAG, "scanDualBleDevice: beanType=${bean.configType},uuid=${bean.uuid}")
// Start configuration -- Dual Device
if (bean.configType == BleConfigType.CONFIG_TYPE_WIFI.type) {
// Get Network Configuration Token
ThingHomeSdk.getActivatorInstance().getActivatorToken(homeId,
object : IThingActivatorGetToken {
override fun onSuccess(token: String) {
// Start configuration -- Dual Ble Device
val param = mutableMapOf<String, String>()
param["ssid"] = etSsid.text.toString()
param["password"] = etPassword.text.toString()
param["token"] = token
ThingHomeSdk.getBleManager()
.startBleConfig(homeId, bean.uuid, param as Map<String, String>,
object : IThingBleConfigListener {
override fun onSuccess(bean: DeviceBean?) {
setPbViewVisible(false)
Toast.makeText(
this@DeviceConfigDualActivity,
"Config Success",
Toast.LENGTH_LONG
).show()
finish()
}
override fun onFail(
code: String?,
msg: String?,
handle: Any?
) {
setPbViewVisible(false)
finish()
Toast.makeText(
this@DeviceConfigDualActivity,
"Config Failed",
Toast.LENGTH_LONG
).show()
}
})
}
override fun onFailure(errorCode: String?, errorMsg: String?) {
setPbViewVisible(false)
Toast.makeText(
this@DeviceConfigDualActivity,
"Error->$errorMsg",
Toast.LENGTH_LONG
).show()
}
})
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
REQUEST_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "onRequestPermissionsResult: agree")
} else {
finish()
Log.e(TAG, "onRequestPermissionsResult: denied")
}
}
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.config.ez
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.resource.HomeModel
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.builder.ActivatorBuilder
import com.thingclips.smart.sdk.api.IThingActivatorGetToken
import com.thingclips.smart.sdk.api.IThingSmartActivatorListener
import com.thingclips.smart.sdk.bean.DeviceBean
import com.thingclips.smart.sdk.enums.ActivatorModelEnum
/**
* Device Configuration EZ Mode Sample
*
* @author qianqi <a href="mailto:developer@tuya.com">Contact me.</a>
* @since 2021/1/5 5:13 PM
*/
class DeviceConfigEZActivity : AppCompatActivity(), View.OnClickListener {
companion object {
const val TAG = "DeviceConfigEZ"
}
lateinit var cpiLoading: CircularProgressIndicator
lateinit var btnSearch: Button
lateinit var mContentTv: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_config_activity)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
toolbar.title = getString(R.string.device_config_ez_title)
mContentTv=findViewById(R.id.content_tv)
mContentTv.text=getString(R.string.device_config_ez_description)
cpiLoading = findViewById(R.id.cpiLoading)
btnSearch = findViewById(R.id.btnSearch)
btnSearch.setOnClickListener(this)
}
override fun onClick(v: View?) {
val strSsid = findViewById<EditText>(R.id.etSsid).text.toString()
val strPassword = findViewById<EditText>(R.id.etPassword).text.toString()
v?.id?.let { it ->
if (it == R.id.btnSearch) {
val homeId = HomeModel.INSTANCE.getCurrentHome(this)
// Get Network Configuration Token
ThingHomeSdk.getActivatorInstance().getActivatorToken(homeId,
object : IThingActivatorGetToken {
override fun onSuccess(token: String) {
// Start network configuration -- EZ mode
val builder = ActivatorBuilder()
.setSsid(strSsid)
.setContext(v.context)
.setPassword(strPassword)
.setActivatorModel(ActivatorModelEnum.THING_EZ)
.setTimeOut(100)
.setToken(token)
.setListener(object : IThingSmartActivatorListener {
@Override
override fun onStep(step: String?, data: Any?) {
Log.i(TAG, "$step --> $data")
}
override fun onActiveSuccess(devResp: DeviceBean?) {
cpiLoading.visibility = View.GONE
Log.i(TAG, "Activate success")
Toast.makeText(
this@DeviceConfigEZActivity,
"Activate success",
Toast.LENGTH_LONG
).show()
finish()
}
override fun onError(
errorCode: String?,
errorMsg: String?
) {
cpiLoading.visibility = View.GONE
btnSearch.isClickable = true
Toast.makeText(
this@DeviceConfigEZActivity,
"Activate error-->$errorMsg",
Toast.LENGTH_LONG
).show()
}
}
)
val mTuyaActivator =
ThingHomeSdk.getActivatorInstance().newMultiActivator(builder)
//Start configuration
mTuyaActivator.start()
//Show loading progress, disable btnSearch clickable
cpiLoading.visibility = View.VISIBLE
btnSearch.isClickable = false
//Stop configuration
// mTuyaActivator.stop()
//Exit the page to destroy some cache data and monitoring data.
// mTuyaActivator.onDestroy()
}
override fun onFailure(s: String, s1: String) {}
})
}
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.config.main
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import android.widget.Toast
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.device.config.ap.DeviceConfigAPActivity
import com.tuya.appsdk.sample.device.config.ble.DeviceConfigBleActivity
import com.tuya.appsdk.sample.device.config.dual.DeviceConfigDualActivity
import com.tuya.appsdk.sample.device.config.ez.DeviceConfigEZActivity
import com.tuya.appsdk.sample.device.config.scan.DeviceConfigQrCodeDeviceActivity
import com.tuya.appsdk.sample.device.config.qrcode.QrCodeConfigActivity
import com.tuya.appsdk.sample.device.config.zigbee.gateway.DeviceConfigZbGatewayActivity
import com.tuya.appsdk.sample.device.config.zigbee.sub.DeviceConfigZbSubDeviceActivity
import com.tuya.appsdk.sample.resource.HomeModel
/**
* Device configuration func Widget
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/9 5:06 PM
*/
class DeviceConfigFuncWidget {
lateinit var mContext: Context
fun render(context: Context): View {
val rootView =
LayoutInflater.from(context).inflate(R.layout.device_config_view_func, null, false)
mContext = context
initView(rootView)
return rootView
}
private fun initView(rootView: View) {
// EZ Mode
rootView.findViewById<TextView>(R.id.tvEzMode).setOnClickListener {
if (!HomeModel.INSTANCE.checkHomeId(mContext)) {
Toast.makeText(
mContext,
mContext.getString(R.string.home_current_home_tips),
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
it.context.startActivity(Intent(it.context, DeviceConfigEZActivity::class.java))
}
// AP Mode
rootView.findViewById<TextView>(R.id.tvApMode).setOnClickListener {
if (!HomeModel.INSTANCE.checkHomeId(mContext)) {
Toast.makeText(
mContext,
mContext.getString(R.string.home_current_home_tips),
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
it.context.startActivity(Intent(it.context, DeviceConfigAPActivity::class.java))
}
// Ble Low Energy
rootView.findViewById<TextView>(R.id.tv_ble).setOnClickListener {
if (!HomeModel.INSTANCE.checkHomeId(mContext)) {
Toast.makeText(
mContext,
mContext.getString(R.string.home_current_home_tips),
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
it.context.startActivity(Intent(it.context, DeviceConfigBleActivity::class.java))
}
// Dual Mode
rootView.findViewById<TextView>(R.id.tv_dual_mode).setOnClickListener {
if (!HomeModel.INSTANCE.checkHomeId(mContext)) {
Toast.makeText(
mContext,
mContext.getString(R.string.home_current_home_tips),
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
it.context.startActivity(Intent(it.context, DeviceConfigDualActivity::class.java))
}
// ZigBee Gateway
rootView.findViewById<TextView>(R.id.tv_zigBee_gateway).setOnClickListener {
if (!HomeModel.INSTANCE.checkHomeId(mContext)) {
Toast.makeText(
mContext,
mContext.getString(R.string.home_current_home_tips),
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
it.context.startActivity(Intent(it.context, DeviceConfigZbGatewayActivity::class.java))
}
// ZigBee Sub Device
rootView.findViewById<TextView>(R.id.tv_zigBee_subDevice).setOnClickListener {
if (!HomeModel.INSTANCE.checkHomeId(mContext)) {
Toast.makeText(
mContext,
mContext.getString(R.string.home_current_home_tips),
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
it.context.startActivity(
Intent(
it.context,
DeviceConfigZbSubDeviceActivity::class.java
)
)
}
// Scan Qr Code
rootView.findViewById<TextView>(R.id.tv_qrcode_subDevice).setOnClickListener {
if (!HomeModel.INSTANCE.checkHomeId(mContext)) {
Toast.makeText(
mContext,
mContext.getString(R.string.home_current_home_tips),
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
it.context.startActivity(
Intent(
it.context,
DeviceConfigQrCodeDeviceActivity::class.java
)
)
}
// Qr Code
rootView.findViewById<TextView>(R.id.tv_qr_code).setOnClickListener{
if (!HomeModel.INSTANCE.checkHomeId(mContext)) {
Toast.makeText(
rootView.context,
rootView.context.getString(R.string.home_current_home_tips),
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
it.context.startActivity(
Intent(
it.context,
QrCodeConfigActivity::class.java
)
)
}
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.config.qrcode
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.google.zxing.WriterException
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.device.config.util.qrcode.DensityUtil
import com.tuya.appsdk.sample.device.config.util.qrcode.QRCodeUtil
import com.tuya.appsdk.sample.resource.HomeModel
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.builder.ThingCameraActivatorBuilder
import com.thingclips.smart.sdk.api.IThingActivatorGetToken
import com.thingclips.smart.sdk.api.IThingCameraDevActivator
import com.thingclips.smart.sdk.api.IThingSmartCameraActivatorListener
import com.thingclips.smart.sdk.bean.DeviceBean
/**
* TODO feature
*二维码配网 QrCode Config
* @author hou qing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/28 2:52 下午
*/
class QrCodeConfigActivity : AppCompatActivity(),View.OnClickListener{
private var wifiSSId = ""
private var wifiPwd = ""
private val mtoken = ""
private lateinit var mIvQr: ImageView
private lateinit var mLlInputWifi: LinearLayout
private lateinit var mEtInputWifiSSid: EditText
private lateinit var mEtInputWifiPwd: EditText
private lateinit var mBtnSave: Button
private var mTuyaActivator: IThingCameraDevActivator? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_config_qr_code)
val toolbar = findViewById<Toolbar>(R.id.toolbar_view)
setSupportActionBar(toolbar)
toolbar.setNavigationOnClickListener { finish() }
mLlInputWifi = findViewById(R.id.ll_input_wifi)
mEtInputWifiSSid = findViewById(R.id.et_wifi_ssid)
mEtInputWifiPwd = findViewById(R.id.et_wifi_pwd)
mBtnSave = findViewById(R.id.btn_save)
mBtnSave.setOnClickListener(this)
mIvQr = findViewById(R.id.iv_qrcode)
}
private fun hideKeyboard(v: View) {
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
}
override fun onClick(v: View) {
if (v.id == R.id.btn_save) {
wifiSSId = mEtInputWifiSSid.text.toString()
wifiPwd = mEtInputWifiPwd.text.toString()
val homeId: Long = HomeModel.INSTANCE.getCurrentHome(this)
// Get Network Configuration Token
ThingHomeSdk.getActivatorInstance().getActivatorToken(homeId,
object : IThingActivatorGetToken {
override fun onSuccess(token: String) {
//Create and show qrCode
val builder = ThingCameraActivatorBuilder()
.setToken(token)
.setPassword(wifiPwd)
.setTimeOut(100)
.setContext(this@QrCodeConfigActivity)
.setSsid(wifiSSId)
.setListener(object : IThingSmartCameraActivatorListener {
override fun onQRCodeSuccess(qrcodeUrl: String) {
val bitmap: Bitmap
try {
val widthAndHeight: Int = DensityUtil.getScreenDispalyWidth(this@QrCodeConfigActivity) - DensityUtil.dip2px(this@QrCodeConfigActivity, 50f)
bitmap = QRCodeUtil.createQRCode(qrcodeUrl, widthAndHeight)
runOnUiThread {
mIvQr.setImageBitmap(bitmap)
mLlInputWifi.visibility = View.GONE
mIvQr.visibility = View.VISIBLE
}
} catch (e: WriterException) {
e.printStackTrace()
}
}
override fun onError(errorCode: String, errorMsg: String) {
}
override fun onActiveSuccess(devResp: DeviceBean) {
Toast.makeText(this@QrCodeConfigActivity, "config success!", Toast.LENGTH_LONG).show()
}
})
mTuyaActivator = ThingHomeSdk.getActivatorInstance().newCameraDevActivator(builder)
mTuyaActivator?.createQRCode()
mTuyaActivator?.start()
}
override fun onFailure(errorCode: String, errorMsg: String) {}
})
hideKeyboard(v)
}
}
override fun onDestroy() {
super.onDestroy()
mTuyaActivator?.stop()
mTuyaActivator?.onDestroy()
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.config.scan
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.android.material.appbar.MaterialToolbar
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.resource.HomeModel
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.builder.ThingQRCodeActivatorBuilder
import com.thingclips.smart.sdk.api.IThingDataCallback
import com.thingclips.smart.sdk.api.IThingSmartActivatorListener
import com.thingclips.smart.sdk.bean.DeviceBean
import com.uuzuche.lib_zxing.activity.CaptureActivity
import com.uuzuche.lib_zxing.activity.CodeUtils
import org.json.JSONObject
import java.util.*
/**
* Qr Code
*
* @author yueguang [](mailto:developer@tuya.com)
* @since 2021/3/11 3:13 PM
*/
class DeviceConfigQrCodeDeviceActivity : AppCompatActivity(), View.OnClickListener {
private var topAppBar: MaterialToolbar? = null
private var bt_search: Button? = null
private var mUuid: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_config_info_hint_activity)
initView()
}
private fun initView() {
topAppBar = findViewById<View>(R.id.topAppBar) as MaterialToolbar
topAppBar!!.setNavigationOnClickListener { finish() }
topAppBar!!.title = getString(R.string.device_qr_code_service_title)
bt_search = findViewById<View>(R.id.bt_search) as Button
bt_search!!.setOnClickListener(this)
bt_search!!.setText(R.string.device_qr_code_service_title)
}
override fun onClick(v: View) {
if (v.id == R.id.bt_search) {
startQrCode()
}
}
private fun startQrCode() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CODE_SCAN
)
return
}
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
REQUEST_CODE_SCAN
)
return
}
val intent = Intent(this, CaptureActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_SCAN)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_SCAN) {
if (null != data) {
val bundle = data.extras ?: return
if (bundle.getInt(CodeUtils.RESULT_TYPE) == CodeUtils.RESULT_SUCCESS) {
val result = bundle.getString(CodeUtils.RESULT_STRING)
Toast.makeText(this, "result:$result", Toast.LENGTH_LONG).show()
deviceQrCode(result)
} else if (bundle.getInt(CodeUtils.RESULT_TYPE) == CodeUtils.RESULT_FAILED) {
Toast.makeText(this, "解析二维码失败", Toast.LENGTH_LONG).show()
}
}
}
}
private fun deviceQrCode(result: String?) {
val postData = HashMap<String, Any?>()
postData["code"] = result
ThingHomeSdk.getRequestInstance().requestWithApiNameWithoutSession(
"tuya.m.qrcode.parse",
"4.0",
postData,
String::class.java,
object : IThingDataCallback<String> {
override fun onSuccess(result: String) {
initQrCode(result)
}
override fun onError(errorCode: String, errorMessage: String) {}
}
)
}
private fun initQrCode(result: String) {
val homeId = HomeModel.INSTANCE.getCurrentHome(this)
val obj = JSONObject(result)
val actionObj = obj.optJSONObject("actionData")
if (null != actionObj) {
mUuid = actionObj.optString("uuid")
val builder = ThingQRCodeActivatorBuilder()
.setUuid(mUuid)
.setHomeId(homeId)
.setContext(this)
.setTimeOut(100)
.setListener(object : IThingSmartActivatorListener {
override fun onError(errorCode: String, errorMsg: String) {}
override fun onActiveSuccess(devResp: DeviceBean) {
Toast.makeText(
this@DeviceConfigQrCodeDeviceActivity,
"ActiveSuccess",
Toast.LENGTH_LONG
).show()
}
override fun onStep(step: String, data: Any) {}
})
val iTuyaActivator = ThingHomeSdk.getActivatorInstance().newQRCodeDevActivator(builder)
iTuyaActivator.start()
}
}
companion object {
private const val REQUEST_CODE_SCAN = 1
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.config.util.qrcode
import android.content.Context
import android.view.WindowManager
/**
* TODO feature
*
* @author hou qing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/28 6:19 下午
*/
class DensityUtil {
companion object{
fun dip2px(context: Context, dpValue: Float): Int {
val scale = context.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
fun dip2pxF(context: Context, dpValue: Float): Float {
val scale = context.resources.displayMetrics.density
return dpValue * scale + 0.5f
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
fun px2dip(context: Context, pxValue: Float): Int {
val scale = context.resources.displayMetrics.density
return (pxValue / scale + 0.5f).toInt()
}
/**
* 将px值转换为sp值,保证文字大小不变
*
* @param pxValue
* @param context
* (DisplayMetrics类中属性scaledDensity)
* @return
*/
fun px2sp(context: Context, pxValue: Float): Int {
val fontScale = context.resources.displayMetrics.scaledDensity
return (pxValue / fontScale + 0.5f).toInt()
}
/**
* 将sp值转换为px值,保证文字大小不变
*
* @param spValue
* @param context
* (DisplayMetrics类中属性scaledDensity)
* @return
*/
fun sp2px(context: Context, spValue: Float): Int {
val fontScale = context.resources.displayMetrics.scaledDensity
return (spValue * fontScale + 0.5f).toInt()
}
fun getScreenDispalyWidth(context: Context): Int {
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val width = wm.defaultDisplay.width //手机屏幕的宽度
val height = wm.defaultDisplay.height //手机屏幕的高度
return if (width > height) height else width
}
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.config.util.qrcode
import android.graphics.Bitmap
import android.graphics.Color
import android.text.TextUtils
import androidx.annotation.ColorInt
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.MultiFormatWriter
import com.google.zxing.WriterException
import com.google.zxing.qrcode.QRCodeWriter
import java.util.*
import kotlin.collections.HashMap
/**
* TODO feature
*
* @author hou qing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/28 2:54 下午
*/
class QRCodeUtil {
/**
* Create a QR code bitmap
* @param content content(Support Chinese)
* @param width width, unit px
* @param height height, unit px
*/
fun createQRCodeBitmap(content: String?, width: Int, height: Int): Bitmap? {
return createQRCodeBitmap(
content,
width,
height,
"UTF-8",
"H",
"2",
Color.BLACK,
Color.WHITE
)
}
/**
* Create QR code bitmap (support custom configuration and custom style)
*
* @param content content(Support Chinese)
* @param width width, unit px
* @param height height, unit px
* @param character_set Character set/character transcoding format. When passing null, zxing source code uses "ISO-8859-1" by default
* @param error_correction Fault tolerance level. When passing null, zxing source code uses "L" by default
* @param margin Blank margin (can be modified, requirement: integer and >=0), when passing null, zxing source code uses "4" by default
* @param color_black Custom color value of black color block
* @param color_white Custom color value of white color block
* @return
*/
private fun createQRCodeBitmap(
content: String?, width: Int, height: Int,
character_set: String?, error_correction: String?, margin: String?,
@ColorInt color_black: Int, @ColorInt color_white: Int): Bitmap? {
/** 1.Parameter legality judgment */
if (TextUtils.isEmpty(content)) { // The string content is blank
return null
}
if (width < 0 || height < 0) { // Both width and height need to be >=0
return null
}
try {
/** 2.Set the QR code related configuration and generate BitMatrix objects */
val hints = Hashtable<EncodeHintType, String?>()
if (!TextUtils.isEmpty(character_set)) {
hints[EncodeHintType.CHARACTER_SET] =
character_set // Character transcoding format setting
}
if (!TextUtils.isEmpty(error_correction)) {
hints[EncodeHintType.ERROR_CORRECTION] =
error_correction // Fault tolerance level setting
}
if (!TextUtils.isEmpty(margin)) {
hints[EncodeHintType.MARGIN] = margin // Margin settings
}
val bitMatrix =
QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints)
/** 3.Create a pixel array and assign color values to the array elements according to the BitMatrix object */
val pixels = IntArray(width * height)
for (y in 0 until height) {
for (x in 0 until width) {
if (bitMatrix[x, y]) {
pixels[y * width + x] = color_black // Black color block pixel settings
} else {
pixels[y * width + x] = color_white // White color block pixel setting
}
}
}
/** 4.Create a Bitmap object, set the color value of each pixel of the Bitmap according to the pixel array, and then return the Bitmap object */
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
return bitmap
} catch (e: WriterException) {
e.printStackTrace()
}
return null
}
companion object{
@Throws(WriterException::class)
fun createQRCode(url: String?, widthAndHeight: Int): Bitmap {
val hints: Hashtable<EncodeHintType, Any> = Hashtable()
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
hints[EncodeHintType.MARGIN] = 0
val matrix = MultiFormatWriter().encode(url,
BarcodeFormat.QR_CODE, widthAndHeight, widthAndHeight, hints
)
val width = matrix.width
val height = matrix.height
val pixels = IntArray(width * height)
for (y in 0 until height) {
for (x in 0 until width) {
if (matrix[x, y]) {
pixels[y * width + x] = Color.BLACK //0xff000000
}
}
}
val bitmap = Bitmap.createBitmap(
width, height,
Bitmap.Config.ARGB_8888
)
bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
return bitmap
}
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.config.util.sp
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import kotlin.reflect.KProperty
/**
* SP Util
* @author aiwen <a href="mailto:developer@tuya.com"/>
* @since 2/25/21 10:15 AM
*/
class Preference<T>(context: Context, val name: String, private val default: T) {
private val prefs: SharedPreferences by lazy {
context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
}
companion object {
private const val SP_NAME = "GATEWAY_LIST"
}
/**
* Clear all data
*/
fun clearPreference() {
prefs.edit().clear().apply()
}
/**
* Clear data by key
*/
fun clearPreference(key: String) {
prefs.edit().remove(key).apply()
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return getSharedPreferences(name, default)
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
putSharedPreferences(name, value)
}
@SuppressLint("CommitPrefEdits")
private fun putSharedPreferences(name: String, value: T) = with(prefs.edit()) {
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
else -> throw IllegalArgumentException("This type can not be saved into Preferences")
}.apply()
}
@Suppress("UNCHECKED_CAST")
private fun getSharedPreferences(name: String, default: T): T = with(prefs) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
is String -> getString(name, default) ?: ""
else -> throw IllegalArgumentException("This type can be saved into Preferences")
}
return res as T
}
}
package com.tuya.appsdk.sample.device.config.zigbee.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.device.config.util.sp.Preference
import com.tuya.appsdk.sample.device.config.zigbee.sub.DeviceConfigZbSubDeviceActivity
import com.thingclips.smart.sdk.bean.DeviceBean
/**
* Zigbee Gateway List
*
* @author aiwen <a href="mailto:developer@tuya.com"/>
* @since 2/25/21 10:18 AM
*/
class ZigBeeGatewayListAdapter(context: Context) :
RecyclerView.Adapter<ZigBeeGatewayListAdapter.ViewHolder>() {
var data: ArrayList<DeviceBean> = arrayListOf()
var currentGatewayId: String by Preference(
context,
DeviceConfigZbSubDeviceActivity.CURRENT_GATEWAY_ID,
""
)
var currentGatewayName: String by Preference(
context,
DeviceConfigZbSubDeviceActivity.CURRENT_GATEWAY_NAME,
""
)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflate = LayoutInflater.from(parent.context)
.inflate(R.layout.device_zb_gateway_list, parent, false)
val viewHolder = ViewHolder(inflate)
viewHolder.itemView.setOnClickListener {
val deviceBean = data[viewHolder.adapterPosition]
currentGatewayId = deviceBean.devId
currentGatewayName = deviceBean.name
notifyDataSetChanged()
}
return viewHolder
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
data[position].let {
holder.itemName.text = it.name
// Switch ZigBee Gateway
if (currentGatewayId == it.devId) {
holder.itemIcon.setImageResource(R.drawable.ic_check)
} else {
holder.itemIcon.setImageResource(0)
}
}
}
override fun getItemCount(): Int {
return data.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val itemName: TextView = itemView.findViewById(R.id.tvName)
val itemIcon: ImageView = itemView.findViewById(R.id.ivIcon)
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.config.zigbee.gateway
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.resource.HomeModel
import com.thingclips.smart.android.hardware.bean.HgwBean
import com.thingclips.smart.home.sdk.ThingHomeSdk.getActivatorInstance
import com.thingclips.smart.home.sdk.builder.ThingGwActivatorBuilder
import com.thingclips.smart.sdk.api.IThingActivatorGetToken
import com.thingclips.smart.sdk.api.IThingSmartActivatorListener
import com.thingclips.smart.sdk.bean.DeviceBean
/**
* Device Configuration ZigBee Gateway Sample
*
* @author aiwen <a href="mailto:developer@tuya.com"/>
* @since 2/24/21 11:12 AM
*/
class DeviceConfigZbGatewayActivity : AppCompatActivity() {
companion object {
private const val TAG = "DeviceConfigZbGateway"
}
private val homeId: Long by lazy {
HomeModel.INSTANCE.getCurrentHome(this@DeviceConfigZbGatewayActivity)
}
lateinit var cpiLoading: CircularProgressIndicator
lateinit var searchButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_config_info_hint_activity)
initToolbar()
initView()
}
private fun initView() {
findViewById<TextView>(R.id.tv_hint_info).text =
getString(R.string.device_config_zb_gateway_hint)
cpiLoading = findViewById(R.id.cpiLoading)
searchButton = findViewById(R.id.bt_search)
searchButton.setOnClickListener {
searchGatewayDevice()
}
}
private fun initToolbar() {
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
toolbar.setTitle(R.string.device_config_zb_gateway_title)
}
// Search ZigBee Gateway Device
private fun searchGatewayDevice() {
setPbViewVisible(true)
val newSearcher = getActivatorInstance().newThingGwActivator().newSearcher()
newSearcher.registerGwSearchListener {
getNetworkConfigToken(it)
}
}
// Get Network Configuration Token
private fun getNetworkConfigToken(hgwBean: HgwBean) {
Log.i(TAG, "getNetworkConfigToken: homeId=${homeId}")
Log.i(TAG, "getNetworkConfigToken: GwId->${hgwBean.getGwId()}")
getActivatorInstance().getActivatorToken(homeId,
object : IThingActivatorGetToken {
override fun onSuccess(token: String) {
Log.i(TAG, "getNetworkConfigToken: onSuccess->${token}")
startNetworkConfig(token, hgwBean)
}
override fun onFailure(errorCode: String?, errorMsg: String?) {
setPbViewVisible(false)
Toast.makeText(
this@DeviceConfigZbGatewayActivity,
"Error->$errorMsg",
Toast.LENGTH_LONG
).show()
}
})
}
// Start network configuration -- ZigBee Gateway
private fun startNetworkConfig(token: String, hgwBean: HgwBean) {
val activatorBuilder = getActivatorInstance().newGwActivator(
ThingGwActivatorBuilder()
.setContext(this@DeviceConfigZbGatewayActivity)
.setTimeOut(100)
.setToken(token)
.setHgwBean(hgwBean)
.setListener(object : IThingSmartActivatorListener {
override fun onError(errorCode: String?, errorMsg: String?) {
Log.i(TAG, "Activate error->$errorMsg")
setPbViewVisible(false)
Toast.makeText(
this@DeviceConfigZbGatewayActivity,
"Activate Error",
Toast.LENGTH_LONG
).show()
}
override fun onActiveSuccess(devResp: DeviceBean?) {
Log.i(TAG, "Activate success")
setPbViewVisible(false)
Toast.makeText(
this@DeviceConfigZbGatewayActivity,
"Activate success",
Toast.LENGTH_LONG
).show()
finish()
}
override fun onStep(step: String?, data: Any?) {
Log.i(TAG, "onStep: step->$step")
}
})
)
//Start configuration
activatorBuilder.start()
// Stop configuration
// mTuyaActivator.stop()
// Exit the page to destroy some cache data and monitoring data.
// mTuyaActivator.onDestroy()
}
private fun setPbViewVisible(isShow: Boolean) {
cpiLoading.visibility = if (isShow) View.VISIBLE else View.GONE
searchButton.isEnabled = !isShow
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.config.zigbee.sub
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.device.config.zigbee.adapter.ZigBeeGatewayListAdapter
import com.tuya.appsdk.sample.resource.HomeModel
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.bean.HomeBean
import com.thingclips.smart.home.sdk.callback.IThingHomeResultCallback
import com.thingclips.smart.sdk.bean.DeviceBean
/**
* Choose Gateway
*
* @author aiwen <a href="mailto:developer@tuya.com"/>
* @since 2/25/21 9:45 AM
*/
class DeviceConfigChooseZbGatewayActivity : AppCompatActivity() {
lateinit var adapter: ZigBeeGatewayListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_config_zb_choose_gateway_activity)
initToolbar()
initView()
}
private fun initView() {
val rvList = findViewById<RecyclerView>(R.id.rvList)
adapter = ZigBeeGatewayListAdapter(this)
// Set List
val linearLayoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
rvList.layoutManager = linearLayoutManager
rvList.adapter = adapter
getZigBeeGatewayList()
}
// Get ZigBee Gateway List
private fun getZigBeeGatewayList() {
val currentHomeId = HomeModel.INSTANCE.getCurrentHome(this)
ThingHomeSdk.newHomeInstance(currentHomeId).getHomeDetail(object : IThingHomeResultCallback {
override fun onSuccess(bean: HomeBean?) {
val deviceList = bean?.deviceList as ArrayList<DeviceBean>
val zigBeeGatewayList = deviceList.filter {
it.isZigBeeWifi
}
adapter.data = zigBeeGatewayList as ArrayList<DeviceBean>
adapter.notifyDataSetChanged()
}
override fun onError(errorCode: String?, errorMsg: String?) {
Toast.makeText(
this@DeviceConfigChooseZbGatewayActivity,
"Error->$errorMsg",
Toast.LENGTH_LONG
).show()
}
})
}
// init Toolbar
private fun initToolbar() {
val toolbar = findViewById<Toolbar>(R.id.topAppBar)
toolbar.setNavigationOnClickListener {
finish()
}
toolbar.setTitle(R.string.device_config_choose_gateway_title)
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.config.zigbee.sub
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.tuya.appsdk.sample.device.config.R
import com.tuya.appsdk.sample.device.config.util.sp.Preference
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.builder.ThingGwSubDevActivatorBuilder
import com.thingclips.smart.sdk.api.IThingSmartActivatorListener
import com.thingclips.smart.sdk.bean.DeviceBean
/**
* Device Configuration ZigBee sub device Mode Sample
*
* @author aiwen <a href="mailto:developer@tuya.com"/>
* @since 2/24/21 11:13 AM
*/
class DeviceConfigZbSubDeviceActivity : AppCompatActivity() {
companion object {
private const val TAG = "DeviceConfigZbSubDevice"
const val CURRENT_GATEWAY_NAME = "current_gateway_name"
const val CURRENT_GATEWAY_ID = "current_gateway_id"
const val REQUEST_CODE = 1003
}
lateinit var btSearch: Button
lateinit var tvCurrentGateway: TextView
lateinit var cpiLoading: CircularProgressIndicator
var currentGatewayName: String by Preference(this, CURRENT_GATEWAY_NAME, "")
var currentGatewayId: String by Preference(this, CURRENT_GATEWAY_ID, "")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_config_zb_sub_device_activity)
initToolbar()
initView()
}
private fun initView() {
// init gatewayName and gatewayId
currentGatewayName = ""
currentGatewayId = ""
btSearch = findViewById(R.id.btnSearch)
cpiLoading = findViewById(R.id.cpiLoading)
tvCurrentGateway = findViewById(R.id.tv_current_gateway_name)
// choose zigBee gateway
findViewById<TextView>(R.id.tv_current_zb_gateway).setOnClickListener {
startActivityForResult(
Intent(this, DeviceConfigChooseZbGatewayActivity::class.java),
REQUEST_CODE
)
}
btSearch.setOnClickListener {
subDeviceConfiguration()
}
}
// Sub-device Configuration
private fun subDeviceConfiguration() {
if (tvCurrentGateway.text.isEmpty()) {
Toast.makeText(this, "Please select gateway first", Toast.LENGTH_LONG).show()
return
}
Log.i(TAG, "subDeviceConfiguration: currentGatewayId=${currentGatewayId}")
setPbViewVisible(true)
val builder = ThingGwSubDevActivatorBuilder()
.setDevId(currentGatewayId)
.setTimeOut(100)
.setListener(object : IThingSmartActivatorListener {
override fun onError(errorCode: String?, errorMsg: String?) {
setPbViewVisible(false)
Toast.makeText(
this@DeviceConfigZbSubDeviceActivity,
"Active Error->$errorMsg",
Toast.LENGTH_LONG
).show()
}
override fun onActiveSuccess(devResp: DeviceBean?) {
setPbViewVisible(false)
Toast.makeText(
this@DeviceConfigZbSubDeviceActivity,
"Active Success",
Toast.LENGTH_LONG
).show()
finish()
}
override fun onStep(step: String?, data: Any?) {
Log.i(TAG, "onStep: step->$step")
}
})
val tuyaGWSubActivator = ThingHomeSdk.getActivatorInstance().newGwSubDevActivator(builder)
// Start network configuration
tuyaGWSubActivator.start()
// Stop network configuration
// tuyaGWSubActivator.stop();
// Destroy
// tuyaGWSubActivator.onDestroy()
}
private fun initToolbar() {
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
toolbar.setTitle(R.string.device_config_zb_sub_device_title)
}
private fun setPbViewVisible(isShow: Boolean) {
cpiLoading.visibility = if (isShow) View.VISIBLE else View.GONE
btSearch.isEnabled = !isShow
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE) {
tvCurrentGateway.text = currentGatewayName
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/device_config_ez_title" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_marginTop="20dp"
android:hint="@string/device_config_wifi_ssid"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etSsid"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/device_config_wifi_password"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/content_tv"
android:layout_width="match_parent"
android:textSize="18sp"
android:layout_marginTop="25dp"
android:layout_height="wrap_content"
android:text="@string/device_config_sp_mode_hint" />
<Button
android:id="@+id/btnSearch"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="20dp"
android:text="@string/device_config_search" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/cpiLoading"
android:layout_gravity="center_horizontal"
android:layout_marginTop="80dp"
android:layout_width="wrap_content"
android:visibility="gone"
android:layout_height="wrap_content"
android:indeterminate="true" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:id="@+id/tv_hint_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textSize="20sp"
android:padding="10dp" />
<Button
android:id="@+id/bt_search"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="50dp"
android:text="@string/device_config_search" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/cpiLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="80dp"
android:indeterminate="true"
android:visibility="gone" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.tuya.appsdk.sample.device.config.qrcode.QrCodeConfigActivity"
android:background="@color/white">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_view"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/device_config_qr_code_title" />
</com.google.android.material.appbar.AppBarLayout>
<ImageView
android:id="@+id/iv_qrcode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="30dp"
android:visibility="gone" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/ll_input_wifi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_marginTop="20dp"
android:hint="@string/hint_wifi_name"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_wifi_ssid"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/hint_wifi_pwd"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_wifi_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="10dp"
android:text="@string/btn_save" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/device_config_module_title"
android:paddingLeft="20dp"
android:layout_marginTop="60dp"
android:textSize="18dp" />
<TextView
style="@style/item_func"
android:id="@+id/tvEzMode"
android:text="@string/device_config_ez_title" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:background="#FF888888" />
<TextView
style="@style/item_func"
android:id="@+id/tvApMode"
android:text="@string/device_config_ap_title" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:background="#FF888888" />
<TextView
style="@style/item_func"
android:id="@+id/tv_ble"
android:text="@string/device_config_ble_title" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:background="#FF888888" />
<TextView
style="@style/item_func"
android:id="@+id/tv_dual_mode"
android:text="@string/device_config_dual_title" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:background="#FF888888" />
<TextView
style="@style/item_func"
android:id="@+id/tv_zigBee_gateway"
android:text="@string/device_config_zb_gateway_title" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:background="#FF888888" />
<TextView
style="@style/item_func"
android:id="@+id/tv_zigBee_subDevice"
android:text="@string/device_config_zb_sub_device_title" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:background="#FF888888" />
<TextView
style="@style/item_func"
android:id="@+id/tv_qrcode_subDevice"
android:text="@string/device_qr_code_service_title" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:background="#FF888888" />
<TextView
style="@style/item_func"
android:id="@+id/tv_qr_code"
android:text="@string/device_config_qr_code_title"/>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/device_config_ez_title" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_current_zb_gateway"
style="@style/item_func"
android:text="@string/device_config_zb_gateway_title" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_current_gateway_name"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="40dp" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:background="#FF888888" />
<Button
android:id="@+id/btnSearch"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="50dp"
android:text="@string/device_config_search" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/cpiLoading"
android:layout_gravity="center_horizontal"
android:layout_marginTop="80dp"
android:layout_width="wrap_content"
android:visibility="gone"
android:layout_height="wrap_content"
android:indeterminate="true" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:id="@+id/tvName" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ivIcon"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="20dp" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_config_module_title">设备配网</string>
<string name="device_config_ez_title">EZ 模式</string>
<string name="device_config_ez_description">EZ 配网是指 App 发送包含 Wi-Fi 名
和 Wi-Fi 密码的 UDP 广播包或者组播包,
设备的 Wi-Fi 芯片可以接收到该 UDP 包,通过特定的 UDP 组织形式就可以解密出 Wi-Fi
名和 Wi-Fi 密码,接着设备配置 Wi-Fi,然后设备可上网连接云,
将设备信息注册到云端的过程</string>
<string name="device_config_ap_title">AP 模式</string>
<string name="device_config_ap_description">AP模式,也称为热点模式。 手机连接智能设备的热点,双方建立了Socket连接,
通过约定的端口交换数据。\n让设备处于配对模式,然后将手机的Wi-Fi切换到设备的热点。
输入您希望设备连接的Wi-Fi的SSID和密码,然后点击“搜索”。</string>
<string name="device_config_ble_title">低功耗蓝牙模式</string>
<string name="device_config_dual_title">双模模式</string>
<string name="device_config_zb_gateway_title">Zigbee 网关</string>
<string name="device_config_zb_sub_device_title">Zigbee 子设备</string>
<string name="device_config_choose_gateway_title">选择网关</string>
<string name="device_qr_code_service_title">扫描二维码</string>
<string name="device_config_qr_code_title">二维码配网</string>
<string name="device_config_wifi_ssid">WIFI 名称</string>
<string name="device_config_wifi_password">WIFI 密码</string>
<string name="device_config_search">搜索</string>
<string name="btn_save">保存</string>
<string name="hint_wifi_name">Wifi 名称</string>
<string name="hint_wifi_pwd">Wifi 密码</string>
<string name="device_config_ble_hint">单点蓝牙设备是指通过蓝牙与手机终端进行一对一连接的设备,
例如蓝牙手环,蓝牙耳机,蓝牙音响等。每个设备可以同时连接到手机 ,
目前每个手机终端同时蓝牙连接数限制为6到7。\n\n点击搜索并与蓝牙设备配对。</string>
<string name="device_config_zb_gateway_hint">让Zigbee网关连接到路由器,并确保手机和网关在同一局域网中,然后点按搜索。 </string>
<string name="device_config_sp_mode_hint">AP模式,也称为热点模式。 手机连接智能设备的热点,双方建立了Socket连接,通过约定的端口交换数据。\n让设备处于配对模式,然后将手机的Wi-Fi切换到设备的热点。 输入您希望设备连接的Wi-Fi的SSID和密码,然后点击“搜索”。</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_config_module_title">DEVICE NETWORK CONFIGURATION</string>
<string name="device_config_ez_title">EZ Mode</string>
<string name="device_config_ez_description">EZ Mode ,it means that the App sends a UDP broadcast
packet or multicast packet containing Wi-Fi name and Wi-Fi password, the Wi-Fi
chip of the device can receive the UDP packet and decrypt the Wi-Fi name and Wi-Fi
password through a specific form of UDP organization, then the device configures Wi-Fi,
and then the device can go online to connect to the cloud, the process of registering the
device information to the cloud
Translated with www.DeepL.com/Translator (free version)</string>
<string name="device_config_ap_title">AP Mode</string>
<string name="device_config_ap_description">AP Mode, also known as hotspot mode.
The mobile phone connects the smart device’s hotspot, and the two parties establish
a Socket connection to exchange data through the agreed port.Let the device in pairing mode,
\nthen switch iPhone’s Wi-Fi to the device’s hotspot. Type in the SSID and password
of which Wi-Fi you want the device connect to, then tap Search.</string>
<string name="device_config_ble_title">Bluetooth Low Energy</string>
<string name="device_config_dual_title">Dual Mode</string>
<string name="device_config_zb_gateway_title">Zigbee Gateway</string>
<string name="device_config_zb_sub_device_title">Zigbee Subdevice</string>
<string name="device_config_choose_gateway_title">Choose Gateway</string>
<string name="device_qr_code_service_title">Scan QR Code</string>
<string name="device_config_qr_code_title">QR Code</string>
<string name="device_config_wifi_ssid">WIFI SSID</string>
<string name="device_config_wifi_password">WIFI Password</string>
<string name="device_config_search">Search</string>
<string name="btn_save">Save</string>
<string name="hint_wifi_name">Wifi SSID</string>
<string name="hint_wifi_pwd">Wifi password</string>
<string name="device_config_ble_hint">Single-point Bluetooth devices are devices that have a one-to-one connection with a cell phone terminal via Bluetooth, such as Bluetooth bracelets,
Bluetooth headsets, Bluetooth speakers, etc.
Each device can be connected to a cell phone at the same time,
and the number of simultaneous Bluetooth connections per cell phone terminal is currently
limited to 6 to 7.\n\nTap Search to pair BLE device.</string>
<string name="device_config_zb_gateway_hint">Let the Zigbee Gateway connect to the router,
and make sure iPhone and the gateway are in the same local area network, then tap search. </string>
<string name="device_config_sp_mode_hint">AP Mode, also known as hotspot mode. The mobile phone connects the smart device’s hotspot, and the two parties establish a Socket connection to exchange data through the agreed port.Let the device in pairing mode, \nthen switch iPhone’s Wi-Fi to the device’s hotspot. Type in the SSID and password of which Wi-Fi you want the device connect to, then tap Search.</string>
</resources>
\ No newline at end of file
package com.example.networks
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
\ No newline at end of file
/build
\ No newline at end of file
plugins {
id 'com.android.library'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0-alpha04'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.alibaba:fastjson:1.1.67.android'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9'
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation project(':base_res')
implementation project(':ipc')
implementation project(':sweeper')
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.example.management
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.management.test", appContext.packageName)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tuya.appsdk.sample.device.mgt">
<application>
<activity
android:name=".list.activity.DeviceMgtListActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".control.activity.DeviceMgtControlActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".list.activity.DeviceSubZigbeeActivity"
android:exported="false"
android:screenOrientation="portrait" />
</application>
</manifest>
\ No newline at end of file
{
"icon-dp_add":"e600",
"icon-dp_reduce":"e601",
"icon-dp_temp":"e602",
"icon-dp_time3":"e603",
"icon-dp_mode":"e604",
"icon-dp_loop":"e605",
"icon-dp_filter":"e606",
"icon-dp_smile":"e607",
"icon-dp_sleep":"e608",
"icon-dp_dust":"e609",
"icon-eco":"e60a",
"icon-eoc2":"e60b",
"icon-dp_tvoc":"e60c",
"icon-dp_smart":"e60d",
"icon-dp_o2":"e60e",
"icon-dp_time2":"e60f",
"icon-dp_light2":"e610",
"icon-dp_shake":"e611",
"icon-dp_home2":"e612",
"icon-dp_direction":"e613",
"icon-dp_power3":"e614",
"icon-dp_wash":"e615",
"icon-dp_location":"e616",
"icon-dp_battery":"e617",
"icon-dp_wind":"e618",
"icon-dp_fresh":"e619",
"icon-dp_wet2":"e61a",
"icon-dp_half":"e61b",
"icon-dp_sun":"e61c",
"icon-dp_bag":"e61d",
"icon-dp_wet":"e61e",
"icon-dp_water":"e61f",
"icon-dp_lock":"e620",
"icon-dp_time":"e621",
"icon-dp_light":"e622",
"icon-dp_0":"e623",
"icon-dp_1":"e624",
"icon-dp_2":"e625",
"icon-dp_3":"e626",
"icon-dp_4":"e627",
"icon-dp_5":"e628",
"icon-dp_6":"e629",
"icon-dp_7":"e62a",
"icon-dp_8":"e62b",
"icon-dp_9":"e62c",
"icon-dp_c":"e62d",
"icon-dp_f":"e62e",
"icon-dp_power2":"e62f",
"icon-dp_power":"e630",
"icon-dp_right":"e631",
"icon-dp_dot":"e632",
"icon-dp_play":"e633",
"icon-dp_pause":"e634",
"icon-dp_down":"e635",
"icon-dp_anti-clockwise":"e636",
"icon-dp_clockwise":"e637",
"icon-dp_up":"e638",
"icon-dp_lightning":"e63a",
"icon-dp_voice":"e639",
"icon-dp_down1":"e63b",
"icon-dp_cloud":"e63c",
"icon-dp_upload":"e63d",
"icon-dp_doc":"e63e",
"icon-dp_curve":"e63f",
"icon-dp_heart":"e640",
"icon-dp_email":"e641",
"icon-dp_circle":"e642",
"icon-dp_plus":"e643",
"icon-dp_home":"e644",
"icon-dp_magnifier":"e645",
"icon-dp_fly":"e646",
"icon-dp_i":"e647",
"icon-dp_down2":"e648",
"icon-dp_book":"e649",
"icon-dp_rabbish":"e64a",
"icon-dp_hill":"e64b",
"icon-dp_compass":"e64c",
"icon-dp_gift":"e64d",
"icon-dp_eye":"e64e",
"icon-dp_notice":"e64f",
"icon-dp_camera":"e650",
"icon-dp_puzzle":"e651",
"icon-dp_ratio":"e652",
"icon-dp_block":"e653",
"icon-dp_chat":"e654",
"icon-dp_list2":"e655",
"icon-dp_bottle":"e656",
"icon-dp_doc2":"e657",
"icon-dp_what":"e658",
"icon-dp_warming":"e659",
"icon-dp_updown":"e65a",
"icon-dp_tool":"e65b",
"icon-dp_tag":"e65c",
"icon-dp_shield":"e65d",
"icon-dp_box2":"e65e",
"icon-dp_box":"e65f",
"icon-dp_money":"e660",
"icon-dp_house":"e661",
"icon-dp_mic":"e662",
"icon-dp_calendar":"e663",
"icon-dp_list":"e664",
"icon-dp_flag":"e665",
"icon-dp_flower":"e666",
"icon-baifeng":"e8c1",
"icon-chushi":"e8c2",
"icon-chengzhong":"e8c3",
"icon-chongnai":"e8c4",
"icon-chushi1":"e8c5",
"icon-chushi2":"e8c6",
"icon-cuowu":"e8c7",
"icon-deng":"e8c8",
"icon-dianliang":"e8c9",
"icon-fengli":"e8ca",
"icon-geren":"e8cb",
"icon-gaodiyin":"e8cc",
"icon-gongneng":"e8cd",
"icon-guanjia":"e8ce",
"icon-huoyan":"e8cf",
"icon-qita":"e8d0",
"icon-jiare":"e8d1",
"icon-liangdu":"e8d2",
"icon-jiare1":"e8d3",
"icon-shangsheng":"e8d4",
"icon-shouji":"e8d5",
"icon-shoucang":"e8d6",
"icon-shezhi":"e8d7",
"icon-qiangli":"e8d8",
"icon-tianjia":"e8d9",
"icon-shoushimima":"e8da",
"icon-shenghua":"e8db",
"icon-shuibeng":"e8dc",
"icon-tongji":"e8dd",
"icon-tongji1":"e8de",
"icon-yinshui":"e8df",
"icon-yinliang":"e8e0",
"icon-yanse":"e8e1",
"icon-wendu":"e8e2",
"icon-yundong":"e8e3",
"icon-yunhang":"e8e4",
"icon-zanting":"e8e5",
"icon-zhengque":"e8e6",
"icon-zhuangtai":"e8e7",
"icon-zhileng":"e8e8",
"icon-zhileng1":"e8e9",
"icon-chushuang":"e8ea",
"icon-zanting1":"e8eb",
"icon-tongji2":"e8ec",
"icon-baifeng1":"e8ed",
"icon-set":"e931",
"icon-yueliang":"e932",
"icon-xue":"e933",
"icon-fangzi":"e934",
"icon-wendu1":"e935",
"icon-taiyang":"e936",
"icon-fangzi1":"e937",
"icon-icon-percent":"e938",
"icon-p6":"e9aa",
"icon-p7":"e9ab",
"icon-p8":"e9ac",
"icon-p9":"e9ad",
"icon-p10":"e9ae",
"icon-p11":"e9af",
"icon-p12":"e9b0",
"icon-p13":"e9b1",
"icon-p14":"e9b2",
"icon-p15":"e9b3",
"icon-ziyouchengxu":"e9b4",
"icon-zhouchengxu":"e9b5",
"icon-minus":"e9ba",
"icon-plus":"e9bb",
"icon-a_fan_low":"e9bd",
"icon-a_fan_auto":"e9be",
"icon-a_fan_med":"e9bf",
"icon-a_fan_high":"e9c0",
"icon-a_function_celsius":"e9c1",
"icon-a_function_fahrenhei":"e9c2",
"icon-a_function_hs":"e9c3",
"icon-a_function_eco":"e9c4",
"icon-a_function_filter":"e9c5",
"icon-a_function_sleep":"e9c6",
"icon-a_function_pump":"e9c7",
"icon-a_function_vs":"e9c8",
"icon-a_function_turbo":"e9c9",
"icon-a_mode_basement":"e9ca",
"icon-a_mode_continuous":"e9cb",
"icon-a_mode_cool":"e9cc",
"icon-a_mode_fan":"e9cd",
"icon-a_mode_clothes":"e9ce",
"icon-a_mode_feel":"e9cf",
"icon-a_mode_heat":"e9d0",
"icon-a_mode_livingroom":"e9d1",
"icon-a_mode_dry":"e9d2",
"icon-a_nav_fan":"e9d3",
"icon-a_nav_function":"e9d4",
"icon-a_nav_mode":"e9d5",
"icon-a_power":"e9d6",
"icon-a_mode_turbo":"e9d7",
"icon-a_nav_timer":"e9d8",
"icon-a_down":"e9d9",
"icon-a_up":"e9da",
"icon-a_water":"e9db",
"icon-a_selected":"e9dc",
"icon-Mute":"e9dd",
"icon-FanSpeed":"e9de",
"icon-Lamp":"e9df",
"icon-Heal":"e9e0",
"icon-Ele":"e9e1",
"icon-Strong":"e9e2",
"icon-dp_bag1":"e9e3",
"icon-off":"e9e4",
"icon-edit":"e9e5",
"icon-on":"e9e6",
"icon-timer":"e9e7",
"icon-power":"e9e8",
"icon-timer1":"e9ea",
"icon-Disarm":"e9e9",
"icon-SystemReady":"e9eb",
"icon-Arm":"e9ec",
"icon-HomeArm":"e9ed",
"icon-AwayArm":"e9ee",
"icon-power1":"e9ef",
"icon-Panic":"e9f0",
"icon-battery":"e9f1",
"icon-setting":"e9f2",
"icon-Trigger":"e9f3",
"icon-CMS":"e9f4",
"icon-tcl_function_eco":"ea17",
"icon-tcl_function_vs":"ea18",
"icon-tcl_mode_shoes":"ea19",
"icon-tcl_function_hs":"ea1a",
"icon-tcl_function_vs1":"ea1b",
"icon-tcl_function_vs2":"ea1c",
"icon-tcl_function_light":"ea1d",
"icon-tcl_function_vs3":"ea1e",
"icon-function_eh":"ea22",
"icon-air_quality":"ea23",
"icon-sound":"ea24",
"icon-icon-test6":"e819",
"icon-icon-test7":"e81a",
"icon-icon-test8":"e81c",
"icon-icon-test9":"e81d",
"icon-icon-test10":"e81e",
"icon-gongnuan":"e820",
"icon-lengnuan":"e821",
"icon-icon-test11":"eaa8",
"icon-zidong":"eaa9",
"icon-icon-test12":"eaaa",
"icon-icon-test13":"eaab",
"icon-xiegang":"ea96",
"icon-a_selected-copy":"eaac",
"icon-dp_play-copy":"eaad",
"icon-jiaquan":"eb05",
"icon-baojing":"eb06",
"icon-menci":"eb07",
"icon-shidu":"eb08",
"icon-ranqi":"eb09",
"icon-renxingyidong":"eb0a",
"icon-zhendong":"eb0b",
"icon-wendu2":"eb0c",
"icon-yanwu":"eb0d",
"icon-liangdu1":"eb0e",
"icon-pm":"eb0f",
"icon-voc":"eb10",
"icon-CH":"eb28",
"icon-gongneng1":"eb29",
"icon-CO":"eb2a",
"icon-yali":"eb2b",
"icon-Light":"eb2c",
"icon-CO1":"eb2d",
"icon-chanpin":"e87b"
}
package com.tuya.appsdk.sample.device.mgt
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import com.thingclips.sdk.core.PluginManager
import com.thingclips.smart.dp.parser.api.IDeviceDpParser
import com.thingclips.smart.dp.parser.api.IDpParser
import com.thingclips.smart.dp.parser.api.ISwitch
import com.thingclips.smart.interior.api.IAppDpParserPlugin
import com.thingclips.smart.sdk.bean.DeviceBean
import com.tuya.appsdk.sample.device.mgt.list.adapter.DeviceMgtAdapter
import java.util.concurrent.Executors
/**
* create by dongdaqing[mibo] 2023/9/20 14:23
*/
class DeviceDataHandler(lifecycleOwner: LifecycleOwner, private val adapter: DeviceMgtAdapter) :
LifecycleEventObserver {
private val plugin by lazy {
PluginManager.service(IAppDpParserPlugin::class.java)
}
private val handler by lazy {
Handler(Looper.getMainLooper())
}
private val mExecutor by lazy {
Executors.newSingleThreadExecutor()
}
init {
lifecycleOwner.lifecycle.addObserver(this)
}
fun execute(runnable: Runnable) {
mExecutor.execute(runnable)
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
mExecutor.shutdownNow()
}
}
fun updateAdapter(list: List<DeviceBean>) {
val data = java.util.ArrayList<SimpleDevice>()
for (deviceBean in list) {
val parser: IDeviceDpParser = plugin.update(deviceBean)
val switch = parser.getSwitchDp()
val simpleDevice = SimpleDevice(
deviceBean.devId,
deviceBean.getIconUrl(),
deviceBean.getName(),
deviceBean.isOnline,
deviceBean.productBean.category,
convert(deviceBean.devId, parser.getDisplayDp(), true),
convert(deviceBean.devId, parser.getOperableDp(), false),
if (switch == null) null else SimpleSwitch(switch.getSwitchStatus() == ISwitch.SWITCH_STATUS_ON)
)
data.add(simpleDevice)
}
handler.post { adapter.update(data) }
}
private fun convert(
devId: String,
list: List<IDpParser<Any>>,
display: Boolean
): List<SimpleDp> {
val data = java.util.ArrayList<SimpleDp>()
for (dp in list) {
val simpleDp = SimpleDp(
devId,
dp.getDpId(),
dp.getIconFont(),
if (display) dp.getDisplayStatus() else dp.getDisplayStatusForQuickOp(),
dp.getDisplayTitle(),
dp.getType()
)
data.add(simpleDp)
}
return data
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.mgt
import android.text.Html
import android.text.TextUtils
import java.util.Locale
import kotlin.math.pow
/**
* create by dongdaqing[mibo] 2023/9/20 11:33
*/
private val DEFAULT_ICON_FONT_MAP = mapOf(
"icon-dp_add" to "e600",
"icon-dp_reduce" to "e601",
"icon-dp_temp" to "e602",
"icon-dp_time3" to "e603",
"icon-dp_mode" to "e604",
"icon-dp_loop" to "e605",
"icon-dp_filter" to "e606",
"icon-dp_smile" to "e607",
"icon-dp_sleep" to "e608",
"icon-dp_dust" to "e609",
"icon-eco" to "e60a",
"icon-eoc2" to "e60b",
"icon-dp_tvoc" to "e60c",
"icon-dp_smart" to "e60d",
"icon-dp_o2" to "e60e",
"icon-dp_time2" to "e60f",
"icon-dp_light2" to "e610",
"icon-dp_shake" to "e611",
"icon-dp_home2" to "e612",
"icon-dp_direction" to "e613",
"icon-dp_power3" to "e614",
"icon-dp_wash" to "e615",
"icon-dp_location" to "e616",
"icon-dp_battery" to "e617",
"icon-dp_wind" to "e618",
"icon-dp_fresh" to "e619",
"icon-dp_wet2" to "e61a",
"icon-dp_half" to "e61b",
"icon-dp_sun" to "e61c",
"icon-dp_bag" to "e61d",
"icon-dp_wet" to "e61e",
"icon-dp_water" to "e61f",
"icon-dp_lock" to "e620",
"icon-dp_time" to "e621",
"icon-dp_light" to "e622",
"icon-dp_0" to "e623",
"icon-dp_1" to "e624",
"icon-dp_2" to "e625",
"icon-dp_3" to "e626",
"icon-dp_4" to "e627",
"icon-dp_5" to "e628",
"icon-dp_6" to "e629",
"icon-dp_7" to "e62a",
"icon-dp_8" to "e62b",
"icon-dp_9" to "e62c",
"icon-dp_c" to "e62d",
"icon-dp_f" to "e62e",
"icon-dp_power2" to "e62f",
"icon-dp_power" to "e630",
"icon-dp_right" to "e631",
"icon-dp_dot" to "e632",
"icon-dp_play" to "e633",
"icon-dp_pause" to "e634",
"icon-dp_down" to "e635",
"icon-dp_anti-clockwise" to "e636",
"icon-dp_clockwise" to "e637",
"icon-dp_up" to "e638",
"icon-dp_lightning" to "e63a",
"icon-dp_voice" to "e639",
"icon-dp_down1" to "e63b",
"icon-dp_cloud" to "e63c",
"icon-dp_upload" to "e63d",
"icon-dp_doc" to "e63e",
"icon-dp_curve" to "e63f",
"icon-dp_heart" to "e640",
"icon-dp_email" to "e641",
"icon-dp_circle" to "e642",
"icon-dp_plus" to "e643",
"icon-dp_home" to "e644",
"icon-dp_magnifier" to "e645",
"icon-dp_fly" to "e646",
"icon-dp_i" to "e647",
"icon-dp_down2" to "e648",
"icon-dp_book" to "e649",
"icon-dp_rabbish" to "e64a",
"icon-dp_hill" to "e64b",
"icon-dp_compass" to "e64c",
"icon-dp_gift" to "e64d",
"icon-dp_eye" to "e64e",
"icon-dp_notice" to "e64f",
"icon-dp_camera" to "e650",
"icon-dp_puzzle" to "e651",
"icon-dp_ratio" to "e652",
"icon-dp_block" to "e653",
"icon-dp_chat" to "e654",
"icon-dp_list2" to "e655",
"icon-dp_bottle" to "e656",
"icon-dp_doc2" to "e657",
"icon-dp_what" to "e658",
"icon-dp_warming" to "e659",
"icon-dp_updown" to "e65a",
"icon-dp_tool" to "e65b",
"icon-dp_tag" to "e65c",
"icon-dp_shield" to "e65d",
"icon-dp_box2" to "e65e",
"icon-dp_box" to "e65f",
"icon-dp_money" to "e660",
"icon-dp_house" to "e661",
"icon-dp_mic" to "e662",
"icon-dp_calendar" to "e663",
"icon-dp_list" to "e664",
"icon-dp_flag" to "e665",
"icon-dp_flower" to "e666",
"icon-baifeng" to "e8c1",
"icon-chushi" to "e8c2",
"icon-chengzhong" to "e8c3",
"icon-chongnai" to "e8c4",
"icon-chushi1" to "e8c5",
"icon-chushi2" to "e8c6",
"icon-cuowu" to "e8c7",
"icon-deng" to "e8c8",
"icon-dianliang" to "e8c9",
"icon-fengli" to "e8ca",
"icon-geren" to "e8cb",
"icon-gaodiyin" to "e8cc",
"icon-gongneng" to "e8cd",
"icon-guanjia" to "e8ce",
"icon-huoyan" to "e8cf",
"icon-qita" to "e8d0",
"icon-jiare" to "e8d1",
"icon-liangdu" to "e8d2",
"icon-jiare1" to "e8d3",
"icon-shangsheng" to "e8d4",
"icon-shouji" to "e8d5",
"icon-shoucang" to "e8d6",
"icon-shezhi" to "e8d7",
"icon-qiangli" to "e8d8",
"icon-tianjia" to "e8d9",
"icon-shoushimima" to "e8da",
"icon-shenghua" to "e8db",
"icon-shuibeng" to "e8dc",
"icon-tongji" to "e8dd",
"icon-tongji1" to "e8de",
"icon-yinshui" to "e8df",
"icon-yinliang" to "e8e0",
"icon-yanse" to "e8e1",
"icon-wendu" to "e8e2",
"icon-yundong" to "e8e3",
"icon-yunhang" to "e8e4",
"icon-zanting" to "e8e5",
"icon-zhengque" to "e8e6",
"icon-zhuangtai" to "e8e7",
"icon-zhileng" to "e8e8",
"icon-zhileng1" to "e8e9",
"icon-chushuang" to "e8ea",
"icon-zanting1" to "e8eb",
"icon-tongji2" to "e8ec",
"icon-baifeng1" to "e8ed",
"icon-set" to "e931",
"icon-yueliang" to "e932",
"icon-xue" to "e933",
"icon-fangzi" to "e934",
"icon-wendu1" to "e935",
"icon-taiyang" to "e936",
"icon-fangzi1" to "e937",
"icon-icon-percent" to "e938",
"icon-p6" to "e9aa",
"icon-p7" to "e9ab",
"icon-p8" to "e9ac",
"icon-p9" to "e9ad",
"icon-p10" to "e9ae",
"icon-p11" to "e9af",
"icon-p12" to "e9b0",
"icon-p13" to "e9b1",
"icon-p14" to "e9b2",
"icon-p15" to "e9b3",
"icon-ziyouchengxu" to "e9b4",
"icon-zhouchengxu" to "e9b5",
"icon-minus" to "e9ba",
"icon-plus" to "e9bb",
"icon-a_fan_low" to "e9bd",
"icon-a_fan_auto" to "e9be",
"icon-a_fan_med" to "e9bf",
"icon-a_fan_high" to "e9c0",
"icon-a_function_celsius" to "e9c1",
"icon-a_function_fahrenhei" to "e9c2",
"icon-a_function_hs" to "e9c3",
"icon-a_function_eco" to "e9c4",
"icon-a_function_filter" to "e9c5",
"icon-a_function_sleep" to "e9c6",
"icon-a_function_pump" to "e9c7",
"icon-a_function_vs" to "e9c8",
"icon-a_function_turbo" to "e9c9",
"icon-a_mode_basement" to "e9ca",
"icon-a_mode_continuous" to "e9cb",
"icon-a_mode_cool" to "e9cc",
"icon-a_mode_fan" to "e9cd",
"icon-a_mode_clothes" to "e9ce",
"icon-a_mode_feel" to "e9cf",
"icon-a_mode_heat" to "e9d0",
"icon-a_mode_livingroom" to "e9d1",
"icon-a_mode_dry" to "e9d2",
"icon-a_nav_fan" to "e9d3",
"icon-a_nav_function" to "e9d4",
"icon-a_nav_mode" to "e9d5",
"icon-a_power" to "e9d6",
"icon-a_mode_turbo" to "e9d7",
"icon-a_nav_timer" to "e9d8",
"icon-a_down" to "e9d9",
"icon-a_up" to "e9da",
"icon-a_water" to "e9db",
"icon-a_selected" to "e9dc",
"icon-Mute" to "e9dd",
"icon-FanSpeed" to "e9de",
"icon-Lamp" to "e9df",
"icon-Heal" to "e9e0",
"icon-Ele" to "e9e1",
"icon-Strong" to "e9e2",
"icon-dp_bag1" to "e9e3",
"icon-off" to "e9e4",
"icon-edit" to "e9e5",
"icon-on" to "e9e6",
"icon-timer" to "e9e7",
"icon-power" to "e9e8",
"icon-timer1" to "e9ea",
"icon-Disarm" to "e9e9",
"icon-SystemReady" to "e9eb",
"icon-Arm" to "e9ec",
"icon-HomeArm" to "e9ed",
"icon-AwayArm" to "e9ee",
"icon-power1" to "e9ef",
"icon-Panic" to "e9f0",
"icon-battery" to "e9f1",
"icon-setting" to "e9f2",
"icon-Trigger" to "e9f3",
"icon-CMS" to "e9f4",
"icon-tcl_function_eco" to "ea17",
"icon-tcl_function_vs" to "ea18",
"icon-tcl_mode_shoes" to "ea19",
"icon-tcl_function_hs" to "ea1a",
"icon-tcl_function_vs1" to "ea1b",
"icon-tcl_function_vs2" to "ea1c",
"icon-tcl_function_light" to "ea1d",
"icon-tcl_function_vs3" to "ea1e",
"icon-function_eh" to "ea22",
"icon-air_quality" to "ea23",
"icon-sound" to "ea24",
"icon-icon-test6" to "e819",
"icon-icon-test7" to "e81a",
"icon-icon-test8" to "e81c",
"icon-icon-test9" to "e81d",
"icon-icon-test10" to "e81e",
"icon-gongnuan" to "e820",
"icon-lengnuan" to "e821",
"icon-icon-test11" to "eaa8",
"icon-zidong" to "eaa9",
"icon-icon-test12" to "eaaa",
"icon-icon-test13" to "eaab",
"icon-xiegang" to "ea96",
"icon-a_selected-copy" to "eaac",
"icon-dp_play-copy" to "eaad",
"icon-jiaquan" to "eb05",
"icon-baojing" to "eb06",
"icon-menci" to "eb07",
"icon-shidu" to "eb08",
"icon-ranqi" to "eb09",
"icon-renxingyidong" to "eb0a",
"icon-zhendong" to "eb0b",
"icon-wendu2" to "eb0c",
"icon-yanwu" to "eb0d",
"icon-liangdu1" to "eb0e",
"icon-pm" to "eb0f",
"icon-voc" to "eb10",
"icon-CH" to "eb28",
"icon-gongneng1" to "eb29",
"icon-CO" to "eb2a",
"icon-yali" to "eb2b",
"icon-Light" to "eb2c",
"icon-CO1" to "eb2d",
"icon-chanpin" to "e87b"
)
fun getIconFontContent(name: String?): CharSequence? {
return if (name == null) {
null
} else {
val content = DEFAULT_ICON_FONT_MAP[name] ?: return null
val real = convert(content)
if (TextUtils.isEmpty(real)) {
null
} else {
Html.fromHtml(real)
}
}
}
private fun convert(str: String): String {
val value = str.lowercase(Locale.getDefault())
var fail = false
var hex = 0L
for (i in value.indices) {
val curV = getValueOfX(value[i])
if (curV == -1) {
fail = true
break
} else {
hex += (curV * 16.0.pow((value.length - i - 1).toDouble())).toLong()
}
}
return if (fail) {
""
} else {
"&#$hex"
}
}
private fun getValueOfX(c: Char): Int {
val codes =
charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
var v = -1
for (i in codes.indices) {
if (codes[i] == c) {
v = i
break
}
}
return v
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.mgt
/**
* create by dongdaqing[mibo] 2023/9/20 11:01
*/
data class SimpleDevice(
val devId: String,
val icon: String,
val name: String,
val online: Boolean,
val category: String?,
val displays: List<SimpleDp>,
val operates: List<SimpleDp>,
val simpleSwitch: SimpleSwitch?
) {
fun sameContent(other: SimpleDevice): Boolean {
return icon == other.icon
&& name == other.name
&& category == other.category
&& online == other.online
&& same(other.simpleSwitch)
&& same(displays, other.displays)
&& same(operates, other.operates)
}
private fun same(other: SimpleSwitch?): Boolean {
return simpleSwitch?.switchOn == other?.switchOn
}
private fun same(a: List<SimpleDp>, b: List<SimpleDp>): Boolean {
if (a.size != b.size) return false
for (i in a.indices) {
val ax = a[i]
val bx = b[i]
if (!ax.same(bx)) return false
}
return true
}
}
data class SimpleDp(
val devId: String,
val dpId: String,
val iconFont: String?,
val status: String,
val dpName: String?,
val type: String,
) {
fun same(other: SimpleDp): Boolean {
return dpId == other.dpId
&& devId == other.devId
&& iconFont == other.iconFont
&& status == other.status
&& dpName == other.dpName
}
}
data class SimpleSwitch(val switchOn: Boolean)
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.mgt.control.activity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.thingclips.smart.android.device.bean.*
import com.tuya.appsdk.sample.device.mgt.R
import com.tuya.appsdk.sample.device.mgt.control.dpItem.*
import com.thingclips.smart.android.device.enums.DataTypeEnum
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.sdk.api.IResultCallback
/**
* Device control sample
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/21 10:30 AM
*/
class DeviceMgtControlActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_mgt_activity_control)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
val llDp = findViewById<LinearLayout>(R.id.llDp)
val deviceId = intent.getStringExtra("deviceId")
deviceId?.let {
val mDevice = ThingHomeSdk.newDeviceInstance(it)
val deviceBean = ThingHomeSdk.getDataInstance().getDeviceBean(it)
findViewById<Button>(R.id.btnReset).setOnClickListener {
// device reset factory
mDevice.resetFactory(object : IResultCallback {
override fun onSuccess() {
finish()
}
override fun onError(code: String?, error: String?) {
}
})
}
findViewById<Button>(R.id.btnRemove).setOnClickListener {
// remove device
mDevice.removeDevice(object : IResultCallback {
override fun onSuccess() {
finish()
}
override fun onError(code: String?, error: String?) {
}
})
}
findViewById<TextView>(R.id.tvDeviceName).text = deviceBean?.name
ThingHomeSdk.getDataInstance().getSchema(it)?.let { map ->
for (bean in map.values) {
val value = deviceBean?.dps?.get(bean.id)
if (bean.type == DataTypeEnum.OBJ.type) {
// obj
when (bean.getSchemaType()) {
BoolSchemaBean.type -> {
val vItem = DpBooleanItem(
this,
schemaBean = bean,
value = value as Boolean,
device = mDevice
)
llDp.addView(vItem)
}
EnumSchemaBean.type -> {
val vItem = DpEnumItem(
this,
schemaBean = bean,
value = value.toString(),
device = mDevice
)
llDp.addView(vItem)
}
StringSchemaBean.type -> {
val vItem = DpCharTypeItem(
this,
schemaBean = bean,
value = value as String,
device = mDevice
)
llDp.addView(vItem)
}
ValueSchemaBean.type -> {
val vItem = DpIntegerItem(
this,
schemaBean = bean,
value = value as Int,
device = mDevice
)
llDp.addView(vItem)
}
BitmapSchemaBean.type -> {
val vItem =
DpFaultItem(this, schemaBean = bean, value = value.toString())
llDp.addView(vItem)
}
}
} else if (bean.type == DataTypeEnum.RAW.type) {
// raw | file
val vItem = DpRawTypeItem(
this,
schemaBean = bean,
value = value.toString(),
device = mDevice
)
llDp.addView(vItem)
}
}
}
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.mgt.control.dpItem
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.FrameLayout
import android.widget.Switch
import android.widget.TextView
import com.alibaba.fastjson.JSONObject
import com.tuya.appsdk.sample.device.mgt.R
import com.thingclips.smart.android.device.bean.SchemaBean
import com.thingclips.smart.sdk.api.IResultCallback
import com.thingclips.smart.sdk.api.IThingDevice
/**
* Data point(DP) Boolean type item
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/21 3:06 PM
*/
class DpBooleanItem @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
schemaBean: SchemaBean,
value: Boolean,
device: IThingDevice
) : FrameLayout(context, attrs, defStyle) {
init {
inflate(context, R.layout.device_mgt_item_dp_boolean, this)
findViewById<TextView>(R.id.tvDpName).text = schemaBean.name
val swDp = findViewById<Switch>(R.id.swDp)
swDp.isChecked = value
if (schemaBean.mode.contains("w")) {
// Data can be issued by the cloud.
swDp.setOnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_UP) {
val map = HashMap<String, Any>()
val isChecked = !swDp.isChecked
map[schemaBean.id] = isChecked
JSONObject.toJSONString(map)?.let {
device.publishDps(it, object : IResultCallback {
override fun onSuccess() {
swDp.isChecked = isChecked
}
override fun onError(code: String?, error: String?) {
}
})
}
}
return@setOnTouchListener true
}
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.mgt.control.dpItem
import android.content.Context
import android.util.AttributeSet
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.TextView
import com.alibaba.fastjson.JSONObject
import com.tuya.appsdk.sample.device.mgt.R
import com.thingclips.smart.android.device.bean.SchemaBean
import com.thingclips.smart.sdk.api.IResultCallback
import com.thingclips.smart.sdk.api.IThingDevice
/**
* Data point(DP) Char Type type item
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/21 3:06 PM
*/
class DpCharTypeItem @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
schemaBean: SchemaBean,
value: String,
device: IThingDevice
) : FrameLayout(context, attrs, defStyle) {
init {
inflate(context, R.layout.device_mgt_item_dp_char_type, this)
findViewById<TextView>(R.id.tvDpName).text = schemaBean.name
val etDp = findViewById<EditText>(R.id.etDp)
etDp.setText(value)
if (schemaBean.mode.contains("w")) {
// Data can be issued by the cloud.
etDp.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
val map = HashMap<String, Any>()
map[schemaBean.id] = etDp.text.toString()
JSONObject.toJSONString(map)?.let {
device.publishDps(it, object : IResultCallback {
override fun onSuccess() {
}
override fun onError(code: String?, error: String?) {
}
})
}
return@setOnEditorActionListener true
}
return@setOnEditorActionListener false
}
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.mgt.control.dpItem
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.widget.ListPopupWindow
import com.alibaba.fastjson.JSONObject
import com.tuya.appsdk.sample.device.mgt.R
import com.thingclips.smart.android.device.bean.SchemaBean
import com.thingclips.smart.home.sdk.utils.SchemaMapper
import com.thingclips.smart.sdk.api.IResultCallback
import com.thingclips.smart.sdk.api.IThingDevice
/**
* Data point(DP) Enum type item
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/21 3:06 PM
*/
class DpEnumItem @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
schemaBean: SchemaBean,
value: String,
device: IThingDevice
) : FrameLayout(context, attrs, defStyle) {
init {
inflate(context, R.layout.device_mgt_item_dp_enum, this)
findViewById<TextView>(R.id.tvDpName).text = schemaBean.name
val btnDp = findViewById<Button>(R.id.btnDp)
btnDp.text = value
if (schemaBean.mode.contains("w")) {
// Data can be issued by the cloud.
val listPopupWindow = ListPopupWindow(context, null, R.attr.listPopupWindowStyle)
listPopupWindow.anchorView = btnDp
val enumSchemaBean = SchemaMapper.toEnumSchema(schemaBean.property)
val items = enumSchemaBean.range.toList()
val adapter = ArrayAdapter(context, R.layout.device_mgt_item_dp_enum_popup_item, items)
listPopupWindow.setAdapter(adapter)
listPopupWindow.setOnItemClickListener { parent, view, position, id ->
val map = HashMap<String, Any>()
map[schemaBean.id] = items[position]
JSONObject.toJSONString(map)?.let {
device.publishDps(it, object : IResultCallback {
override fun onSuccess() {
btnDp.text = items[position]
}
override fun onError(code: String?, error: String?) {
Log.e("DpEnumItem", "$code --> $error")
}
})
}
listPopupWindow.dismiss()
}
btnDp.setOnClickListener {
listPopupWindow.show()
}
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.mgt.control.dpItem
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import android.widget.TextView
import com.tuya.appsdk.sample.device.mgt.R
import com.thingclips.smart.android.device.bean.SchemaBean
/**
* Data point(DP) Fault type item
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/21 3:06 PM
*/
class DpFaultItem @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
schemaBean: SchemaBean,
value: String
) : FrameLayout(context, attrs, defStyle) {
init {
inflate(context, R.layout.device_mgt_item_dp_fault, this)
findViewById<TextView>(R.id.tvDpName).text = schemaBean.name
val tvFault = findViewById<TextView>(R.id.tvFault)
tvFault.text = value
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.mgt.control.dpItem
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import android.widget.TextView
import com.alibaba.fastjson.JSONObject
import com.google.android.material.slider.Slider
import com.tuya.appsdk.sample.device.mgt.R
import com.thingclips.smart.android.device.bean.SchemaBean
import com.thingclips.smart.home.sdk.utils.SchemaMapper
import com.thingclips.smart.sdk.api.IResultCallback
import com.thingclips.smart.sdk.api.IThingDevice
import kotlin.math.pow
/**
* Data point(DP) Integer type item
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/21 3:06 PM
*/
class DpIntegerItem @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
schemaBean: SchemaBean,
value: Int,
device: IThingDevice
) : FrameLayout(context, attrs, defStyle) {
init {
inflate(context, R.layout.device_mgt_item_dp_integer, this)
val slDp = findViewById<Slider>(R.id.slDp)
val valueSchemaBean = SchemaMapper.toValueSchema(schemaBean.property)
val scale = 10.0.pow(valueSchemaBean.scale.toDouble())
var curValue = (value * valueSchemaBean.step + valueSchemaBean.min).toFloat() / scale
if (curValue > valueSchemaBean.max) {
curValue = valueSchemaBean.max.toDouble()
}else if(curValue < valueSchemaBean.min){
curValue = valueSchemaBean.min.toDouble()
}
slDp.value = curValue.toFloat()
slDp.stepSize = (valueSchemaBean.step.toDouble() / scale).toFloat()
slDp.valueFrom = valueSchemaBean.min.toFloat()
slDp.valueTo = valueSchemaBean.max.toFloat()
findViewById<TextView>(R.id.tvDpName).text = "${schemaBean.name}(${valueSchemaBean.unit})"
if (schemaBean.mode.contains("w")) {
// Data can be issued by the cloud.
slDp.addOnChangeListener { slider, sValue, fromUser ->
val map = HashMap<String, Any>()
map[schemaBean.id] =
(((sValue * scale) - valueSchemaBean.min) / valueSchemaBean.step).toInt()
JSONObject.toJSONString(map)?.let {
device.publishDps(it, object : IResultCallback {
override fun onSuccess() {
}
override fun onError(code: String?, error: String?) {
}
})
}
}
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.mgt.control.dpItem
import android.content.Context
import android.util.AttributeSet
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.TextView
import com.alibaba.fastjson.JSONObject
import com.tuya.appsdk.sample.device.mgt.R
import com.thingclips.smart.android.common.utils.HexUtil
import com.thingclips.smart.android.device.bean.SchemaBean
import com.thingclips.smart.sdk.api.IResultCallback
import com.thingclips.smart.sdk.api.IThingDevice
/**
* Data point(DP) Raw Type type item
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/21 3:06 PM
*/
class DpRawTypeItem @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
schemaBean: SchemaBean,
value: String,
device: IThingDevice
) : FrameLayout(context, attrs, defStyle) {
init {
inflate(context, R.layout.device_mgt_item_dp_raw_type, this)
findViewById<TextView>(R.id.tvDpName).text = schemaBean.name
val etDp = findViewById<EditText>(R.id.etDp)
etDp.setText(value)
if (schemaBean.mode.contains("w")) {
// Data can be issued by the cloud.
etDp.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
val rawValue = etDp.text.toString()
if (checkRawValue(rawValue)) { //raw | file
val map = HashMap<String, Any>()
map[schemaBean.id] = rawValue
JSONObject.toJSONString(map)?.let {
device.publishDps(it, object : IResultCallback {
override fun onSuccess() {
}
override fun onError(code: String?, error: String?) {
}
})
}
}
return@setOnEditorActionListener true
}
return@setOnEditorActionListener false
}
}
}
private fun checkRawValue(rawValue: String): Boolean {
return HexUtil.checkHexString(rawValue) && rawValue.length % 2 == 0
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.mgt.list.activity
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.bean.HomeBean
import com.thingclips.smart.home.sdk.callback.IThingHomeResultCallback
import com.tuya.appsdk.sample.device.mgt.DeviceDataHandler
import com.tuya.appsdk.sample.device.mgt.R
import com.tuya.appsdk.sample.device.mgt.list.adapter.DeviceMgtAdapter
import com.tuya.appsdk.sample.device.mgt.list.enum.DeviceListTypePage
import com.tuya.appsdk.sample.resource.HomeModel
/**
* Device Management initial device data sample
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/21 9:58 AM
*/
class DeviceMgtListActivity : AppCompatActivity() {
lateinit var adapter: DeviceMgtAdapter
var type = DeviceListTypePage.NORMAL_DEVICE_LIST
private lateinit var dataHandler: DeviceDataHandler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_mgt_activity_list)
// Get Device List Type
type = intent.getIntExtra("type", DeviceListTypePage.NORMAL_DEVICE_LIST)
initToolbar()
// Set List
val rvList = findViewById<RecyclerView>(R.id.rvList)
rvList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
adapter = DeviceMgtAdapter(this, type)
dataHandler = DeviceDataHandler(this, adapter)
rvList.adapter = adapter
}
private fun initToolbar() {
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
toolbar.title = when (type) {
DeviceListTypePage.NORMAL_DEVICE_LIST -> getString(R.string.device_mgt_list)
DeviceListTypePage.ZIGBEE_GATEWAY_LIST -> getString(R.string.device_zb_gateway_list)
else -> getString(R.string.device_mgt_list)
}
}
override fun onResume() {
super.onResume()
val homeId = HomeModel.INSTANCE.getCurrentHome(this)
/**
* The device control must first initialize the data,
* and call the following method to get the device information in the home.
* initialization only need when the begin of app lifecycle and switch home.
*/
ThingHomeSdk.newHomeInstance(homeId).getHomeDetail(object : IThingHomeResultCallback {
override fun onSuccess(bean: HomeBean?) {
dataHandler.execute {
bean?.let { it ->
if (type == DeviceListTypePage.NORMAL_DEVICE_LIST) {
dataHandler.updateAdapter(it.deviceList)
} else {
dataHandler.updateAdapter(it.deviceList.filter { it.isZigBeeWifi })
}
}
}
}
override fun onError(errorCode: String?, errorMsg: String?) {
}
})
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.mgt.list.activity
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.tuya.appsdk.sample.device.mgt.R
import com.tuya.appsdk.sample.device.mgt.list.adapter.DeviceMgtAdapter
import com.tuya.appsdk.sample.device.mgt.list.enum.DeviceListTypePage
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.sdk.api.IThingDataCallback
import com.thingclips.smart.sdk.bean.DeviceBean
import com.tuya.appsdk.sample.device.mgt.DeviceDataHandler
/**
* ZigBee Sub-device List Sample
*
* @author aiwen <a href="mailto:developer@tuya.com"/>
* @since 2/25/21 2:26 PM
*/
class DeviceSubZigbeeActivity : AppCompatActivity() {
private lateinit var adapter: DeviceMgtAdapter
private lateinit var deviceId: String
private lateinit var dataHandler: DeviceDataHandler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.device_mgt_activity_list)
deviceId = intent.getStringExtra("deviceId").toString()
initToolbar()
val rvList = findViewById<RecyclerView>(R.id.rvList)
rvList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
adapter = DeviceMgtAdapter(this, DeviceListTypePage.ZIGBEE_SUB_DEVICE_LIST)
dataHandler = DeviceDataHandler(this, adapter)
rvList.adapter = adapter
getZbSubDeviceList()
}
// Get Sub-devices
private fun getZbSubDeviceList() {
ThingHomeSdk.newGatewayInstance(deviceId)
.getSubDevList(object : IThingDataCallback<List<DeviceBean>> {
override fun onSuccess(result: List<DeviceBean>?) {
dataHandler.execute {
dataHandler.updateAdapter(result ?: arrayListOf())
}
}
override fun onError(errorCode: String?, errorMessage: String?) {
Toast.makeText(
this@DeviceSubZigbeeActivity,
"Error->$errorMessage",
Toast.LENGTH_LONG
).show()
}
})
}
private fun initToolbar() {
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
toolbar.title = getString(R.string.device_zb_sub_device_list)
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.mgt.list.adapter
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.thing.smart.sweeper.SweeperActivity
import com.thingclips.sdk.core.PluginManager
import com.thingclips.smart.interior.api.IAppDpParserPlugin
import com.thingclips.smart.interior.api.IThingDevicePlugin
import com.tuya.appsdk.sample.device.mgt.R
import com.tuya.appsdk.sample.device.mgt.SimpleDevice
import com.tuya.appsdk.sample.device.mgt.control.activity.DeviceMgtControlActivity
import com.tuya.appsdk.sample.device.mgt.getIconFontContent
import com.tuya.appsdk.sample.device.mgt.list.activity.DeviceSubZigbeeActivity
import com.tuya.appsdk.sample.device.mgt.list.enum.DeviceListTypePage
import com.tuya.smart.android.demo.camera.CameraUtils
/**
* Device list adapter
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/21 10:06 AM
*/
class DeviceMgtAdapter(context: Context, val type: Int) :
RecyclerView.Adapter<DeviceMgtAdapter.ViewHolder>() {
private val differ by lazy {
AsyncListDiffer(this, object : ItemCallback<SimpleDevice>() {
override fun areItemsTheSame(oldItem: SimpleDevice, newItem: SimpleDevice): Boolean {
return oldItem.devId == newItem.devId
}
override fun areContentsTheSame(oldItem: SimpleDevice, newItem: SimpleDevice): Boolean {
return oldItem.sameContent(newItem)
}
})
}
private val typeface: Typeface
init {
typeface = Typeface.createFromAsset(context.assets, "fonts/iconfont.ttf")
}
fun update(data: List<SimpleDevice>) {
differ.submitList(data)
}
fun itemClicked(view: View, position: Int) {
val deviceBean = differ.currentList[position]
if (CameraUtils.ipcProcess(view.context, deviceBean.devId)) {
return
}
if (deviceBean.category?.contains("sd") == true) {
val intent = Intent(view.context, SweeperActivity::class.java)
intent.putExtra("deviceId", deviceBean.devId)
view.context.startActivity(intent)
return
}
when (type) {
DeviceListTypePage.ZIGBEE_GATEWAY_LIST -> {
// Navigate to zigBee sub device list
val intent = Intent(view.context, DeviceSubZigbeeActivity::class.java)
intent.putExtra("deviceId", deviceBean.devId)
view.context.startActivity(intent)
}
else -> {
// Navigate to zigBee sub device management
val intent = Intent(view.context, DeviceMgtControlActivity::class.java)
intent.putExtra("deviceId", deviceBean.devId)
view.context.startActivity(intent)
}
}
}
private fun switchClicked(position: Int) {
try {
val plugin = PluginManager.service(IThingDevicePlugin::class.java)
val device = differ.currentList[position] ?: return
val parserPlugin = PluginManager.service(IAppDpParserPlugin::class.java)
val iSwitch = parserPlugin.getParser(device.devId)!!.getSwitchDp()
plugin.newDeviceInstance(device.devId)
.publishDps(iSwitch!!.getCommands(!device.simpleSwitch!!.switchOn), null)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val holder = ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.device_item, parent, false),
::switchClicked
)
holder.itemView.setOnClickListener {
val recyclerView = it.parent as RecyclerView
itemClicked(it, recyclerView.getChildAdapterPosition(it))
}
return holder
}
override fun getItemCount(): Int {
return differ.currentList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val bean: SimpleDevice = differ.currentList[position]
Glide.with(holder.itemView).load(bean.icon).into(holder.iconView)
holder.deviceName.text = bean.name
if (bean.online) {
val switchDp = bean.simpleSwitch
if (switchDp != null) {
holder.switchView.visibility = View.VISIBLE
holder.switchView.setImageResource(if (switchDp.switchOn) R.drawable.on else R.drawable.off)
} else {
holder.switchView.visibility = View.GONE
}
val displays = bean.displays
val builder = StringBuilder()
for (dp in displays) {
val content = dp.status
if (!TextUtils.isEmpty(content)) {
builder.append(getIconFontContent(dp.iconFont)).append(content).append(" ")
}
}
if (builder.isNotEmpty()) {
holder.statusView.visibility = View.VISIBLE
holder.statusView.typeface = typeface
} else {
holder.statusView.visibility = View.GONE
holder.statusView.typeface = null
}
holder.statusView.text = builder
val operable = bean.operates
if (operable.isEmpty()) {
holder.recyclerView.adapter = null
holder.recyclerView.visibility = View.GONE
holder.devFunc.visibility = View.GONE
} else {
val adapter = holder.recyclerView.adapter as OperableDpAdapter?
if (adapter == null) {
holder.recyclerView.adapter = OperableDpAdapter(typeface, operable) {
holder.itemView.performClick()
}
} else {
adapter.update(operable)
}
holder.devFunc.visibility = View.VISIBLE
holder.recyclerView.visibility = View.VISIBLE
}
} else {
holder.statusView.typeface = null
holder.statusView.setText(R.string.device_mgt_offline)
holder.devFunc.visibility = View.GONE
holder.statusView.visibility = View.VISIBLE
holder.switchView.visibility = View.GONE
holder.recyclerView.visibility = View.GONE
}
}
class ViewHolder(itemView: View, clickListener: (Int) -> Unit) :
RecyclerView.ViewHolder(itemView) {
val deviceName = itemView.findViewById<TextView>(R.id.deviceName)
val statusView = itemView.findViewById<TextView>(R.id.statusView)
val iconView = itemView.findViewById<ImageView>(R.id.iconView)
val switchView = itemView.findViewById<ImageView>(R.id.switchButton).apply {
// setOnClickListener {
// clickListener(adapterPosition)
// }
}
val recyclerView = itemView.findViewById<RecyclerView>(R.id.recyclerView).apply {
layoutManager = GridLayoutManager(itemView.context, 4)
}
val devFunc = itemView.findViewById<View>(R.id.devFuncView)
}
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.mgt.list.adapter
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.RecyclerView
import com.thingclips.sdk.core.PluginManager
import com.thingclips.smart.interior.api.IAppDpParserPlugin
import com.thingclips.smart.interior.api.IThingDevicePlugin
import com.tuya.appsdk.sample.device.mgt.R
import com.tuya.appsdk.sample.device.mgt.SimpleDp
import com.tuya.appsdk.sample.device.mgt.getIconFontContent
/**
* create by dongdaqing[mibo] 2023/9/20 11:25
*/
class OperableDpAdapter(
private val typeface: Typeface,
data: List<SimpleDp>,
clickListener: OnClickListener
) :
RecyclerView.Adapter<DpViewHolder>() {
private var listener: OnClickListener = clickListener
private val differ by lazy {
AsyncListDiffer(this, object : ItemCallback<SimpleDp>() {
override fun areItemsTheSame(oldItem: SimpleDp, newItem: SimpleDp): Boolean {
return oldItem.dpId == newItem.dpId
}
override fun areContentsTheSame(oldItem: SimpleDp, newItem: SimpleDp): Boolean {
return oldItem.same(newItem)
}
})
}
init {
differ.submitList(data)
}
fun update(data: List<SimpleDp>) {
differ.submitList(data)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DpViewHolder {
return DpViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.device_mgt_dp_item, parent, false),
typeface
)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
override fun onBindViewHolder(holder: DpViewHolder, position: Int) {
val dp: SimpleDp = differ.currentList[position]
if ("bool" == dp.type) {
holder.itemView.setOnClickListener { v: View ->
val recyclerView = v.parent as RecyclerView
val p = recyclerView.getChildAdapterPosition(v)
val plugin = PluginManager.service(IThingDevicePlugin::class.java)
val simpleDp = differ.currentList[p] ?: return@setOnClickListener
val parserPlugin = PluginManager.service(IAppDpParserPlugin::class.java)
val list = parserPlugin.getParser(simpleDp.devId)!!.getOperableDp()
for (parser in list) {
if (parser.getDpId() == simpleDp.dpId) {
plugin.newDeviceInstance(simpleDp.devId)
.publishDps(parser.getCommands((parser.getValue() as Boolean)), null)
return@setOnClickListener
}
}
}
} else {
holder.itemView.setOnClickListener(listener)
}
holder.iconView.text = getIconFontContent(dp.iconFont)
holder.titleView.text = dp.dpName
holder.statusView.text = dp.status
}
}
class DpViewHolder(itemView: View, typeface: Typeface?) :
RecyclerView.ViewHolder(itemView) {
val iconView: TextView = itemView.findViewById<TextView>(R.id.iconView).apply {
setTypeface(typeface)
}
val titleView: TextView = itemView.findViewById(R.id.titleView)
val statusView: TextView = itemView.findViewById(R.id.statusView)
}
\ No newline at end of file
package com.tuya.appsdk.sample.device.mgt.list.enum
/**
* Device List Type Page
*
* @author aiwen <a href="mailto:developer@tuya.com"/>
* @since 2/25/21 2:16 PM
*/
interface DeviceListTypePage {
companion object {
const val NORMAL_DEVICE_LIST = 1
const val ZIGBEE_GATEWAY_LIST = 2
const val ZIGBEE_SUB_DEVICE_LIST = 3
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.device.mgt.main
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import android.widget.Toast
import com.tuya.appsdk.sample.device.mgt.R
import com.tuya.appsdk.sample.device.mgt.list.activity.DeviceMgtListActivity
import com.tuya.appsdk.sample.device.mgt.list.enum.DeviceListTypePage
import com.tuya.appsdk.sample.resource.HomeModel
/**
* Device Management Widget
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/9 5:06 PM
*/
class DeviceMgtFuncWidget {
fun render(context: Context): View {
val rootView =
LayoutInflater.from(context).inflate(R.layout.device_mgt_view_func, null, false)
initView(rootView)
return rootView
}
private fun initView(rootView: View) {
// Device list
rootView.findViewById<TextView>(R.id.tvDeviceList).setOnClickListener {
if (!HomeModel.INSTANCE.checkHomeId(rootView.context)) {
Toast.makeText(
rootView.context,
rootView.context.getString(R.string.home_current_home_tips),
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
val intent = Intent(it.context, DeviceMgtListActivity::class.java)
intent.putExtra("type", DeviceListTypePage.NORMAL_DEVICE_LIST)
it.context.startActivity(intent)
}
// ZigBee Gateway List
rootView.findViewById<TextView>(R.id.tv_zb_gateway_list).setOnClickListener {
if (!HomeModel.INSTANCE.checkHomeId(rootView.context)) {
Toast.makeText(
rootView.context,
rootView.context.getString(R.string.home_current_home_tips),
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
val intent = Intent(it.context, DeviceMgtListActivity::class.java)
intent.putExtra("type", DeviceListTypePage.ZIGBEE_GATEWAY_LIST)
it.context.startActivity(intent)
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="MissingDefaultResource">
<corners android:radius="8dp" />
<solid android:color="#ffffffff" />
</shape>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="42dp"
android:height="40dp"
android:viewportWidth="21"
android:viewportHeight="20">
<path
android:fillAlpha="0.2"
android:fillColor="#000000"
android:pathData="M10.56,10.08m-9.728,0a9.728,9.728 0,1 1,19.456 0a9.728,9.728 0,1 1,-19.456 0"
android:strokeAlpha="0.2" />
<path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M10.602,5.216C10.252,5.216 9.968,5.477 9.968,5.799V8.912C9.968,9.235 10.252,9.496 10.602,9.496C10.953,9.496 11.237,9.235 11.237,8.912V5.799C11.237,5.477 10.953,5.216 10.602,5.216ZM13.115,6.932C12.861,7.154 12.85,7.524 13.091,7.758C14.51,9.135 14.51,11.38 13.09,12.757C11.689,14.116 9.431,14.116 8.03,12.757C6.61,11.38 6.61,9.135 8.029,7.758C8.27,7.524 8.26,7.154 8.005,6.932C7.751,6.711 7.349,6.721 7.108,6.954C5.225,8.782 5.225,11.733 7.109,13.56C9.011,15.405 12.109,15.405 14.011,13.56C15.895,11.733 15.895,8.782 14.012,6.954C13.771,6.721 13.369,6.711 13.115,6.932Z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#23D1A5"
android:pathData="M10.04,10.08m-9.728,0a9.728,9.728 0,1 1,19.456 0a9.728,9.728 0,1 1,-19.456 0" />
<path
android:fillColor="#00000000"
android:pathData="M10.04,10.08m-9.475,0a9.475,9.475 0,1 1,18.949 0a9.475,9.475 0,1 1,-18.949 0"
android:strokeWidth="0.506667"
android:strokeAlpha="0.05"
android:strokeColor="#000000" />
<path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M10.082,5.216C9.732,5.216 9.448,5.477 9.448,5.799V8.912C9.448,9.235 9.732,9.496 10.082,9.496C10.433,9.496 10.717,9.235 10.717,8.912V5.799C10.717,5.477 10.433,5.216 10.082,5.216ZM12.595,6.932C12.34,7.154 12.33,7.524 12.571,7.758C13.99,9.135 13.99,11.38 12.57,12.757C11.169,14.116 8.911,14.116 7.51,12.757C6.09,11.38 6.09,9.135 7.509,7.758C7.75,7.524 7.74,7.154 7.485,6.932C7.231,6.711 6.829,6.721 6.588,6.954C4.705,8.782 4.705,11.733 6.589,13.56C8.491,15.405 11.589,15.405 13.491,13.56C15.375,11.733 15.375,8.782 13.492,6.954C13.251,6.721 12.849,6.711 12.595,6.932Z" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
android:background="@drawable/card_bg"
android:minWidth="121dp">
<View
android:id="@+id/anchor"
android:layout_width="1px"
android:layout_height="104dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iconView"
android:layout_width="56dp"
android:layout_height="56dp"
app:layout_constraintBottom_toBottomOf="@id/anchor"
app:layout_constraintStart_toEndOf="@+id/anchor"
app:layout_constraintTop_toTopOf="@+id/anchor"
tools:background="@android:color/holo_red_light" />
<Space
android:id="@+id/center"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
app:layout_constraintBottom_toBottomOf="@id/anchor"
app:layout_constraintEnd_toStartOf="@+id/switchButton"
app:layout_constraintStart_toEndOf="@+id/iconView"
app:layout_constraintTop_toTopOf="@+id/anchor" />
<ImageView
android:id="@+id/switchButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginRight="16dp"
app:layout_constraintBottom_toBottomOf="@id/anchor"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/center"
app:layout_constraintTop_toTopOf="@id/anchor" />
<TextView
android:id="@+id/deviceName"
android:layout_width="0dp"
android:layout_height="24dp"
android:ellipsize="end"
android:lines="1"
android:textColor="#e6000000"
android:textSize="17dp"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toStartOf="@id/center"
app:layout_constraintEnd_toEndOf="@id/center"
app:layout_constraintTop_toTopOf="@id/center"
app:layout_constraintBottom_toTopOf="@id/statusView"
app:layout_constraintVertical_chainStyle="packed"
tools:text="照明照照明照照明照照明照照明照照明照照明照照明照照明照照明照照明照" />
<TextView
android:id="@+id/statusView"
android:layout_width="0dp"
android:layout_height="20dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:lines="1"
android:textSize="13dp"
android:visibility="gone"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="@id/center"
app:layout_constraintStart_toStartOf="@id/center"
app:layout_constraintTop_toBottomOf="@id/deviceName"
app:layout_constraintBottom_toTopOf="@id/devFuncView"
tools:text="测试传感区域过程会怎么样呢测试传感区域过程会怎么样呢" />
<TextView
android:id="@+id/devFuncView"
android:layout_width="wrap_content"
android:layout_height="16dp"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="@string/quick_op_area"
android:textColor="#ff00cc99"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/statusView"
app:layout_constraintBottom_toBottomOf="@id/center"
app:layout_constraintStart_toStartOf="@id/center"
android:textSize="11dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/center" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/device_mgt_control" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingBottom="20dp">
<TextView
android:id="@+id/tvDeviceName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:textSize="20dp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/llDp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<Button
android:id="@+id/btnRemove"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:text="@string/device_mgt_remove" />
<Button
android:id="@+id/btnReset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:text="@string/device_mgt_reset" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff6f7fb">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/device_mgt_list" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="16dp">
<TextView
android:id="@+id/iconView"
android:layout_width="24dp"
android:layout_height="24dp"
android:gravity="center"
android:textColor="#888B8D"
android:textSize="24dp" />
<TextView
android:id="@+id/titleView"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:textColor="#e6000000"
android:textSize="13dp" />
<TextView
android:id="@+id/statusView"
android:layout_width="wrap_content"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:lines="1"
android:textColor="#e6000000"
android:textSize="11dp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp">
<TextView
android:id="@+id/tvDeviceName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp" />
<TextView
android:id="@+id/tvDeviceStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="60dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="20dp"
android:src="@drawable/ic_next" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp">
<TextView
android:id="@+id/tvDpName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp" />
<Switch
android:id="@+id/swDp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="20dp" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp">
<TextView
android:id="@+id/tvDpName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp" />
<EditText
android:id="@+id/etDp"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:maxLines="1"
android:layout_gravity="center_vertical|right"
android:imeOptions="actionDone"
android:layout_marginRight="20dp" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp">
<TextView
android:id="@+id/tvDpName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp" />
<Button
android:id="@+id/btnDp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="20dp" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceSubtitle1">
</TextView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp">
<TextView
android:id="@+id/tvDpName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp" />
<TextView
android:id="@+id/tvFault"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="20dp" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp">
<TextView
android:id="@+id/tvDpName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp" />
<com.google.android.material.slider.Slider
android:id="@+id/slDp"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="20dp" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp">
<TextView
android:id="@+id/tvDpName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp" />
<EditText
android:id="@+id/etDp"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:maxLines="1"
android:layout_gravity="center_vertical|right"
android:imeOptions="actionDone"
android:layout_marginRight="20dp" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="60dp"
android:paddingLeft="20dp"
android:text="@string/device_mgt_module_title"
android:textSize="18dp" />
<TextView
android:id="@+id/tvDeviceList"
style="@style/item_func"
android:text="@string/device_mgt_list" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:background="#FF888888"
android:visibility="gone" />
<TextView
android:id="@+id/tv_zb_gateway_list"
style="@style/item_func"
android:text="@string/device_zb_gateway_list"
android:visibility="gone" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_mgt_module_title">设备管理</string>
<string name="device_mgt_list">设备列表</string>
<string name="device_zb_gateway_list">Zigbee 网关列表</string>
<string name="device_zb_sub_device_list">子设备列表</string>
<string name="device_mgt_online">在线</string>
<string name="device_mgt_offline">离线</string>
<string name="device_mgt_control">设备控制</string>
<string name="device_mgt_remove">移除</string>
<string name="device_mgt_reset">恢复出厂设置</string>
<string name="quick_op_area">快捷操作</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_mgt_module_title">DEVICE MANAGEMENT</string>
<string name="device_mgt_list">Device List</string>
<string name="device_zb_gateway_list">Zigbee Gateway List</string>
<string name="device_zb_sub_device_list">Subdevice List</string>
<string name="device_mgt_online">Online</string>
<string name="device_mgt_offline">Offline</string>
<string name="device_mgt_control">Device Control</string>
<string name="device_mgt_remove">Remove</string>
<string name="device_mgt_reset">Reset Factory</string>
<string name="quick_op_area">quick op area</string>
</resources>
\ No newline at end of file
package com.example.management
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
\ No newline at end of file
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
\ No newline at end of file
#Mon Sep 04 11:45:20 CST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
/build
\ No newline at end of file
plugins {
id 'com.android.library'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0-alpha04'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation project(':base_res')
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.tuya.appsdk.sample.user
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.tuya.appsdk.sample.user.test", appContext.packageName)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tuya.appsdk.sample.user">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application>
<activity
android:name="com.tuya.appsdk.sample.home.list.HomeListActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name="com.tuya.appsdk.sample.home.detail.HomeDetailActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name="com.tuya.appsdk.sample.home.newHome.NewHomeActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name="com.tuya.appsdk.sample.home.edit.HomeEditActivity"
android:exported="false"
android:screenOrientation="portrait" />
</application>
</manifest>
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.home.detail
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.tuya.appsdk.sample.home.edit.HomeEditActivity
import com.tuya.appsdk.sample.user.R
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.bean.HomeBean
import com.thingclips.smart.home.sdk.bean.WeatherBean
import com.thingclips.smart.home.sdk.callback.IIGetHomeWetherSketchCallBack
import com.thingclips.smart.home.sdk.callback.IThingHomeResultCallback
import com.thingclips.smart.sdk.api.IResultCallback
/**
* Home Detail Sample
*
* @author yueguang [](mailto:developer@tuya.com)
* @since 2021/1/18 6:11 PM
*/
class HomeDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_acitivty_detail)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
val homeId = intent.getLongExtra("homeId", 0)
// Get home info
ThingHomeSdk.newHomeInstance(homeId).getHomeDetail(object : IThingHomeResultCallback {
override fun onSuccess(bean: HomeBean?) {
bean?.let {
findViewById<TextView>(R.id.tvHomeId).text = bean.homeId.toString()
findViewById<TextView>(R.id.tvHomeName).text = bean.name
findViewById<TextView>(R.id.tvHomeCity).text = bean.geoName
// Get home weather info
ThingHomeSdk.newHomeInstance(homeId).getHomeWeatherSketch(bean.lon,
bean.lat,
object : IIGetHomeWetherSketchCallBack {
override fun onSuccess(result: WeatherBean?) {
result?.let {
findViewById<TextView>(R.id.tvWeather).text = it.condition
findViewById<TextView>(R.id.tvHomeTemperature).text = it.temp
}
}
override fun onFailure(errorCode: String?, errorMsg: String?) {
Toast.makeText(
this@HomeDetailActivity,
"get home weather error->$errorMsg",
Toast.LENGTH_LONG
).show()
}
})
}
}
override fun onError(errorCode: String?, errorMsg: String?) {
}
})
// Edit home
findViewById<Button>(R.id.btnEdit).setOnClickListener {
val intent = Intent(this, HomeEditActivity::class.java)
intent.putExtra("homeId", homeId)
startActivity(intent)
}
// Dismiss home
findViewById<Button>(R.id.btnDismiss).setOnClickListener {
ThingHomeSdk.newHomeInstance(homeId).dismissHome(object : IResultCallback {
override fun onSuccess() {
finish()
}
override fun onError(code: String?, error: String?) {
}
})
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.home.edit
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.google.android.material.appbar.MaterialToolbar
import com.tuya.appsdk.sample.user.R
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.bean.HomeBean
import com.thingclips.smart.home.sdk.callback.IThingHomeResultCallback
import com.thingclips.smart.sdk.api.IResultCallback
/**
* Home Edit Sample
*
* @author yueguang [](mailto:developer@tuya.com)
* @since 2021/1/18 6:13 PM
*/
class HomeEditActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_activity_new_home)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
findViewById<MaterialToolbar>(R.id.topAppBar).title = getString(R.string.home_edit_title)
findViewById<Button>(R.id.btnDone).text = getString(R.string.home_done)
val homeId = intent.getLongExtra("homeId", 0)
// Get home info
ThingHomeSdk.newHomeInstance(homeId).getHomeDetail(object : IThingHomeResultCallback {
override fun onSuccess(bean: HomeBean?) {
bean?.let {
findViewById<EditText>(R.id.etHomeName).setText(bean.name)
findViewById<EditText>(R.id.etCity).setText(bean.geoName)
}
}
override fun onError(errorCode: String?, errorMsg: String?) {
}
})
// Update home info
findViewById<Button>(R.id.btnDone).setOnClickListener {
val strHomeName = findViewById<EditText>(R.id.etHomeName).text.toString()
val strCity = findViewById<EditText>(R.id.etCity).text.toString()
ThingHomeSdk.newHomeInstance(homeId).updateHome(
strHomeName,
// Get location by yourself, here just sample as Shanghai's location
120.52,
30.40,
strCity,
arrayListOf(),
false,
object : IResultCallback {
override fun onSuccess() {
Toast.makeText(
this@HomeEditActivity,
"Update success",
Toast.LENGTH_LONG
).show()
}
override fun onError(code: String?, error: String?) {
}
}
)
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.home.list
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.MaterialToolbar
import com.tuya.appsdk.sample.home.list.adapter.HomeListAdapter
import com.tuya.appsdk.sample.home.list.enum.HomeListPageType
import com.tuya.appsdk.sample.user.R
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.bean.HomeBean
import com.thingclips.smart.home.sdk.callback.IThingGetHomeListCallback
/**
* Home List Example
*
* @author yueguang [](mailto:developer@tuya.com)
* @since 2021/1/18 6:10 PM
*/
class HomeListActivity : AppCompatActivity() {
var type = HomeListPageType.LIST
lateinit var adapter: HomeListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_activity_list)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
// Get this page type
type = intent.getIntExtra("type", HomeListPageType.LIST)
// Set title
val topAppBar = findViewById<MaterialToolbar>(R.id.topAppBar)
topAppBar.title =
getString(if (type == HomeListPageType.SWITCH) R.string.home_switch_home else R.string.home_home_list)
// Set list
val rvList = findViewById<RecyclerView>(R.id.rvList)
adapter = HomeListAdapter(type)
rvList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
rvList.adapter = adapter
}
override fun onResume() {
super.onResume()
// Query home list from server
ThingHomeSdk.getHomeManagerInstance().queryHomeList(object : IThingGetHomeListCallback {
override fun onSuccess(homeBeans: MutableList<HomeBean>?) {
adapter.data = homeBeans as ArrayList<HomeBean>
adapter.notifyDataSetChanged()
}
override fun onError(errorCode: String?, error: String?) {
}
})
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.home.list.adapter
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.tuya.appsdk.sample.home.detail.HomeDetailActivity
import com.tuya.appsdk.sample.home.list.enum.HomeListPageType
import com.tuya.appsdk.sample.resource.HomeModel
import com.tuya.appsdk.sample.user.R
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.bean.HomeBean
/**
* Home List Adapter
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/20 2:32 PM
*/
class HomeListAdapter(val type: Int) : RecyclerView.Adapter<HomeListAdapter.ViewHolder>() {
var data: ArrayList<HomeBean> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val holder = ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.home_item_list, parent, false)
)
if (type == HomeListPageType.LIST) {
holder.ivIcon.setImageResource(R.drawable.ic_next)
holder.itemView.setOnClickListener {
// Home Detail
val intent = Intent(it.context, HomeDetailActivity::class.java)
intent.putExtra("homeId", data[holder.adapterPosition].homeId)
it.context.startActivity(intent)
}
} else if (type == HomeListPageType.SWITCH) {
holder.itemView.setOnClickListener {
// Switch Home
val bean = data[holder.adapterPosition]
ThingHomeSdk.newHomeInstance(bean.homeId)
HomeModel.INSTANCE.setCurrentHome(it.context, bean.homeId)
notifyDataSetChanged()
}
}
return holder
}
override fun getItemCount(): Int {
return data.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val bean = data[position]
holder.tvName.text = bean.name
if (type == HomeListPageType.SWITCH) {
// Switch Home Type
if (HomeModel.INSTANCE.getCurrentHome(holder.itemView.context) == bean.homeId) {
holder.ivIcon.setImageResource(R.drawable.ic_check)
} else {
holder.ivIcon.setImageResource(0)
}
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tvName: TextView = itemView.findViewById(R.id.tvName)
val ivIcon: ImageView = itemView.findViewById(R.id.ivIcon)
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.home.list.enum
/**
* Home List Page Type
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/20 2:12 PM
*/
interface HomeListPageType {
companion object {
/**
* for switch home
*/
const val SWITCH = 0
/**
* for get home list
*/
const val LIST = 1
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.home.main
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import com.tuya.appsdk.sample.home.list.HomeListActivity
import com.tuya.appsdk.sample.home.list.enum.HomeListPageType
import com.tuya.appsdk.sample.home.newHome.NewHomeActivity
import com.tuya.appsdk.sample.resource.HomeModel
import com.tuya.appsdk.sample.user.R
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.bean.HomeBean
import com.thingclips.smart.home.sdk.callback.IThingHomeResultCallback
/**
* Home Management Widget
*
* @author qianqi <a href="mailto:developer@tuya.com"/>
* @since 2021/1/9 5:06 PM
*/
class HomeFuncWidget {
lateinit var tvCurrentHomeName: TextView
lateinit var mContext: Context
fun render(context: Context): View {
mContext = context
val rootView =
LayoutInflater.from(context).inflate(R.layout.home_view_func, null, false)
initView(rootView)
return rootView
}
private fun initView(rootView: View) {
// Create Home
rootView.findViewById<TextView>(R.id.tvNewHome).setOnClickListener {
it.context.startActivity(Intent(it.context, NewHomeActivity::class.java))
}
// Switch Home
val tvCurrentHome = rootView.findViewById<TextView>(R.id.tvCurrentHome)
tvCurrentHome.setOnClickListener {
val intent = Intent(it.context, HomeListActivity::class.java)
intent.putExtra("type", HomeListPageType.SWITCH)
it.context.startActivity(intent)
}
tvCurrentHomeName = rootView.findViewById<TextView>(R.id.tvCurrentHomeName)
// Get Home List And Home Detail
rootView.findViewById<TextView>(R.id.tvHomeList).setOnClickListener {
val intent = Intent(it.context, HomeListActivity::class.java)
intent.putExtra("type", HomeListPageType.LIST)
it.context.startActivity(intent)
}
}
fun refresh() {
val currentHomeId = HomeModel.INSTANCE.getCurrentHome(tvCurrentHomeName.context)
if (currentHomeId != 0L) {
ThingHomeSdk.newHomeInstance(currentHomeId)
.getHomeDetail(object : IThingHomeResultCallback {
override fun onSuccess(bean: HomeBean?) {
bean?.let {
tvCurrentHomeName.text = it.name
if (it.name == null) {
HomeModel.INSTANCE.clear(mContext)
}
}
}
override fun onError(errorCode: String?, errorMsg: String?) {
}
})
}
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Tuya Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tuya.appsdk.sample.home.newHome
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.tuya.appsdk.sample.resource.HomeModel
import com.tuya.appsdk.sample.user.R
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.home.sdk.bean.HomeBean
import com.thingclips.smart.home.sdk.callback.IThingHomeResultCallback
/**
* Create Home Sample
*
* @author yueguang [](mailto:developer@tuya.com)
* @since 2021/1/18 6:09 PM
*/
class NewHomeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_activity_new_home)
val toolbar: Toolbar = findViewById<View>(R.id.topAppBar) as Toolbar
toolbar.setNavigationOnClickListener {
finish()
}
findViewById<Button>(R.id.btnDone).setOnClickListener {
//Create Home
val strHomeName = findViewById<EditText>(R.id.etHomeName).text.toString()
val strCity = findViewById<EditText>(R.id.etCity).text.toString()
ThingHomeSdk.getHomeManagerInstance().createHome(
strHomeName,
// Get location by yourself, here just sample as Shanghai's location
120.52,
30.40,
strCity,
arrayListOf(),
object : IThingHomeResultCallback {
override fun onSuccess(bean: HomeBean?) {
HomeModel.INSTANCE.setCurrentHome(this@NewHomeActivity, bean?.homeId
?: 0)
finish()
Toast.makeText(
this@NewHomeActivity,
"Create Home success",
Toast.LENGTH_LONG
).show()
}
override fun onError(errorCode: String?, errorMsg: String?) {
Toast.makeText(
this@NewHomeActivity,
"Create Home error->$errorMsg",
Toast.LENGTH_LONG
).show()
}
}
)
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/home_detail" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingBottom="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:background="@color/white"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/home_id"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<TextView
android:id="@+id/tvHomeId"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FF888888" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:background="@color/white"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/home_home_name"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<TextView
android:id="@+id/tvHomeName"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FF888888" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:background="@color/white"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/home_city"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<TextView
android:id="@+id/tvHomeCity"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FF888888" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:background="@color/white"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/home_weather"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<TextView
android:id="@+id/tvWeather"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#FF888888" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:background="@color/white"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/home_temperature"
android:textSize="18dp" />
<Space
android:layout_width="0dp"
android:layout_height="1px"
android:layout_weight="1" />
<TextView
android:id="@+id/tvHomeTemperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:id="@+id/btnEdit"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="50dp"
android:text="@string/home_edit" />
<Button
android:id="@+id/btnDismiss"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="50dp"
android:text="@string/home_dismiss" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/home_new_home" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_marginTop="20dp"
android:hint="@string/home_home_name"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etHomeName"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/home_city"
app:endIconMode="clear_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etCity"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnDone"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="50dp"
android:text="@string/home_create" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_gravity="center_vertical"
android:textSize="18dp"
android:id="@+id/tvName" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ivIcon"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="20dp" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/home_module_title"
android:paddingLeft="20dp"
android:layout_marginTop="50dp"
android:textSize="18dp" />
<TextView
style="@style/item_func"
android:id="@+id/tvNewHome"
android:text="@string/home_new_home" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:background="#FF888888" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvCurrentHome"
style="@style/item_func"
android:text="@string/home_current_home" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvCurrentHomeName"
android:layout_gravity="center_vertical|right"
android:layout_marginRight="40dp" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="20dp"
android:background="#FF888888" />
<TextView
style="@style/item_func"
android:id="@+id/tvHomeList"
android:text="@string/home_home_list" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="home_module_title">家庭管理</string>
<string name="home_new_home">添加家庭</string>
<string name="home_current_home">当前家庭</string>
<string name="home_home_list">家庭列表</string>
<string name="home_create">创建</string>
<string name="home_home_name">家庭名称</string>
<string name="home_city">城市</string>
<string name="home_switch_home">选择家庭</string>
<string name="home_id">家庭编号</string>
<string name="home_weather">天气情况</string>
<string name="home_temperature">温度</string>
<string name="home_detail">家庭详情</string>
<string name="home_edit">编辑</string>
<string name="home_dismiss">注销</string>
<string name="home_edit_title">编辑家庭</string>
<string name="home_done">完成</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="home_module_title">HOME MANAGEMENT</string>
<string name="home_new_home">New Home</string>
<string name="home_current_home">Current Home</string>
<string name="home_home_list">Home List</string>
<string name="home_create">Create</string>
<string name="home_home_name">Home Name</string>
<string name="home_city">City</string>
<string name="home_switch_home">Switch Home</string>
<string name="home_id">Home ID</string>
<string name="home_weather">Weather Condition</string>
<string name="home_temperature">Temperature</string>
<string name="home_detail">Home Detail</string>
<string name="home_edit">Edit</string>
<string name="home_dismiss">Dismiss</string>
<string name="home_edit_title">Edit Home</string>
<string name="home_done">Done</string>
</resources>
\ No newline at end of file
package com.tuya.appsdk.sample.user
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
\ No newline at end of file
/build
\ No newline at end of file
plugins {
id 'com.android.library'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
implementation 'com.alibaba:fastjson:1.1.67.android'
implementation "org.jetbrains.kotlin:kotlin-reflect:1.5.21"
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.9'
implementation project(':base_res')
api 'com.thingclips.smart:thingsmart-ipcsdk:5.1.0'
implementation 'com.thingclips.smart:thingsmart-ipc-camera-autotest:5.0.0'
implementation 'com.thingclips.smart:thingsmart-ipc-camera-cloudtool:5.0.0'
//timeline view
implementation 'com.thingclips.smart:thingsmart-ipc-camera-timeline:1.1.0'
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tuya.smart.android.demo.camera">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:requestLegacyExternalStorage="true">
<activity android:name=".CameraPanelActivity" android:screenOrientation="portrait"/>
<activity android:name=".CameraSettingActivity" android:screenOrientation="portrait"/>
<activity android:name=".AlarmDetectionActivity" android:screenOrientation="portrait"/>
<activity android:name=".CameraInfoActivity" android:screenOrientation="portrait"/>
<activity android:name=".CameraCloudVideoActivity" android:screenOrientation="portrait"/>
<activity android:name=".CameraPlaybackActivity" android:screenOrientation="portrait"/>
<activity android:name=".CameraCloudStorageActivity" android:screenOrientation="portrait"/>
<activity android:name=".CameraDoorBellActivity" android:screenOrientation="portrait"/>
</application>
</manifest>
\ No newline at end of file
package com.tuya.smart.android.demo.camera
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Message
import android.text.TextUtils
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.thingclips.smart.android.camera.sdk.api.IThingCameraMessage
import com.tuya.smart.android.demo.camera.adapter.AlarmDetectionAdapter
import com.tuya.smart.android.demo.camera.adapter.AlarmDetectionAdapter.OnItemListener
import com.tuya.smart.android.demo.camera.databinding.ActivityCameraMessageBinding
import com.tuya.smart.android.demo.camera.utils.*
import com.thingclips.smart.home.sdk.callback.IThingResultCallback
import com.thingclips.smart.ipc.messagecenter.bean.CameraMessageBean
import com.thingclips.smart.ipc.messagecenter.bean.CameraMessageClassifyBean
import java.text.SimpleDateFormat
import java.util.*
/**
* Alarm Detection Messages
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/27 4:11 PM
*/
class AlarmDetectionActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var devId: String
private lateinit var mWaitingDeleteCameraMessageList: MutableList<CameraMessageBean>
private lateinit var mCameraMessageList: MutableList<CameraMessageBean>
private var selectClassify: CameraMessageClassifyBean? = null
private var adapter: AlarmDetectionAdapter? = null
private var day = 0
private var year: Int = 0
private var month: Int = 0
private var offset = 0
private var mTyCameraMessage: IThingCameraMessage? = null
private lateinit var viewBinding: ActivityCameraMessageBinding
private val mHandler: Handler = object : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
Constants.ALARM_DETECTION_DATE_MONTH_FAILED -> handlAlarmDetectionDateFail(msg)
Constants.ALARM_DETECTION_DATE_MONTH_SUCCESS -> handlAlarmDetectionDateSuccess(msg)
Constants.MSG_GET_ALARM_DETECTION -> handleAlarmDetection()
Constants.MSG_DELETE_ALARM_DETECTION -> handleDeleteAlarmDetection()
}
super.handleMessage(msg)
}
}
private fun handleDeleteAlarmDetection() {
mCameraMessageList.removeAll(mWaitingDeleteCameraMessageList)
adapter?.updateAlarmDetectionMessage(mCameraMessageList)
adapter?.notifyDataSetChanged()
}
private fun handleAlarmDetection() {
adapter?.updateAlarmDetectionMessage(mCameraMessageList)
adapter?.notifyDataSetChanged()
}
private fun handlAlarmDetectionDateFail(msg: Message) {}
private fun handlAlarmDetectionDateSuccess(msg: Message) {
if (null != mTyCameraMessage && selectClassify != null) {
val time = DateUtils.getCurrentTime(year, month, day)
val startTime = DateUtils.getTodayStart(time).toInt()
val endTime = (DateUtils.getTodayEnd(time) - 1).toInt()
mTyCameraMessage?.getAlarmDetectionMessageList(
devId,
startTime,
endTime,
selectClassify!!.msgCode,
offset,
30,
object : IThingResultCallback<List<CameraMessageBean>?> {
override fun onSuccess(result: List<CameraMessageBean>?) {
if (result != null) {
offset += result.size
mCameraMessageList = result.toMutableList()
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_GET_ALARM_DETECTION,
Constants.ARG1_OPERATE_SUCCESS
)
)
} else {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_GET_ALARM_DETECTION,
Constants.ARG1_OPERATE_FAIL
)
)
}
}
override fun onError(errorCode: String, errorMessage: String) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_GET_ALARM_DETECTION,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityCameraMessageBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
viewBinding.toolbarView.setNavigationOnClickListener { onBackPressed() }
devId = intent.getStringExtra(Constants.INTENT_DEV_ID).toString()
initView()
initData()
initListener()
}
private fun initListener() {
viewBinding.queryBtn.setOnClickListener(this)
}
private fun initView() {
val simpleDateFormat = SimpleDateFormat("yyyy/MM/dd")
val date = Date(System.currentTimeMillis())
viewBinding.dateInputEdt.hint = simpleDateFormat.format(date)
viewBinding.dateInputEdt.setText(simpleDateFormat.format(date))
}
private fun initData() {
mWaitingDeleteCameraMessageList = arrayListOf()
mCameraMessageList = arrayListOf()
mTyCameraMessage = ThingIPCSdk.getMessage()?.createCameraMessage()
queryCameraMessageClassify(devId)
viewBinding.queryList.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
viewBinding.queryList.addItemDecoration(
DividerItemDecoration(
this,
DividerItemDecoration.VERTICAL
)
)
adapter = AlarmDetectionAdapter(this, mCameraMessageList)
adapter!!.setListener(object : OnItemListener {
override fun onLongClick(o: CameraMessageBean) {
deleteCameraMessageClassify(o)
}
override fun onItemClick(o: CameraMessageBean) {
//if type is video, jump to CameraCloudVideoActivity
if (o.attachVideos?.size!! > 0) {
// TODO: 2021/7/27
val intent =
Intent(this@AlarmDetectionActivity, CameraCloudVideoActivity::class.java)
val attachVideo = o.attachVideos[0]
val playUrl = attachVideo.substring(0, attachVideo.lastIndexOf('@'))
val encryptKey = attachVideo.substring(attachVideo.lastIndexOf('@') + 1)
intent.putExtra("playUrl", playUrl)
intent.putExtra("encryptKey", encryptKey)
intent.putExtra("devId", devId)
startActivity(intent)
}
}
})
viewBinding.queryList.adapter = adapter
}
private fun queryCameraMessageClassify(devId: String?) {
mTyCameraMessage?.queryAlarmDetectionClassify(
devId,
object : IThingResultCallback<List<CameraMessageClassifyBean>> {
override fun onSuccess(result: List<CameraMessageClassifyBean>) {
selectClassify = result[0]
mHandler.sendEmptyMessage(Constants.MOTION_CLASSIFY_SUCCESS)
}
override fun onError(errorCode: String, errorMessage: String) {
mHandler.sendEmptyMessage(Constants.MOTION_CLASSIFY_FAILED)
}
})
}
fun deleteCameraMessageClassify(cameraMessageBean: CameraMessageBean) {
mWaitingDeleteCameraMessageList.add(cameraMessageBean)
val ids: MutableList<String> = ArrayList()
ids.add(cameraMessageBean.id)
mTyCameraMessage?.deleteMotionMessageList(ids, object : IThingResultCallback<Boolean?> {
override fun onSuccess(result: Boolean?) {
mCameraMessageList.removeAll(mWaitingDeleteCameraMessageList)
mWaitingDeleteCameraMessageList.clear()
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_DELETE_ALARM_DETECTION,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onError(errorCode: String, errorMessage: String) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_DELETE_ALARM_DETECTION,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
}
override fun onClick(v: View) {
when (v.id) {
R.id.query_btn -> queryAlarmDetectionByMonth()
}
}
private fun queryAlarmDetectionByMonth() {
val inputStr: String = viewBinding.dateInputEdt.text.toString()
if (TextUtils.isEmpty(inputStr)) {
ToastUtil.shortToast(this, getString(R.string.not_input_query_data))
return
}
val substring = inputStr.split("/".toRegex()).toTypedArray()
year = substring[0].toInt()
month = substring[1].toInt()
mTyCameraMessage?.queryMotionDaysByMonth(
devId,
year,
month,
object : IThingResultCallback<List<String>> {
override fun onSuccess(result: List<String>) {
if (result.isNotEmpty()) {
Collections.sort(result)
day = result[result.size - 1].toInt()
}
mHandler.sendEmptyMessage(Constants.ALARM_DETECTION_DATE_MONTH_SUCCESS)
}
override fun onError(errorCode: String, errorMessage: String) {
mHandler.sendEmptyMessage(Constants.ALARM_DETECTION_DATE_MONTH_FAILED)
}
})
}
override fun onDestroy() {
super.onDestroy()
mHandler.removeCallbacksAndMessages(null)
mTyCameraMessage?.destroy()
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera
import android.Manifest
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.alibaba.fastjson.JSONObject
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.thingclips.smart.android.camera.sdk.bean.CloudStatusBean
import com.thingclips.smart.camera.annotation.CloudPlaySpeed
import com.thingclips.smart.camera.camerasdk.bean.ThingVideoFrameInfo
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.AbsP2pCameraListener
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.IRegistorIOTCListener
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.OperationCallBack
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.OperationDelegateCallBack
import com.thingclips.smart.camera.ipccamerasdk.cloud.IThingCloudCamera
import com.thingclips.smart.camera.middleware.cloud.bean.CloudDayBean
import com.thingclips.smart.camera.middleware.cloud.bean.TimePieceBean
import com.thingclips.smart.camera.middleware.cloud.bean.TimeRangeBean
import com.thingclips.smart.camera.middleware.widget.AbsVideoViewCallback
import com.thingclips.smart.home.sdk.callback.IThingResultCallback
import com.tuya.smart.android.demo.camera.adapter.CameraCloudVideoDateAdapter
import com.tuya.smart.android.demo.camera.adapter.CameraVideoTimeAdapter
import com.tuya.smart.android.demo.camera.databinding.ActivityCameraCloudStorageBinding
import com.tuya.smart.android.demo.camera.utils.Constants
import com.tuya.smart.android.demo.camera.utils.IPCSavePathUtils
import com.tuya.smart.android.demo.camera.utils.ToastUtil
import java.io.File
import java.nio.ByteBuffer
import java.util.*
/**
* CloudStorage Video
* @author hou qing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/28 10:24 AM
*/
class CameraCloudStorageActivity : AppCompatActivity() {
private var cloudCamera: IThingCloudCamera? = null
private val dayBeanList: MutableList<CloudDayBean> = ArrayList()
private val timePieceBeans = ArrayList<TimePieceBean>()
private var soundState = 0
private var devId: String? = null
private lateinit var viewBinding: ActivityCameraCloudStorageBinding
private var timeAdapter: CameraVideoTimeAdapter? = null
private var dateAdapter: CameraCloudVideoDateAdapter? = null
private val SERVICE_RUNNING = 10010
private val SERVICE_EXPIRED = 10011
private val NO_SERVICE = 10001
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityCameraCloudStorageBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
devId = intent.getStringExtra(Constants.INTENT_DEV_ID)
cloudCamera = ThingIPCSdk.getCloud()?.createCloudCamera()
viewBinding.cameraCloudVideoView.setViewCallback(object : AbsVideoViewCallback() {
override fun onCreated(o: Any) {
super.onCreated(o)
if (o is IRegistorIOTCListener) {
cloudCamera?.generateCloudCameraView(o)
}
}
})
viewBinding.cameraCloudVideoView.createVideoView(devId)
cloudCamera?.createCloudDevice(application.cacheDir.path, devId)
viewBinding.toolbarView.setNavigationOnClickListener { onBackPressed() }
cloudCamera?.queryCloudServiceStatus(
devId,
object : IThingResultCallback<CloudStatusBean> {
override fun onSuccess(result: CloudStatusBean) {
//Get cloud storage status
viewBinding.statusTv.text =
getString(R.string.cloud_status) + getServiceStatus(result.status)
if (result.status == SERVICE_EXPIRED || result.status == SERVICE_RUNNING) {
viewBinding.queryBtn.visibility = View.VISIBLE
viewBinding.llBottom.visibility = View.VISIBLE
}
}
override fun onError(errorCode: String, errorMessage: String) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.err_code) + errorCode
)
}
})
viewBinding.queryBtn.setOnClickListener {
//1. Get device cloud storage-related data
cloudCamera?.getCloudDays(devId,
TimeZone.getDefault().id,
object : IThingResultCallback<List<CloudDayBean>?> {
override fun onSuccess(result: List<CloudDayBean>?) {
if (result == null || result.isEmpty()) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.no_data)
)
} else {
dayBeanList.clear()
dayBeanList.addAll(result)
dateAdapter?.notifyDataSetChanged()
if (dayBeanList.size > 0)
viewBinding.dateRv.scrollToPosition(dayBeanList.size - 1)
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_suc)
)
}
}
override fun onError(errorCode: String, errorMessage: String) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.err_code) + errorCode
)
}
})
}
viewBinding.pauseBtn.setOnClickListener { pausePlayCloudVideo() }
viewBinding.resumeBtn.setOnClickListener { resumePlayCloudVideo() }
viewBinding.stopBtn.setOnClickListener { stopPlayCloudVideo() }
viewBinding.cameraMute.setOnClickListener { setMuteValue(if (soundState == 0) 1 else 0) }
viewBinding.cameraMute.isSelected = soundState == 0
viewBinding.snapshotBtn.setOnClickListener { snapshot() }
viewBinding.recordStart.setOnClickListener { startCloudRecordLocalMP4() }
viewBinding.recordEnd.setOnClickListener { stopCloudRecordLocalMP4() }
val mLayoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
viewBinding.timeRv.layoutManager = mLayoutManager
viewBinding.timeRv.addItemDecoration(DividerItemDecoration(this,
DividerItemDecoration.VERTICAL))
timeAdapter = CameraVideoTimeAdapter(this, timePieceBeans)
viewBinding.timeRv.adapter = timeAdapter
val ipcSavePathUtils = IPCSavePathUtils(this)
timeAdapter?.setListener(object : CameraVideoTimeAdapter.OnTimeItemListener {
override fun onClick(bean: TimePieceBean) {
playCloudDataWithStartTime(bean.startTime, bean.endTime, bean.isEvent)
}
override fun onLongClick(o: TimePieceBean) {
val openStorage = Constants.requestPermission(this@CameraCloudStorageActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Constants.EXTERNAL_STORAGE_REQ_CODE,
"open_storage")
if (openStorage) {
ToastUtil.shortToast(this@CameraCloudStorageActivity, "start download")
startCloudDataDownload(o.startTime.toLong(),
o.endTime.toLong(),
ipcSavePathUtils.recordPathSupportQ(
devId!!),
"download_" + System.currentTimeMillis() + ".mp4")
}
}
})
val mDateLayoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
viewBinding.dateRv.layoutManager = mDateLayoutManager
dateAdapter = CameraCloudVideoDateAdapter(this, dayBeanList)
viewBinding.dateRv.adapter = dateAdapter
dateAdapter?.setListener(object : CameraCloudVideoDateAdapter.OnTimeItemListener {
override fun onClick(dayBean: CloudDayBean?) {
getTimeLineInfoByTimeSlice(devId,
dayBean?.currentStartDayTime.toString(),
dayBean?.currentDayEndTime.toString())
}
})
}
private fun getServiceStatus(code: Int): String? {
return when (code) {
SERVICE_EXPIRED -> {
getString(R.string.ipc_sdk_service_expired)
}
SERVICE_RUNNING -> {
getString(R.string.ipc_sdk_service_running)
}
NO_SERVICE -> {
getString(R.string.ipc_sdk_no_service)
}
else -> {
code.toString()
}
}
}
override fun onResume() {
super.onResume()
viewBinding.cameraCloudVideoView.onResume()
cloudCamera?.let {
if (viewBinding.cameraCloudVideoView.createdView() is IRegistorIOTCListener) {
it.generateCloudCameraView(viewBinding.cameraCloudVideoView.createdView() as IRegistorIOTCListener)
}
it.registerP2PCameraListener(object : AbsP2pCameraListener() {
override fun receiveFrameDataForMediaCodec(
i: Int,
bytes: ByteArray,
i1: Int,
i2: Int,
bytes1: ByteArray,
b: Boolean,
i3: Int,
) {
}
override fun onReceiveFrameYUVData(
i: Int,
byteBuffer: ByteBuffer,
byteBuffer1: ByteBuffer,
byteBuffer2: ByteBuffer,
i1: Int,
i2: Int,
i3: Int,
i4: Int,
l: Long,
l1: Long,
l2: Long,
o: Any,
) {
}
override fun onReceiveFrameYUVData(
sessionId: Int,
y: ByteBuffer?,
u: ByteBuffer?,
v: ByteBuffer?,
videoFrameInfo: ThingVideoFrameInfo?,
camera: Any?,
) {
}
override fun onSessionStatusChanged(o: Any, i: Int, i1: Int) {}
override fun onReceiveAudioBufferData(
i: Int,
i1: Int,
i2: Int,
l: Long,
l1: Long,
l2: Long,
) {
}
override fun onReceiveSpeakerEchoData(byteBuffer: ByteBuffer, i: Int) {}
})
}
}
override fun onPause() {
super.onPause()
viewBinding.cameraCloudVideoView.onPause()
cloudCamera?.removeOnP2PCameraListener()
}
override fun onDestroy() {
super.onDestroy()
cloudCamera?.destroy()
cloudCamera?.deinitCloudCamera()
}
private fun startCloudRecordLocalMP4() {
cloudCamera?.let {
val path = getExternalFilesDir(null)!!.path + "/" + devId
val file = File(path)
if (!file.exists()) {
file.mkdirs()
}
it.startRecordLocalMp4(
path,
System.currentTimeMillis().toString() + ".mp4",
object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_suc)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_failed) + " errCode=$errCode"
)
}
})
}
}
/**
* record stop
*/
private fun stopCloudRecordLocalMP4() {
cloudCamera?.stopRecordLocalMp4(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_suc)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_failed) + " errCode=$errCode"
)
}
})
}
private fun snapshot() {
cloudCamera?.let {
val path = getExternalFilesDir(null)!!.path + "/" + devId
val file = File(path)
if (!file.exists()) {
file.mkdirs()
}
it.snapshot(path, object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_suc)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_failed) + " errCode=$errCode"
)
}
})
}
}
private fun setMuteValue(mute: Int) {
cloudCamera?.setCloudMute(mute, object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
soundState = Integer.valueOf(data)
viewBinding.cameraMute.isSelected = soundState == 0
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_failed)
)
}
})
}
private fun stopPlayCloudVideo() {
cloudCamera?.stopPlayCloudVideo(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_failed)
)
}
})
}
private fun resumePlayCloudVideo() {
cloudCamera?.resumePlayCloudVideo(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_failed) + " errCode=$errCode"
)
}
})
}
private fun pausePlayCloudVideo() {
cloudCamera?.pausePlayCloudVideo(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_failed) + " errCode=$errCode"
)
}
})
}
private fun playCloudDataWithStartTime(startTime: Int, endTime: Int, isEvent: Boolean) {
cloudCamera?.playCloudDataWithStartTime(startTime.toLong(), endTime.toLong(), isEvent,
object : OperationCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String, camera: Any) {
//playing
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int, camera: Any) {}
}, object : OperationCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String, camera: Any) {
//playCompleted
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int, camera: Any) {}
})
}
/**
* Get the time slice of the specified time.
*
* @param devId Device id.
* @param timeGT Start time.
* @param timeLT End time.
*/
private fun getTimeLineInfoByTimeSlice(devId: String?, timeGT: String?, timeLT: String?) {
timeGT ?: return
timeLT ?: return
cloudCamera?.getTimeLineInfo(
devId,
timeGT.toLong(),
timeLT.toLong(),
object : IThingResultCallback<List<TimePieceBean>?> {
override fun onSuccess(result: List<TimePieceBean>?) {
if (result == null || result.isEmpty()) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.no_data)
)
} else {
timePieceBeans.clear()
timePieceBeans.addAll(result)
timeAdapter?.notifyDataSetChanged()
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.operation_suc)
)
}
}
override fun onError(errorCode: String, errorMessage: String) {
ToastUtil.shortToast(
this@CameraCloudStorageActivity,
getString(R.string.err_code) + errorCode
)
}
})
}
/**
* Obtain the corresponding motion detection data according to the beginning and end of the time segment.
*
* @param devId Device id.
* @param timeGT Start time.
* @param timeLT End time.
* @param offset Which page, default 0
* @param limit The number of items pulled each time, the default is -1, which means all data
*/
fun getMotionDetectionByTimeSlice(
devId: String?,
timeGT: String,
timeLT: String,
offset: Int,
limit: Int,
) {
cloudCamera?.getMotionDetectionInfo(
devId,
timeGT.toLong(),
timeLT.toLong(),
offset,
limit,
object : IThingResultCallback<List<TimeRangeBean?>?> {
override fun onSuccess(result: List<TimeRangeBean?>?) {}
override fun onError(errorCode: String, errorMessage: String) {}
})
}
fun getTodayEnd(currentTime: Long): Long {
val calendar = Calendar.getInstance()
calendar.time = Date(currentTime)
calendar[Calendar.HOUR_OF_DAY] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.SECOND] = 0
calendar.add(Calendar.DAY_OF_MONTH, 1)
return calendar.timeInMillis
}
/**
* 设置倍数播放,在开始播放时进行设置
*/
private fun setPlayCloudDataSpeed(@CloudPlaySpeed speed: Int) {
cloudCamera?.setPlayCloudDataSpeed(speed, object : OperationCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String, camera: Any) {
// TODO " setPlayCloudDataSpeed onSuccess"
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int, camera: Any) {}
})
}
/**
* 查询 NVR 子设备云盘配置信息(子设备是否开通云存储)
*
* @param curNodeId 当前设备的nodeId
* @param parentDeviceId 当前设备的父设备id
*/
private fun getCloudDiskPro(curNodeId: String, parentDeviceId: String) {
cloudCamera?.queryCloudDiskProperty(parentDeviceId,
object : IThingResultCallback<JSONObject> {
override fun onSuccess(result: JSONObject) {
try { // 解析子列表
val jsonArray = result.getJSONArray("propertyList")
if (jsonArray != null && jsonArray.size > 0) {
for (i in jsonArray.indices) {
val jsonObject = jsonArray.getJSONObject(i)
val nodeId = jsonObject.getString("nodeId")
if (TextUtils.equals(curNodeId, nodeId)) {
val openStatus = jsonObject.getBoolean("openStatus")
if (openStatus) {
// TODO 已开通云存储
}
break
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onError(errorCode: String, errorMessage: String) {}
})
}
/**
* 云存储下载
*
* @param startTime
* @param stopTime
* @param folderPath
* @param mp4FileName
*/
private fun startCloudDataDownload(
startTime: Long,
stopTime: Long,
folderPath: String,
mp4FileName: String,
) {
cloudCamera?.startCloudDataDownload(startTime,
stopTime,
folderPath,
mp4FileName,
object : OperationCallBack {
override fun onSuccess(
sessionId: Int,
requestId: Int,
data: String,
camera: Any,
) {
}
override fun onFailure(
sessionId: Int,
requestId: Int,
errCode: Int,
camera: Any,
) {
}
},
{ sessionId: Int, requestId: Int, pos: Int, camera: Any? -> },
object : OperationCallBack {
override fun onSuccess(
sessionId: Int,
requestId: Int,
data: String,
camera: Any,
) {
runOnUiThread{
ToastUtil.shortToast(this@CameraCloudStorageActivity, "download finished")
}
}
override fun onFailure(
sessionId: Int,
requestId: Int,
errCode: Int,
camera: Any,
) {
}
})
}
/**
* 停止下载视频
*/
private fun stopCloudDataDownload() {
cloudCamera?.stopCloudDataDownload(object : OperationCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String, camera: Any) {}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int, camera: Any) {}
})
}
/**
* 删除云存储视频
*
* @param devId
* @param timeGT
* @param timeLT
* @param isAllDay
* @param timeZone
*/
private fun deleteCloudVideo(
devId: String,
timeGT: Long,
timeLT: Long,
isAllDay: Boolean,
timeZone: String,
) {
cloudCamera?.deleteCloudVideo(devId,
timeGT,
timeLT,
isAllDay,
timeZone,
object : IThingResultCallback<String?> {
override fun onSuccess(result: String?) {}
override fun onError(errorCode: String, errorMessage: String) {}
})
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera
import android.os.Bundle
import android.os.Handler
import android.os.Message
import android.util.Log
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.tuya.smart.android.demo.camera.databinding.ActivityCameraCloudVideoBinding
import com.tuya.smart.android.demo.camera.utils.Constants
import com.tuya.smart.android.demo.camera.utils.MessageUtil
import com.tuya.smart.android.demo.camera.utils.ToastUtil
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.AbsP2pCameraListener
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.IRegistorIOTCListener
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.OperationCallBack
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.OperationDelegateCallBack
import com.thingclips.smart.camera.ipccamerasdk.msgvideo.IThingCloudVideo
import com.thingclips.smart.camera.ipccamerasdk.p2p.ICameraP2P
import org.json.JSONObject
/**
* Cloud Video Message
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/27 5:50 PM
*/
class CameraCloudVideoActivity : AppCompatActivity() {
private val OPERATE_SUCCESS = 1
private val OPERATE_FAIL = 0
private val MSG_CLOUD_VIDEO_DEVICE = 1000
private var mCloudVideo: IThingCloudVideo? = null
private var playUrl: String? = null
private var encryptKey: String? = null
private var playDuration = 0
private var cachePath: String? = null
private var mDevId: String? = null
private var previewMute = ICameraP2P.MUTE
private lateinit var viewBinding: ActivityCameraCloudVideoBinding
private val mHandler: Handler = object : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_CLOUD_VIDEO_DEVICE -> startplay()
Constants.MSG_MUTE -> handleMute(msg)
}
super.handleMessage(msg)
}
}
private fun startplay() {
mCloudVideo?.playVideo(playUrl, 0, encryptKey, object : OperationCallBack {
override fun onSuccess(i: Int, i1: Int, s: String?, o: Any) {
Log.d("mcloudCamera", "onsuccess")
}
override fun onFailure(i: Int, i1: Int, i2: Int, o: Any) {}
}, object : OperationCallBack {
override fun onSuccess(i: Int, i1: Int, s: String?, o: Any) {
Log.d("mcloudCamera", "finish onsuccess")
}
override fun onFailure(i: Int, i1: Int, i2: Int, o: Any) {}
})
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityCameraCloudVideoBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
initData()
initView()
initCloudCamera()
}
override fun onDestroy() {
super.onDestroy()
mCloudVideo?.stopVideo(null)
mCloudVideo?.removeOnDelegateP2PCameraListener()
mCloudVideo?.deinitCloudVideo()
}
private fun initData() {
playUrl = intent.getStringExtra("playUrl")
encryptKey = intent.getStringExtra("encryptKey")
playDuration = intent.getIntExtra("playDuration", 0)
mDevId = intent.getStringExtra("devId")
cachePath = application.cacheDir.path
}
private fun initCloudCamera() {
mCloudVideo = ThingIPCSdk.getMessage()?.run { this.createVideoMessagePlayer() }
mCloudVideo?.let {
it.registerP2PCameraListener(object : AbsP2pCameraListener() {
override fun receiveFrameDataForMediaCodec(
i: Int,
bytes: ByteArray,
i1: Int,
i2: Int,
bytes1: ByteArray,
b: Boolean,
i3: Int
) {
super.receiveFrameDataForMediaCodec(i, bytes, i1, i2, bytes1, b, i3)
}
})
val listener: Any? = viewBinding.cameraCloudVideoView.createdView()
if (listener != null) {
it.generateCloudCameraView(listener as IRegistorIOTCListener)
}
it.createCloudDevice(cachePath, mDevId, object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String?) {
mHandler.sendMessage(
MessageUtil.getMessage(
MSG_CLOUD_VIDEO_DEVICE,
OPERATE_SUCCESS
)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {}
})
}
}
private fun initView() {
viewBinding.cameraCloudVideoView.createVideoView(mDevId)
viewBinding.btnPauseVideoMsg.setOnClickListener {
mCloudVideo?.pauseVideo(null)
}
viewBinding.btnResumeVideoMsg.setOnClickListener {
mCloudVideo?.resumeVideo(null)
}
viewBinding.cameraMute.setOnClickListener { muteClick() }
viewBinding.cameraMute.isSelected = true
}
private fun muteClick() {
mCloudVideo?.let {
val mute = if (previewMute == ICameraP2P.MUTE) ICameraP2P.UNMUTE else ICameraP2P.MUTE
it.setCloudVideoMute(mute, object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String?) {
val jsonObject = com.alibaba.fastjson.JSONObject.parseObject(data)
val value = jsonObject["mute"]
previewMute = Integer.valueOf(value.toString())
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_MUTE,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_MUTE,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
}
}
private fun handleMute(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
viewBinding.cameraMute.isSelected = previewMute == ICameraP2P.MUTE
} else {
ToastUtil.shortToast(
this@CameraCloudVideoActivity,
getString(R.string.operation_failed)
)
}
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.thingclips.smart.android.camera.sdk.bean.ThingDoorBellCallModel
import com.thingclips.smart.android.camera.sdk.callback.ThingSmartDoorBellObserver
import com.tuya.smart.android.demo.camera.databinding.ActivityCameraDoorbellCallingBinding
import com.tuya.smart.android.demo.camera.utils.Constants
import com.thingclips.smart.home.sdk.ThingHomeSdk
/**
* DoorBell Call
* @author hou qing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/28 11:22 AM
*/
class CameraDoorBellActivity : AppCompatActivity() {
private var mMessageId: String? = null
private val mDoorBellInstance = ThingIPCSdk.getDoorbell().ipcDoorBellManagerInstance
private lateinit var viewBinding: ActivityCameraDoorbellCallingBinding
private val mObserver: ThingSmartDoorBellObserver = object : ThingSmartDoorBellObserver() {
override fun doorBellCallDidCanceled(callModel: ThingDoorBellCallModel, isTimeOut: Boolean) {
if (isTimeOut) {
Toast.makeText(
this@CameraDoorBellActivity,
"Automatically hang up when the doorbell expires",
Toast.LENGTH_LONG
).show()
} else {
Toast.makeText(
this@CameraDoorBellActivity,
"The doorbell was cancelled by the device",
Toast.LENGTH_LONG
).show()
}
finish()
}
override fun doorBellCallDidHangUp(callModel: ThingDoorBellCallModel) {
Toast.makeText(this@CameraDoorBellActivity, "Hung up", Toast.LENGTH_LONG).show()
finish()
}
override fun doorBellCallDidAnsweredByOther(callModel: ThingDoorBellCallModel) {
Toast.makeText(
this@CameraDoorBellActivity,
"The doorbell is answered by another user",
Toast.LENGTH_LONG
).show()
mDoorBellInstance.refuseDoorBellCall(callModel.messageId)
finish()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityCameraDoorbellCallingBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
mMessageId = intent.getStringExtra(Constants.INTENT_MSGID)
initData()
initView()
}
private fun initData() {
if (TextUtils.isEmpty(mMessageId)) {
finish()
return
}
mDoorBellInstance.addObserver(mObserver)
}
@SuppressLint("SetTextI18n")
private fun initView() {
val model = mDoorBellInstance.getCallModelByMessageId(mMessageId)
val deviceBean = ThingHomeSdk.getDataInstance().getDeviceBean(model.devId)
viewBinding.tvState.text = """${deviceBean!!.getName()} call, waiting to be answered.."""
viewBinding.btnRefuse.setOnClickListener {
if (isAnsweredBySelf()) {
mDoorBellInstance.hangupDoorBellCall(mMessageId)
} else {
mDoorBellInstance.refuseDoorBellCall(mMessageId)
}
finish()
}
viewBinding.btnAccept.setOnClickListener {
mDoorBellInstance.answerDoorBellCall(mMessageId)
viewBinding.tvState.text = "The doorbell has been answered."
it.visibility = View.GONE
viewBinding.btnRefuse.setText(R.string.ipc_doorbell_hangup)
}
}
private fun isAnsweredBySelf(): Boolean {
val callModel = mDoorBellInstance.getCallModelByMessageId(mMessageId) ?: return false
return callModel.isAnsweredBySelf
}
override fun onDestroy() {
super.onDestroy()
mDoorBellInstance.removeObserver(mObserver)
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.tuya.smart.android.demo.camera.adapter.CameraInfoAdapter
import com.tuya.smart.android.demo.camera.databinding.ActivityCameraInfoBinding
import com.tuya.smart.android.demo.camera.utils.Constants
/**
* Camera Device Info
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/27 5:32 PM
*/
class CameraInfoActivity : AppCompatActivity() {
private var mDevId: String? = null
private lateinit var mData: MutableList<String>
private lateinit var viewBinding: ActivityCameraInfoBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityCameraInfoBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
mDevId = intent.getStringExtra(Constants.INTENT_DEV_ID)
initData()
initView()
}
private fun initData() {
mData = arrayListOf()
ThingIPCSdk.getCameraInstance()?.let {
mData.add(getString(R.string.low_power) + it.isLowPowerDevice(mDevId))
it.getCameraConfig(mDevId)?.run {
mData.add(getString(R.string.video_num) + this.videoNum)
mData.add(getString(R.string.default_definition) + parseClarity(this.defaultDefinition))
mData.add(getString(R.string.is_support_speaker) + this.isSupportSpeaker)
mData.add(getString(R.string.is_support_picK_up) + this.isSupportPickup)
mData.add(getString(R.string.is_support_talk) + this.isSupportChangeTalkBackMode)
mData.add(getString(R.string.default_talk_mode) + this.defaultTalkBackMode)
mData.add(getString(R.string.support_speed) + list2String(this.supportPlaySpeedList))
mData.add(getString(R.string.raw_data) + this.rawDataJsonStr)
}
}
}
private fun parseClarity(clarityMode: Int): String {
var info = getString(R.string.other)
if (clarityMode == 4) {
info = getString(R.string.hd)
} else if (clarityMode == 2) {
info = getString(R.string.sd)
}
return info
}
private fun list2String(list: List<Int>?): String {
return if (list != null && list.isNotEmpty()) {
val stringBuilder = StringBuilder()
for (i in list.indices) {
stringBuilder.append(list[i].toString())
if (i < list.size - 1) {
stringBuilder.append(", ")
}
}
stringBuilder.toString()
} else {
""
}
}
private fun initView() {
viewBinding.cameraInfoRy.layoutManager = LinearLayoutManager(this)
val cameraInfoAdapter = CameraInfoAdapter(mData)
viewBinding.cameraInfoRy.adapter = cameraInfoAdapter
cameraInfoAdapter.notifyDataSetChanged()
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera
import android.Manifest
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Message
import android.text.TextUtils
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.WindowManager
import android.widget.RelativeLayout
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.alibaba.fastjson.JSONObject
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.AbsP2pCameraListener
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.OnRenderDirectionCallback
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.OperationDelegateCallBack
import com.thingclips.smart.camera.ipccamerasdk.p2p.ICameraP2P
import com.thingclips.smart.camera.middleware.p2p.IThingSmartCameraP2P
import com.thingclips.smart.camera.middleware.widget.AbsVideoViewCallback
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.ipc.camera.autotesting.activity.AutoCameraTestingProgramListActivity
import com.thingclips.smart.ipc.camera.cloudtool.activity.CloudToolHomeActivity
import com.thingclips.smart.sdk.api.IResultCallback
import com.thingclips.smart.sdk.api.IThingDevice
import com.tuya.appsdk.sample.resource.HomeModel
import com.tuya.smart.android.demo.camera.databinding.ActivityCameraPanelBinding
import com.tuya.smart.android.demo.camera.utils.*
import java.io.File
import java.nio.ByteBuffer
/**
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 5:02 PM
*/
class CameraPanelActivity : AppCompatActivity(), View.OnClickListener {
companion object {
private const val ASPECT_RATIO_WIDTH = 9
private const val ASPECT_RATIO_HEIGHT = 16
private const val TAG = "CameraPanelActivity"
}
private var isSpeaking = false
private var isRecording = false
private var isPlay = false
private var previewMute = ICameraP2P.MUTE
private var videoClarity = ICameraP2P.HD
private var currVideoClarity: String? = null
private var devId: String? = null
private lateinit var viewBinding: ActivityCameraPanelBinding
private var mCameraP2P: IThingSmartCameraP2P<Any>? = null
@SuppressLint("HandlerLeak")
private val mHandler: Handler = object : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
Constants.MSG_CONNECT -> handleConnect(msg)
Constants.MSG_SET_CLARITY -> handleClarity(msg)
Constants.MSG_MUTE -> handleMute(msg)
Constants.MSG_SCREENSHOT -> handlesnapshot(msg)
Constants.MSG_VIDEO_RECORD_BEGIN -> ToastUtil.shortToast(
this@CameraPanelActivity,
getString(R.string.operation_suc)
)
Constants.MSG_VIDEO_RECORD_FAIL -> ToastUtil.shortToast(
this@CameraPanelActivity,
getString(R.string.operation_failed)
)
Constants.MSG_VIDEO_RECORD_OVER -> handleVideoRecordOver(msg)
Constants.MSG_TALK_BACK_BEGIN -> handleStartTalk(msg)
Constants.MSG_TALK_BACK_OVER -> handleStopTalk(msg)
Constants.MSG_GET_VIDEO_CLARITY -> handleGetVideoClarity(msg)
}
super.handleMessage(msg)
}
}
var cameraPTZHelper: CameraPTZHelper? = null
private fun handleStopTalk(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
ToastUtil.shortToast(
this@CameraPanelActivity,
getString(R.string.ipc_stop_talk) + getString(R.string.operation_suc)
)
} else {
ToastUtil.shortToast(
this@CameraPanelActivity,
getString(R.string.ipc_stop_talk) + getString(R.string.operation_failed)
)
}
}
private fun handleStartTalk(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
ToastUtil.shortToast(
this@CameraPanelActivity,
getString(R.string.ipc_start_talk) + getString(R.string.operation_suc)
)
} else {
ToastUtil.shortToast(
this@CameraPanelActivity,
getString(R.string.ipc_start_talk) + getString(R.string.operation_failed)
)
}
}
private fun handleVideoRecordOver(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
ToastUtil.shortToast(this@CameraPanelActivity, getString(R.string.operation_suc))
} else {
ToastUtil.shortToast(this@CameraPanelActivity, getString(R.string.operation_failed))
}
}
private fun handlesnapshot(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
ToastUtil.shortToast(this@CameraPanelActivity, getString(R.string.operation_suc))
} else {
ToastUtil.shortToast(this@CameraPanelActivity, getString(R.string.operation_failed))
}
}
private fun handleMute(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
viewBinding.cameraMute.isSelected = (previewMute == ICameraP2P.MUTE)
} else {
ToastUtil.shortToast(this@CameraPanelActivity, getString(R.string.operation_failed))
}
}
private fun handleClarity(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
viewBinding.cameraQuality.text =
if (videoClarity == ICameraP2P.HD) getString(R.string.hd) else getString(R.string.sd)
} else {
ToastUtil.shortToast(this@CameraPanelActivity, getString(R.string.operation_failed))
}
}
private fun handleConnect(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
preview();
} else {
ToastUtil.shortToast(this@CameraPanelActivity, getString(R.string.connect_failed))
}
}
private fun handleGetVideoClarity(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS && !TextUtils.isEmpty(currVideoClarity)) {
var info = getString(R.string.other)
if (currVideoClarity == ICameraP2P.HD.toString()) {
info = getString(R.string.hd)
} else if (currVideoClarity == ICameraP2P.STANDEND.toString()) {
info = getString(R.string.sd)
}
ToastUtil.shortToast(
this@CameraPanelActivity,
getString(R.string.get_current_clarity) + info
)
} else {
ToastUtil.shortToast(this@CameraPanelActivity, getString(R.string.operation_failed))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityCameraPanelBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
setSupportActionBar(viewBinding.toolbarView)
val windowManager = this.getSystemService(WINDOW_SERVICE) as WindowManager
val width = windowManager.defaultDisplay.width
val height = width * ASPECT_RATIO_WIDTH / ASPECT_RATIO_HEIGHT
val layoutParams = RelativeLayout.LayoutParams(width, height)
layoutParams.addRule(RelativeLayout.BELOW, R.id.toolbar_view)
viewBinding.cameraVideoViewRl.layoutParams = layoutParams
viewBinding.cameraMute.isSelected = true
initData()
initListener()
if (querySupportByDPID(DPConstants.PTZ_CONTROL)) {
//Cloud Station Control
viewBinding.cameraVideoView.setOnRenderDirectionCallback(object :
OnRenderDirectionCallback {
override fun onLeft() {
cameraPTZHelper?.ptzControl(DPConstants.PTZ_LEFT)
}
override fun onRight() {
cameraPTZHelper?.ptzControl(DPConstants.PTZ_RIGHT)
}
override fun onUp() {
cameraPTZHelper?.ptzControl(DPConstants.PTZ_UP)
}
override fun onDown() {
cameraPTZHelper?.ptzControl(DPConstants.PTZ_DOWN)
}
override fun onCancel() {
cameraPTZHelper?.ptzStop()
}
})
}
}
private var iTuyaDevice: IThingDevice? = null
private fun publishDps(dpId: String, value: Any) {
if (iTuyaDevice == null) {
iTuyaDevice = ThingHomeSdk.newDeviceInstance(devId)
}
val jsonObject = JSONObject()
jsonObject[dpId] = value
val dps = jsonObject.toString()
iTuyaDevice?.publishDps(dps, object : IResultCallback {
override fun onError(code: String, error: String) {
Log.e(TAG, "publishDps err $dps")
}
override fun onSuccess() {
Log.i(TAG, "publishDps suc $dps")
}
})
}
private fun querySupportByDPID(dpId: String): Boolean {
return ThingHomeSdk.getDataInstance().getDeviceBean(devId)?.run {
val dps = this.getDps()
return (dps != null && dps[dpId] != null)
} == true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_camera_panel, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.menu_remove_device) {
val dialog = AlertDialog.Builder(this)
.setCancelable(true)
.setTitle(getString(R.string.remove_device_dialog))
.setPositiveButton(getString(R.string.confirm)) { _: DialogInterface?, _: Int -> unBindDevice() }
.create()
dialog.show()
}
return super.onOptionsItemSelected(item)
}
private fun unBindDevice() {
ThingHomeSdk.newDeviceInstance(devId).removeDevice(object : IResultCallback {
override fun onError(s: String, s1: String) {
ToastUtil.shortToast(this@CameraPanelActivity, s1)
}
override fun onSuccess() {
mHandler.removeCallbacksAndMessages(null)
finish()
}
})
}
private fun initData() {
devId = intent.getStringExtra(Constants.INTENT_DEV_ID)
ThingIPCSdk.getCameraInstance()?.let {
mCameraP2P = it.createCameraP2P(devId)
}
viewBinding.cameraVideoView.setViewCallback(object : AbsVideoViewCallback() {
override fun onCreated(o: Any) {
super.onCreated(o)
mCameraP2P?.generateCameraView(o)
}
})
// viewBinding.cameraVideoView.createVideoView(p2pType)
viewBinding.cameraVideoView.createVideoView(devId)
if (mCameraP2P == null) showNotSupportToast()
devId?.let {
cameraPTZHelper = CameraPTZHelper(it)
}
cameraPTZHelper?.bindPtzBoard(findViewById(R.id.sv_ptz_board))
}
private fun showNotSupportToast() {
ToastUtil.shortToast(this@CameraPanelActivity, getString(R.string.not_support_device))
}
private fun initListener() {
mCameraP2P?.let {
viewBinding.cameraMute.setOnClickListener(this)
viewBinding.cameraQuality.setOnClickListener(this)
viewBinding.cameraControlBoard.speakTxt.setOnClickListener(this)
viewBinding.cameraControlBoard.recordTxt.setOnClickListener(this)
viewBinding.cameraControlBoard.photoTxt.setOnClickListener(this)
viewBinding.cameraControlBoard.replayTxt.setOnClickListener(this)
viewBinding.cameraControlBoard.cloudTxt.setOnClickListener(this)
viewBinding.cameraControlBoard.messageCenterTxt.setOnClickListener(this)
viewBinding.cameraControlBoard.debugTxt.setOnClickListener(this)
viewBinding.cameraControlBoard.ptzTxt.setOnClickListener(this)
}
viewBinding.toolbarView.setNavigationOnClickListener {
onBackPressed()
}
viewBinding.cameraControlBoard.settingTxt.setOnClickListener(this)
viewBinding.cameraControlBoard.infoTxt.setOnClickListener(this)
viewBinding.cameraControlBoard.getClarityTxt.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.camera_mute -> muteClick()
R.id.camera_quality -> setVideoClarity()
R.id.speak_Txt -> speakClick()
R.id.record_Txt -> recordClick()
R.id.photo_Txt -> snapShotClick()
R.id.replay_Txt -> {
val intent = Intent(this@CameraPanelActivity, CameraPlaybackActivity::class.java)
intent.putExtra(Constants.INTENT_DEV_ID, devId)
startActivity(intent)
}
R.id.setting_Txt -> {
val intent1 = Intent(this@CameraPanelActivity, CameraSettingActivity::class.java)
intent1.putExtra(Constants.INTENT_DEV_ID, devId)
startActivity(intent1)
}
R.id.cloud_Txt -> {
// 判断设备是否支持云存储
val isSupportCloudStorage =
ThingIPCSdk.getCloud()?.isSupportCloudStorage(devId) == true
if (!isSupportCloudStorage) {
ToastUtil.shortToast(this@CameraPanelActivity, getString(R.string.not_support))
return
}
val intent2 =
Intent(this@CameraPanelActivity, CameraCloudStorageActivity::class.java)
intent2.putExtra(Constants.INTENT_DEV_ID, devId)
startActivity(intent2)
}
R.id.message_center_Txt -> {
val intent3 = Intent(this@CameraPanelActivity, AlarmDetectionActivity::class.java)
intent3.putExtra(Constants.INTENT_DEV_ID, devId)
startActivity(intent3)
}
R.id.info_Txt -> {
val intent4 = Intent(this@CameraPanelActivity, CameraInfoActivity::class.java)
intent4.putExtra(Constants.INTENT_DEV_ID, devId)
startActivity(intent4)
}
R.id.get_clarity_Txt -> {
mCameraP2P?.getVideoClarity(object : OperationDelegateCallBack {
override fun onSuccess(i: Int, i1: Int, s: String) {
currVideoClarity = s
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_GET_VIDEO_CLARITY,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onFailure(i: Int, i1: Int, i2: Int) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_GET_VIDEO_CLARITY,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
}
R.id.debug_Txt -> {
val items = arrayOf(
getString(R.string.ipc_sdk_autotest_tools),
getString(R.string.ipc_cloud_debug_tools)
)
val builder = AlertDialog.Builder(this)
builder.setItems(
items
) { _: DialogInterface?, which: Int ->
if (which == 0) {
val intent = Intent(
this@CameraPanelActivity,
AutoCameraTestingProgramListActivity::class.java
)
startActivity(intent)
} else if (which == 1) {
val intent =
Intent(this@CameraPanelActivity, CloudToolHomeActivity::class.java)
intent.putExtra(
"extra_current_home_id",
HomeModel.INSTANCE.getCurrentHome(this)
)
startActivity(intent)
}
}
builder.setNegativeButton(
getString(R.string.ipc_close)
) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
builder.create().show()
}
R.id.ptz_Txt -> {
cameraPTZHelper?.show()
}
}
}
private fun preview() {
mCameraP2P?.startPreview(videoClarity, object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
Log.d(TAG, "start preview onSuccess")
isPlay = true
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
Log.d(TAG, "start preview onFailure, errCode: $errCode")
isPlay = false
}
})
}
private fun recordClick() {
if (!isRecording) {
val picPath = getExternalFilesDir(null)!!.path + "/" + devId
val file = File(picPath)
if (!file.exists()) {
file.mkdirs()
}
val fileName = System.currentTimeMillis().toString() + ".mp4"
mCameraP2P?.startRecordLocalMp4(
picPath,
fileName,
this@CameraPanelActivity,
object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
isRecording = true
mHandler.sendEmptyMessage(Constants.MSG_VIDEO_RECORD_BEGIN)
//returns the recorded thumbnail path (.jpg)
Log.i(TAG, "record :$data")
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
mHandler.sendEmptyMessage(Constants.MSG_VIDEO_RECORD_FAIL)
}
})
recordStatue(true)
} else {
mCameraP2P?.stopRecordLocalMp4(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
isRecording = false
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_VIDEO_RECORD_OVER,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
isRecording = false
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_VIDEO_RECORD_OVER,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
recordStatue(false)
}
}
private fun snapShotClick() {
val picPath = getExternalFilesDir(null)!!.path + "/" + devId
val file = File(picPath)
if (!file.exists()) file.mkdirs()
val fileName = System.currentTimeMillis().toString() + ".jpg"
mCameraP2P?.snapshot(
picPath,
fileName,
this@CameraPanelActivity,
object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_SCREENSHOT,
Constants.ARG1_OPERATE_SUCCESS
)
)
Log.i(TAG, "snapshot :$data")
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_SCREENSHOT,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
}
private fun muteClick() {
val mute = if (previewMute == ICameraP2P.MUTE) ICameraP2P.UNMUTE else ICameraP2P.MUTE
mCameraP2P?.setMute(mute, object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
previewMute = Integer.valueOf(data)
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_MUTE,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_MUTE,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
}
private fun speakClick() {
if (isSpeaking) {
mCameraP2P?.stopAudioTalk(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
isSpeaking = false
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_TALK_BACK_OVER,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
isSpeaking = false
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_TALK_BACK_OVER,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
} else {
if (Constants.hasRecordPermission()) {
mCameraP2P?.startAudioTalk(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
isSpeaking = true
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_TALK_BACK_BEGIN,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
isSpeaking = false
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_TALK_BACK_BEGIN,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
} else {
Constants.requestPermission(
this@CameraPanelActivity,
Manifest.permission.RECORD_AUDIO,
Constants.EXTERNAL_AUDIO_REQ_CODE,
"open_recording"
)
}
}
}
/**
* Set video quality, HD or SD
*/
private fun setVideoClarity() {
mCameraP2P?.setVideoClarity(
if (videoClarity == ICameraP2P.HD) ICameraP2P.STANDEND else ICameraP2P.HD,
object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
videoClarity = Integer.valueOf(data)
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_SET_CLARITY,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_SET_CLARITY,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
}
private fun recordStatue(isRecording: Boolean) {
viewBinding.cameraControlBoard.speakTxt.isEnabled = !isRecording
viewBinding.cameraControlBoard.photoTxt.isEnabled = !isRecording
viewBinding.cameraControlBoard.replayTxt.isEnabled = !isRecording
viewBinding.cameraControlBoard.recordTxt.isEnabled = true
viewBinding.cameraControlBoard.recordTxt.isSelected = isRecording
}
/**
* 设置智能画框的属性来控制框的样式(如框的颜色,画笔宽度,闪烁频率等),需要设备上报的 SEI 信息支持
*
* @param rectFeaturesJson 格式
* {
* "SmartRectFeature":[
* {
* "type":0,
* "index":0,
* "brushWidth":1,
* "flashFps":{
* "drawKeepFrames":2,
* "stopKeepFrames":2
* },
* "rgb":0xFF0000,
* "shape":0
* },
* {
* "type":0,
* "index":1,
* "brushWidth":2,
* "flashFps":{
* "drawKeepFrames":3,
* "stopKeepFrames":2
* },
* "rgb":0x00FF00,
* "shape":1
* }
* ]
* }
*/
private fun setSmartRectFeatures(rectFeaturesJson: String) {
mCameraP2P?.setSmartRectFeatures(rectFeaturesJson)
}
/**
* 支持拉伸/缩放,左右/上下镜像,90/180/270度旋转等。
*
* @param renderFeaturesJson 格式
* {
* "DecPostProcess":{
* "video":[
* {
* "restype":"4",
* "oldres":"944*1080",
* "newres":"1920*1080"
* },
* {
* "restype":"2",
* "oldres":"944*1080",
* "newres":"1920*1080"
* }
* ],
* "mirror":0,
* "rotation":2
* }
* }
*/
private fun setDeviceFeatures(renderFeaturesJson: String) {
mCameraP2P?.setDeviceFeatures(renderFeaturesJson)
}
override fun onResume() {
super.onResume()
viewBinding.cameraVideoView.onResume()
//must register again,or can't callback
mCameraP2P?.let {
it.registerP2PCameraListener(p2pCameraListener)
it.generateCameraView(viewBinding.cameraVideoView.createdView())
if (it.isConnecting) {
it.startPreview(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
isPlay = true
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
Log.d(TAG, "start preview onFailure, errCode: $errCode")
}
})
} else {
if (ThingIPCSdk.getCameraInstance()?.isLowPowerDevice(devId) == true) {
ThingIPCSdk.getDoorbell()?.wirelessWake(devId)
}
//Establishing a p2p channel
it.connect(devId, object : OperationDelegateCallBack {
override fun onSuccess(i: Int, i1: Int, s: String) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_CONNECT,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onFailure(i: Int, i1: Int, i2: Int) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_CONNECT,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
}
}
}
var reConnect = false
private val p2pCameraListener: AbsP2pCameraListener = object : AbsP2pCameraListener() {
override fun onReceiveSpeakerEchoData(pcm: ByteBuffer, sampleRate: Int) {
mCameraP2P?.let {
val length = pcm.capacity()
Log.d(TAG, "receiveSpeakerEchoData pcmlength $length sampleRate $sampleRate")
val pcmData = ByteArray(length)
pcm[pcmData, 0, length]
it.sendAudioTalkData(pcmData, length)
}
}
override fun onSessionStatusChanged(camera: Any?, sessionId: Int, sessionStatus: Int) {
super.onSessionStatusChanged(camera, sessionId, sessionStatus)
if (sessionStatus == -3 || sessionStatus == -105) {
// 遇到超时/鉴权失败,建议重连一次,避免循环调用
if (!reConnect) {
reConnect = true
mCameraP2P?.connect(devId, object : OperationDelegateCallBack {
override fun onSuccess(i: Int, i1: Int, s: String) {
mHandler.sendMessage(MessageUtil.getMessage(Constants.MSG_CONNECT,
Constants.ARG1_OPERATE_SUCCESS))
}
override fun onFailure(i: Int, i1: Int, i2: Int) {
mHandler.sendMessage(MessageUtil.getMessage(Constants.MSG_CONNECT,
Constants.ARG1_OPERATE_FAIL))
}
})
}
}
}
}
override fun onPause() {
super.onPause()
viewBinding.cameraVideoView.onPause()
mCameraP2P?.let {
if (isSpeaking) it.stopAudioTalk(null)
if (isPlay) {
it.stopPreview(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {}
})
isPlay = false
}
it.removeOnP2PCameraListener()
it.disconnect(object : OperationDelegateCallBack {
override fun onSuccess(i: Int, i1: Int, s: String) {}
override fun onFailure(i: Int, i1: Int, i2: Int) {}
})
}
}
override fun onDestroy() {
super.onDestroy()
mHandler.removeCallbacksAndMessages(null)
mCameraP2P?.destroyP2P()
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera
import android.Manifest
import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.Message
import android.text.TextUtils
import android.view.View
import android.view.WindowManager
import android.widget.RelativeLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.thingclips.smart.android.camera.timeline.OnBarMoveListener
import com.thingclips.smart.android.camera.timeline.TimeBean
import com.thingclips.smart.android.common.utils.L
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.AbsP2pCameraListener
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.OperationDelegateCallBack
import com.thingclips.smart.camera.camerasdk.thingplayer.callback.ProgressCallBack
import com.thingclips.smart.camera.ipccamerasdk.bean.MonthDays
import com.thingclips.smart.camera.ipccamerasdk.p2p.ICameraP2P
import com.thingclips.smart.camera.middleware.p2p.IThingSmartCameraP2P
import com.thingclips.smart.camera.middleware.widget.AbsVideoViewCallback
import com.tuya.smart.android.demo.camera.adapter.CameraPlaybackTimeAdapter
import com.tuya.smart.android.demo.camera.adapter.CameraPlaybackVideoDateAdapter
import com.tuya.smart.android.demo.camera.bean.RecordInfoBean
import com.tuya.smart.android.demo.camera.bean.TimePieceBean
import com.tuya.smart.android.demo.camera.databinding.ActivityCameraPlaybackBinding
import com.tuya.smart.android.demo.camera.utils.Constants
import com.tuya.smart.android.demo.camera.utils.IPCSavePathUtils
import com.tuya.smart.android.demo.camera.utils.MessageUtil
import com.tuya.smart.android.demo.camera.utils.ToastUtil
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.*
/**
* SdCard Video PlayBack
* @author hou qing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/27 8:27 PM
*/
class CameraPlaybackActivity : AppCompatActivity(), View.OnClickListener {
companion object {
private const val TAG = "CameraPlaybackActivity"
}
private lateinit var viewBinding: ActivityCameraPlaybackBinding
private lateinit var mCameraP2P: IThingSmartCameraP2P<Any>
private val ASPECT_RATIO_WIDTH = 9
private val ASPECT_RATIO_HEIGHT = 16
private var devId: String? = null
private var adapter: CameraPlaybackTimeAdapter? = null
private var dateAdapter: CameraPlaybackVideoDateAdapter? = null
private var queryDateList: MutableList<TimePieceBean>? = null
private var dateList: ArrayList<String>? = null
private var isPlayback = false
var mBackDataMonthCache: MutableMap<String, MutableList<String>>? = null
var mBackDataDayCache: MutableMap<String, MutableList<TimePieceBean>>? = null
private var mPlaybackMute = ICameraP2P.MUTE
private var isSupportPlaybackDownload = false
private var isSupportPlaybackDelete = false
private var isDownloading = false
@SuppressLint("HandlerLeak")
private val mHandler: Handler = object : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
Constants.MSG_MUTE -> handleMute(msg)
Constants.MSG_DATA_DATE -> handleDataDate(msg)
Constants.MSG_DATA_DATE_BY_DAY_SUCC, Constants.MSG_DATA_DATE_BY_DAY_FAIL -> handleDataDay(
msg
)
}
super.handleMessage(msg)
}
}
private fun handleDataDay(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
queryDateList?.clear()
//Timepieces with data for the query day
val timePieceBeans = mBackDataDayCache!![mCameraP2P.dayKey]
if (!timePieceBeans.isNullOrEmpty()) {
queryDateList?.addAll(timePieceBeans)
val timelineData: MutableList<TimeBean> = arrayListOf()
for ((startTime, endTime) in timePieceBeans) {
val b = TimeBean()
b.startTime = startTime
b.endTime = endTime
timelineData.add(b)
}
viewBinding.timeline.setCurrentTimeConfig(timePieceBeans[0].endTime * 1000L)
viewBinding.timeline.setRecordDataExistTimeClipsList(timelineData)
} else {
showErrorToast()
}
adapter?.notifyDataSetChanged()
}
}
private fun handleMute(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
viewBinding.cameraMute.isSelected = mPlaybackMute == ICameraP2P.MUTE
} else {
ToastUtil.shortToast(this@CameraPlaybackActivity, getString(R.string.operation_failed))
}
}
private fun showErrorToast() {
runOnUiThread {
ToastUtil.shortToast(this@CameraPlaybackActivity, getString(R.string.no_data))
}
}
private fun handleDataDate(msg: Message) {
if (msg.arg1 == Constants.ARG1_OPERATE_SUCCESS) {
dateList?.clear()
queryDateList?.clear();
val days = mBackDataMonthCache?.get(mCameraP2P.monthKey)
if (null == days || days.isEmpty()) {
showErrorToast()
return
}
viewBinding.rvMonth.scrollToPosition(dateList?.size?.minus(1) ?: 0)
val inputStr: String = viewBinding.dateInputEdt.text.toString()
if (inputStr.isNotEmpty() && inputStr.contains("/")) {
for (s in days) {
dateList?.add("$inputStr/$s")
}
}
dateAdapter?.notifyDataSetChanged()
}
}
private fun parsePlaybackData(obj: Any) {
val parseObject = JSON.parseObject(obj.toString(), RecordInfoBean::class.java)
if (parseObject.count != 0) {
if (parseObject.items.isNotEmpty()) {
mBackDataDayCache?.put(mCameraP2P.dayKey, parseObject.items)
}
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_DATA_DATE_BY_DAY_SUCC,
Constants.ARG1_OPERATE_SUCCESS
)
)
} else {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_DATA_DATE_BY_DAY_FAIL,
Constants.ARG1_OPERATE_FAIL
)
)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityCameraPlaybackBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
initView()
initData()
initListener()
}
override fun onPause() {
super.onPause()
viewBinding.cameraVideoView.onPause()
if (isPlayback) {
mCameraP2P.stopPlayBack(null)
}
mCameraP2P.removeOnP2PCameraListener()
if (isFinishing) {
mCameraP2P.disconnect(object : OperationDelegateCallBack {
override fun onSuccess(i: Int, i1: Int, s: String) {}
override fun onFailure(i: Int, i1: Int, i2: Int) {}
})
}
}
override fun onResume() {
super.onResume()
viewBinding.cameraVideoView.onResume()
mCameraP2P.registerP2PCameraListener(p2pCameraListener)
mCameraP2P.generateCameraView(viewBinding.cameraVideoView.createdView())
}
private val p2pCameraListener: AbsP2pCameraListener = object : AbsP2pCameraListener() {
override fun onReceiveFrameYUVData(
i: Int,
byteBuffer: ByteBuffer,
byteBuffer1: ByteBuffer,
byteBuffer2: ByteBuffer,
i1: Int,
i2: Int,
i3: Int,
i4: Int,
l: Long,
l1: Long,
l2: Long,
o: Any,
) {
super.onReceiveFrameYUVData(
i,
byteBuffer,
byteBuffer1,
byteBuffer2,
i1,
i2,
i3,
i4,
l,
l1,
l2,
o
)
viewBinding.timeline.setCurrentTimeInMillisecond(l * 1000L)
}
}
private fun initView() {
setSupportActionBar(viewBinding.toolbarView)
viewBinding.toolbarView.setNavigationOnClickListener { onBackPressed() }
//It is best to set the aspect ratio to 16:9
val windowManager = this.getSystemService(WINDOW_SERVICE) as WindowManager
val width = windowManager.defaultDisplay.width
val height: Int = width * ASPECT_RATIO_WIDTH / ASPECT_RATIO_HEIGHT
val layoutParams = RelativeLayout.LayoutParams(width, height)
layoutParams.addRule(RelativeLayout.BELOW, viewBinding.toolbarView.id)
viewBinding.cameraVideoViewRl.layoutParams = layoutParams
viewBinding.timeline.setOnBarMoveListener(object : OnBarMoveListener {
override fun onBarMove(l: Long, l1: Long, l2: Long) {}
override fun onBarMoveFinish(startTime: Long, endTime: Long, currentTime: Long) {
viewBinding.timeline.setCanQueryData()
viewBinding.timeline.setQueryNewVideoData(false)
if (startTime != -1L && endTime != -1L) {
playback(startTime.toInt(), endTime.toInt(), currentTime.toInt())
}
}
override fun onBarActionDown() {}
})
viewBinding.timeline.setOnSelectedTimeListener { _, _ -> }
}
private fun initData() {
mBackDataMonthCache = HashMap()
mBackDataDayCache = HashMap()
devId = intent.getStringExtra(Constants.INTENT_DEV_ID)
viewBinding.queryList.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
viewBinding.queryList.addItemDecoration(
DividerItemDecoration(
this,
DividerItemDecoration.VERTICAL
)
)
queryDateList = arrayListOf()
adapter = CameraPlaybackTimeAdapter(queryDateList as ArrayList<TimePieceBean>)
viewBinding.queryList.adapter = adapter
val mDateLayoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
viewBinding.rvMonth.layoutManager = mDateLayoutManager
dateList = ArrayList()
dateAdapter = CameraPlaybackVideoDateAdapter(this, dateList)
viewBinding.rvMonth.adapter = dateAdapter
val cameraInstance = ThingIPCSdk.getCameraInstance()
if (cameraInstance != null) {
mCameraP2P = cameraInstance.createCameraP2P(devId)
}
viewBinding.cameraVideoView.setViewCallback(object : AbsVideoViewCallback() {
override fun onCreated(o: Any) {
super.onCreated(o)
mCameraP2P.generateCameraView(viewBinding.cameraVideoView.createdView())
}
})
viewBinding.cameraVideoView.createVideoView(devId)
if (!mCameraP2P.isConnecting) {
mCameraP2P.connect(devId, object : OperationDelegateCallBack {
override fun onSuccess(i: Int, i1: Int, s: String) {
}
override fun onFailure(i: Int, i1: Int, i2: Int) {
mHandler.post {
ToastUtil.shortToast(
this@CameraPlaybackActivity,
"p2p connect failed "
)
}
}
})
}
viewBinding.cameraMute.isSelected = true
val simpleDateFormat = SimpleDateFormat("yyyy/MM")
val date = Date(System.currentTimeMillis())
viewBinding.dateInputEdt.setText(simpleDateFormat.format(date))
isSupportPlaybackDownload = isSupportPlaybackDownload()
isSupportPlaybackDelete = isSupportPlaybackDelete()
}
private fun initListener() {
viewBinding.cameraMute.setOnClickListener(this)
viewBinding.queryBtn.setOnClickListener(this)
viewBinding.pauseBtn.setOnClickListener(this)
viewBinding.resumeBtn.setOnClickListener(this)
viewBinding.stopBtn.setOnClickListener(this)
val ipcSavePathUtils = IPCSavePathUtils(this)
adapter?.setListener(object : CameraPlaybackTimeAdapter.OnTimeItemListener {
override fun onClick(timePieceBean: TimePieceBean) {
playback(timePieceBean.startTime, timePieceBean.endTime, timePieceBean.startTime)
}
override fun onLongClick(timePieceBean: TimePieceBean) {
val open_storage = Constants.requestPermission(this@CameraPlaybackActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Constants.EXTERNAL_STORAGE_REQ_CODE,
"open_storage")
if (isSupportPlaybackDownload && open_storage) {
ToastUtil.shortToast(this@CameraPlaybackActivity, "start download")
// 写文件权限申请
devId?.let { ipcSavePathUtils.recordPathSupportQ(it) }?.let {
startPlayBackDownload(timePieceBean.startTime,
timePieceBean.endTime,
it,
"download_" + System.currentTimeMillis() + ".mp4",
object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
L.i(TAG, " startCloudDataDownload onSuccess")
isDownloading = true
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
L.e(TAG,
" startCloudDataDownload onFailure= $errCode")
isDownloading = false
}
},
{ sessionId, requestId, pos, camera ->
L.i(TAG,
" startCloudDataDownload onProgress= $pos")
},
object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
L.i(TAG, " startCloudDataDownload Finished onSuccess")
isDownloading = false
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
L.e(TAG,
" startCloudDataDownload Finished onFailure= $errCode")
isDownloading = false
}
})
}
}
}
})
dateAdapter?.setListener(object : CameraPlaybackVideoDateAdapter.OnTimeItemListener {
override fun onClick(date: String?) {
showTimePieceAtDay(date)
}
})
}
private fun showTimePieceAtDay(inputStr: String?) {
if (null != inputStr && inputStr.isNotEmpty() && inputStr.contains("/")) {
val substring = inputStr.split("/".toRegex()).toTypedArray()
val year = substring[0].toInt()
val mouth = substring[1].toInt()
val day = substring[2].toInt()
mCameraP2P.queryRecordTimeSliceByDay(
year,
mouth,
day,
object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
L.e(TAG, "$inputStr --- $data")
parsePlaybackData(data)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
mHandler.sendEmptyMessage(Constants.MSG_DATA_DATE_BY_DAY_FAIL)
}
})
}
}
private fun playback(startTime: Int, endTime: Int, playTime: Int) {
mCameraP2P.startPlayBack(startTime, endTime, playTime, object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String?) {
isPlayback = true
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
isPlayback = false
}
}, object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String?) {
isPlayback = false
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
isPlayback = false
}
})
}
override fun onClick(v: View) {
when (v.id) {
R.id.camera_mute -> muteClick()
R.id.query_btn -> queryDayByMonthClick()
R.id.pause_btn -> pauseClick()
R.id.resume_btn -> resumeClick()
R.id.stop_btn -> stopClick()
}
}
private fun stopClick() {
mCameraP2P.stopPlayBack(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {}
})
isPlayback = false
}
private fun resumeClick() {
mCameraP2P.resumePlayBack(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
isPlayback = true
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {}
})
}
private fun pauseClick() {
mCameraP2P.pausePlayBack(object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
isPlayback = false
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {}
})
}
private fun startPlayback() {
if (!queryDateList.isNullOrEmpty()) {
queryDateList!![0].let {
mCameraP2P.startPlayBack(
it.startTime,
it.endTime,
it.startTime,
object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
isPlayback = true
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {}
},
object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
isPlayback = false
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {}
})
}
} else {
ToastUtil.shortToast(this, getString(R.string.no_data))
}
}
private fun queryDayByMonthClick() {
if (!mCameraP2P.isConnecting) {
ToastUtil.shortToast(this@CameraPlaybackActivity, getString(R.string.connect_first))
return
}
val inputStr: String = viewBinding.dateInputEdt.text.toString()
if (TextUtils.isEmpty(inputStr)) {
return
}
if (inputStr.contains("/")) {
val substring = inputStr.split("/".toRegex()).toTypedArray()
if (substring.size >= 2) {
try {
val year = substring[0].toInt()
val mouth = substring[1].toInt()
mCameraP2P.queryRecordDaysByMonth(
year,
mouth,
object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
val monthDays = JSONObject.parseObject(data, MonthDays::class.java)
mBackDataMonthCache!![mCameraP2P.monthKey] = monthDays.dataDays
L.e(TAG, "MonthDays --- $data")
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_DATA_DATE,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_DATA_DATE,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
} catch (e: Exception) {
ToastUtil.shortToast(this@CameraPlaybackActivity, getString(R.string.input_err))
}
}
}
}
private fun muteClick() {
val mute: Int = if (mPlaybackMute == ICameraP2P.MUTE) ICameraP2P.UNMUTE else ICameraP2P.MUTE
mCameraP2P.setMute(mute, object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {
mPlaybackMute = Integer.valueOf(data)
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_MUTE,
Constants.ARG1_OPERATE_SUCCESS
)
)
}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {
mHandler.sendMessage(
MessageUtil.getMessage(
Constants.MSG_MUTE,
Constants.ARG1_OPERATE_FAIL
)
)
}
})
}
/**
* 获取支持的回放倍数(建连之后)
*/
private fun getSupportPlaySpeedList(): List<Int?>? {
val cameraConfigInfo = ThingIPCSdk.getCameraInstance().getCameraConfig(devId)
return cameraConfigInfo?.supportPlaySpeedList
}
/**
* 开始回放成功后进行设置倍数回放
*
* @param speed 回放倍数
*/
private fun setPlayBackSpeed(speed: Int) {
mCameraP2P.setPlayBackSpeed(speed, object : OperationDelegateCallBack {
override fun onSuccess(sessionId: Int, requestId: Int, data: String) {}
override fun onFailure(sessionId: Int, requestId: Int, errCode: Int) {}
})
}
/**
* 是否支持回放下载
*/
private fun isSupportPlaybackDownload(): Boolean {
val cameraInstance = ThingIPCSdk.getCameraInstance()
if (cameraInstance != null) {
val cameraConfig = cameraInstance.getCameraConfig(devId)
if (cameraConfig != null) {
return cameraConfig.isSupportPlaybackDownload
}
}
return false
}
/**
* 回放视频下载,设备侧SDK 支持完整单个/多个连续片段的下载
* 需要在开启播放后
*
* @param downloadStartTime 传选择开始片段的开始时间
* @param downloadEndTime 传选择结束片段的结束时间
* @param folderPath 下载的路径
* @param fileName 下载保存文件名
* @param callBack 下载开始回调
* @param progressCallBack 下载进度回调
* @param finishCallBack 下载结束回调
*/
private fun startPlayBackDownload(
downloadStartTime: Int, downloadEndTime: Int, folderPath: String, fileName: String,
callBack: OperationDelegateCallBack,
progressCallBack: ProgressCallBack,
finishCallBack: OperationDelegateCallBack,
) {
mCameraP2P.startPlayBackDownload(downloadStartTime, downloadEndTime, folderPath, fileName,
callBack, progressCallBack, finishCallBack)
}
/**
* 暂停回放下载
*/
private fun pausePlayBackDownload(callBack: OperationDelegateCallBack) {
mCameraP2P.pausePlayBackDownload(callBack)
}
/**
* 恢复回放下载
*/
private fun resumePlayBackDownload(callBack: OperationDelegateCallBack) {
mCameraP2P.resumePlayBackDownload(callBack)
}
/**
* 停止回放下载
*/
private fun stopPlayBackDownload(callBack: OperationDelegateCallBack) {
mCameraP2P.stopPlayBackDownload(callBack)
}
/**
* 查询视频是否支持删除
*/
private fun isSupportPlaybackDelete(): Boolean {
val cameraInstance = ThingIPCSdk.getCameraInstance()
if (cameraInstance != null) {
val cameraConfig = cameraInstance.getCameraConfig(devId)
if (cameraConfig != null) {
return cameraConfig.isSupportPlaybackDelete
}
}
return false
}
/**
* 删除指定日期的视频
*
* @param day 日期 格式为 yyyyMMdd
* @param callBack 操作回调
* @param finishCallBack 结束回调
*/
private fun deletePlaybackDataByDay(
day: String, callBack: OperationDelegateCallBack,
finishCallBack: OperationDelegateCallBack,
) {
mCameraP2P.deletePlaybackDataByDay(day, callBack, finishCallBack)
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.alibaba.fastjson.JSONObject
import com.tuya.smart.android.demo.camera.CameraSettingActivity.DPCallback
import com.tuya.smart.android.demo.camera.databinding.ActivityCameraSettingBinding
import com.tuya.smart.android.demo.camera.utils.Constants
import com.tuya.smart.android.demo.camera.utils.DPConstants
import com.thingclips.smart.home.sdk.ThingHomeSdk
import com.thingclips.smart.sdk.api.IDevListener
import com.thingclips.smart.sdk.api.IResultCallback
import com.thingclips.smart.sdk.api.IThingDevice
/**
* SdCard Setting and WaterMark Setting
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/27 3:40 PM
*/
class CameraSettingActivity : AppCompatActivity() {
companion object {
private val TAG = CameraSettingActivity::class.java.simpleName
}
private var devId: String? = null
private var iTuyaDevice: IThingDevice? = null
private lateinit var viewBinding: ActivityCameraSettingBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityCameraSettingBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
setSupportActionBar(viewBinding.toolbarView)
viewBinding.toolbarView.setNavigationOnClickListener { onBackPressed() }
devId = intent.getStringExtra(Constants.INTENT_DEV_ID)
sdStatus()//SD card status
sdCardFormat()//SD card format
watermark()//Watermark switch
sdCardSave()//SD card recording switch
sdCardSaveModel()//SD card recording mode
record()
}
private fun sdCardSave() {
viewBinding.tvSdSaveVideo.text = getString(R.string.not_support)
queryValueByDPID("150")?.let {
viewBinding.tvSdSaveVideo.text = it.toString()
viewBinding.openSdSaveVideo.setOnClickListener {
publishDps(
"150",
!java.lang.Boolean.parseBoolean(viewBinding.tvSdSaveVideo.text.toString())
)
}
listenDPUpdate("150", object : DPCallback {
override fun callback(obj: Any) {
viewBinding.tvSdSaveVideo.text = obj.toString()
}
})
}
}
private fun sdCardSaveModel() {
viewBinding.tvSdSaveVideoModel.text = getString(R.string.not_support)
queryValueByDPID("151")?.let {
viewBinding.tvSdSaveVideoModel.text = it.toString()
listenDPUpdate("151", object : DPCallback {
override fun callback(obj: Any) {
viewBinding.tvSdSaveVideo.text = obj.toString()
}
})
}
}
private fun sdStatus() {
viewBinding.tvSdStatus.text = getString(R.string.not_support)
queryValueByDPID(DPConstants.SD_STATUS)?.let {
viewBinding.tvSdStatus.text = it.toString()
listenDPUpdate(DPConstants.SD_STATUS, object : DPCallback {
override fun callback(obj: Any) {
viewBinding.tvSdStatus.text = it.toString()
}
})
}
}
private fun sdCardFormat() {
viewBinding.tvSdFormat.text = getString(R.string.not_support)
queryValueByDPID(DPConstants.SD_STORAGE)?.let {
viewBinding.tvSdFormat.text = it.toString()
queryValueByDPID(DPConstants.SD_FORMAT)?.run {
viewBinding.btnSdFormat.visibility = View.VISIBLE
viewBinding.btnSdFormat.setOnClickListener {
publishDps(DPConstants.SD_FORMAT, true)
listenDPUpdate(DPConstants.SD_FORMAT_STATUS, object : DPCallback {
override fun callback(obj: Any) {
viewBinding.tvSdFormat.text = getString(R.string.format_status) + obj
if ("100" == obj.toString()) {
viewBinding.tvSdFormat.text =
queryValueByDPID(DPConstants.SD_STORAGE)?.toString()
}
}
})
}
}
}
}
private fun watermark() {
viewBinding.tvWatermark.text = getString(R.string.not_support)
queryValueByDPID(DPConstants.WATERMARK)?.let {
viewBinding.tvWatermark.text = it.toString()
viewBinding.btnWatermark.visibility = View.VISIBLE
viewBinding.btnWatermark.setOnClickListener {
publishDps(
DPConstants.WATERMARK,
!java.lang.Boolean.parseBoolean(viewBinding.tvWatermark.text.toString())
)
}
listenDPUpdate(DPConstants.WATERMARK, object : DPCallback {
override fun callback(obj: Any) {
viewBinding.tvWatermark.text = obj.toString()
}
})
}
}
private fun record() {
val dpId: String = DPConstants.SD_CARD_RECORD_SWITCH
val tv = findViewById<TextView>(R.id.tv_record)
val value = queryValueByDPID(dpId)
if (value != null) {
tv.text = value.toString()
val btn = findViewById<Button>(R.id.btn_record)
btn.visibility = View.VISIBLE
btn.setOnClickListener {
publishDps(
DPConstants.SD_CARD_RECORD_SWITCH,
!java.lang.Boolean.parseBoolean(tv.text.toString())
)
}
listenDPUpdate(dpId, object : DPCallback {
override fun callback(obj: Any) {
tv.text = obj.toString()
}
})
} else {
tv.text = getString(R.string.not_support)
}
}
private fun queryValueByDPID(dpId: String): Any? {
ThingHomeSdk.getDataInstance().getDeviceBean(devId)?.also {
return it.getDps()?.get(dpId)
}
return null
}
private fun publishDps(dpId: String, value: Any) {
if (iTuyaDevice == null) {
iTuyaDevice = ThingHomeSdk.newDeviceInstance(devId)
}
val jsonObject = JSONObject()
jsonObject[dpId] = value
val dps = jsonObject.toString()
iTuyaDevice!!.publishDps(dps, object : IResultCallback {
override fun onError(code: String, error: String) {
Log.e(TAG, "publishDps err $dps")
}
override fun onSuccess() {
Log.i(TAG, "publishDps suc $dps")
}
})
}
private fun listenDPUpdate(dpId: String, callback: DPCallback?) {
ThingHomeSdk.newDeviceInstance(devId).registerDevListener(object : IDevListener {
override fun onDpUpdate(devId: String, dpStr: String) {
callback?.let {
val dps: Map<String, Any> =
JSONObject.parseObject<Map<String, Any>>(dpStr, MutableMap::class.java)
if (dps.containsKey(dpId)) {
dps[dpId]?.let { it1 -> callback.callback(it1) }
}
}
}
override fun onRemoved(devId: String) {}
override fun onStatusChanged(devId: String, online: Boolean) {}
override fun onNetworkStatusChanged(devId: String, status: Boolean) {}
override fun onDevInfoUpdate(devId: String) {}
})
}
private interface DPCallback {
fun callback(obj: Any)
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera
import android.app.Application
import android.content.Context
import android.content.Intent
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.tuya.smart.android.demo.camera.utils.CameraDoorbellManager
import com.tuya.smart.android.demo.camera.utils.Constants
import com.tuya.smart.android.demo.camera.utils.FrescoManager
/**
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 3:33 PM
*/
class CameraUtils {
companion object {
fun init(application: Application) {
FrescoManager.initFresco(application)
CameraDoorbellManager.getInstance().init(application)
}
fun ipcProcess(context: Context, devId: String?): Boolean {
val cameraInstance = ThingIPCSdk.getCameraInstance()
if (cameraInstance?.isIPCDevice(devId) == true) {
val intent = Intent(context, CameraPanelActivity::class.java)
intent.putExtra(Constants.INTENT_DEV_ID, devId)
context.startActivity(intent)
return true
}
return false
}
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera
import android.util.Log
import com.thingclips.smart.android.common.utils.L
import com.thingclips.smart.android.common.utils.log.ILogInterception
import com.tuya.smart.android.demo.camera.IPCLogUtils
object IPCLogUtils {
fun init() {
L.setLogInterception(2) { _, tag, msg -> customLog(tag, msg) }
}
private fun customLog(var1: String, var2: String) {
//use your log system to print/save log
Log.i(var1, var2)
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.adapter
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.os.Environment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.backends.pipeline.Fresco
import com.thingclips.drawee.view.DecryptImageView
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.tuya.smart.android.demo.camera.R
import com.tuya.smart.android.demo.camera.databinding.CameraNewuiMoreMotionRecycleItemBinding
import com.tuya.smart.android.demo.camera.utils.ToastUtil
import com.tuya.smart.android.demo.camera.utils.BitmapUtils
import com.thingclips.smart.home.sdk.callback.IThingResultCallback
import com.thingclips.smart.ipc.messagecenter.bean.CameraMessageBean
import java.io.File
import java.lang.Exception
import java.util.*
/**
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/27 3:09 PM
*/
class AlarmDetectionAdapter(context: Context, cameraMessageBeans: MutableList<CameraMessageBean>) : RecyclerView.Adapter<AlarmDetectionAdapter.MyViewHolder>() {
private var cameraMessageBeans: MutableList<CameraMessageBean> = cameraMessageBeans
private var listener: OnItemListener? = null
private val context: Context = context
fun setListener(listener: OnItemListener) {
this.listener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = CameraNewuiMoreMotionRecycleItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val ipcVideoBean = cameraMessageBeans[position]
holder.mTvStartTime.text = ipcVideoBean.dateTime
holder.mTvDescription.text = ipcVideoBean.msgTypeContent
holder.itemView.setOnLongClickListener {
listener?.onLongClick(ipcVideoBean)
false
}
holder.itemView.setOnClickListener {
listener?.onItemClick(ipcVideoBean)
}
holder.showPicture(context,ipcVideoBean)
}
override fun getItemCount(): Int {
return cameraMessageBeans.size
}
fun updateAlarmDetectionMessage(messageBeans: MutableList<CameraMessageBean>) {
cameraMessageBeans.clear()
cameraMessageBeans.addAll(messageBeans)
notifyDataSetChanged()
}
inner class MyViewHolder(binding:CameraNewuiMoreMotionRecycleItemBinding) : RecyclerView.ViewHolder(binding.root) {
val mTvStartTime: TextView = binding.tvTimeRangeStartTime
val mTvDescription: TextView = binding.tvAlarmDetectionDescription
//don't forget Fresco.initialize(context, config)
private val mSnapshot: DecryptImageView = binding.ivTimeRangeSnapshot
private val mBtn: Button = binding.btnDownloadImg
fun showPicture(context :Context,cameraMessageBean: CameraMessageBean) {
val attachPics = cameraMessageBean.attachPics
mSnapshot.visibility = View.VISIBLE
if (attachPics.contains("@")) {
val index = attachPics.lastIndexOf("@")
try {
val decryption = attachPics.substring(index + 1)
val imageUrl = attachPics.substring(0, index)
mSnapshot.setImageURI(imageUrl, decryption.toByteArray())
//show download encryptedImg button
mBtn.visibility = View.VISIBLE
mBtn.setOnClickListener {
ThingIPCSdk.getTool()?.downloadEncryptedImg(imageUrl, decryption, object : IThingResultCallback<Bitmap?> {
override fun onSuccess(result: Bitmap?) {
// String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Camera/";
val path = Objects.requireNonNull<File>(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)).path + "/Camera"
val file = File(path)
if (!file.exists()) {
file.mkdirs()
}
if (BitmapUtils.savePhotoToSDCard(result, path)) {
ToastUtil.shortToast(context, context.getString(R.string.download_suc))
}
}
override fun onError(errorCode: String, errorMessage: String) {
Log.e("AlarmDetectionAdapter", "download encrypted img err: $errorCode$errorMessage")
}
})
}
} catch (e: Exception) {
e.printStackTrace()
}
} else {
var uri: Uri? = null
try {
uri = Uri.parse(attachPics)
} catch (e: Exception) {
e.printStackTrace()
}
mSnapshot.controller = Fresco.newDraweeControllerBuilder().setUri(uri).build()
}
}
}
interface OnItemListener {
fun onLongClick(o: CameraMessageBean)
fun onItemClick(o: CameraMessageBean)
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.adapter
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.thingclips.smart.camera.middleware.cloud.bean.CloudDayBean
import com.tuya.smart.android.demo.camera.R
class CameraCloudVideoDateAdapter(context: Context?, dateList: List<CloudDayBean>) :
RecyclerView.Adapter<CameraCloudVideoDateAdapter.MyViewHolder?>() {
private val mInflater: LayoutInflater
private val dateList: List<CloudDayBean>
private var listener: OnTimeItemListener? = null
init {
mInflater = LayoutInflater.from(context)
this.dateList = dateList
}
fun setListener(listener: OnTimeItemListener?) {
this.listener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(mInflater.inflate(R.layout.activity_camera_video_date_tem,
parent,
false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val bean: CloudDayBean = dateList[position]
holder.mDate.text = bean.uploadDay
holder.itemView.setOnClickListener {
listener?.onClick(bean)
}
}
override fun getItemCount(): Int {
return dateList.size
}
inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var mDate: TextView
init {
mDate = view.findViewById<TextView>(R.id.tv_date)
}
}
interface OnTimeItemListener {
fun onClick(date: CloudDayBean?)
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.tuya.smart.android.demo.camera.databinding.RecyclerCameraInfoBinding
/**
* TODO feature
*
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/27 2:50 PM
*/
class CameraInfoAdapter(data: MutableList<String>) : RecyclerView.Adapter<CameraInfoAdapter.ViewHolder>() {
private var data: MutableList<String> = data
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = RecyclerCameraInfoBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = data[position]
holder.textView.maxLines = 5
}
override fun getItemCount(): Int {
return data!!.size
}
class ViewHolder(binding: RecyclerCameraInfoBinding) : RecyclerView.ViewHolder(binding.root) {
val textView: TextView = binding.recycleItemInfoText
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.tuya.smart.android.demo.camera.R
import com.tuya.smart.android.demo.camera.bean.TimePieceBean
import com.tuya.smart.android.demo.camera.databinding.ActivityCameraPlaybackTimeTemBinding
import java.text.SimpleDateFormat
import java.util.*
/**
* TODO feature
*
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/27 8:28 PM
*/
class CameraPlaybackTimeAdapter(beans: MutableList<TimePieceBean>) :
RecyclerView.Adapter<CameraPlaybackTimeAdapter.MyViewHolder>() {
private val timePieceBeans: MutableList<TimePieceBean> = beans
private var listener: OnTimeItemListener? = null
fun setListener(listener: OnTimeItemListener?) {
this.listener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding =
ActivityCameraPlaybackTimeTemBinding.inflate(LayoutInflater.from(parent.context),
parent,
false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val ipcVideoBean = timePieceBeans[position]
holder.mTvStartTime.text = timeFormat(ipcVideoBean.startTime * 1000L)
val lastTime = ipcVideoBean.endTime - ipcVideoBean.startTime
holder.mTvDuration.text =
holder.mTvDuration.context.getString(R.string.duration) + changeSecond(lastTime)
holder.itemView.setOnClickListener {
listener?.onClick(ipcVideoBean)
}
holder.itemView.setOnLongClickListener {
listener?.onLongClick(ipcVideoBean)
true
}
}
fun timeFormat(time: Long): String? {
val sdf = SimpleDateFormat("HH:mm:ss")
val date = Date(time)
return sdf.format(date)
}
fun changeSecond(seconds: Int): String {
val timer = StringBuilder()
var temp: Int = seconds / 3600
timer.append(if (temp < 10) "0$temp:" else "$temp:")
temp = seconds % 3600 / 60
timer.append(if (temp < 10) "0$temp:" else "$temp:")
temp = seconds % 3600 % 60
timer.append(if (temp < 10) "0$temp" else "" + temp)
return timer.toString()
}
override fun getItemCount(): Int {
return timePieceBeans.size
}
class MyViewHolder(viewBinding: ActivityCameraPlaybackTimeTemBinding) :
RecyclerView.ViewHolder(viewBinding.root) {
var mTvStartTime: TextView = viewBinding.timeStart
var mTvDuration: TextView = viewBinding.timeDuration
}
interface OnTimeItemListener {
fun onClick(o: TimePieceBean)
fun onLongClick(o: TimePieceBean)
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.adapter
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.tuya.smart.android.demo.camera.R
class CameraPlaybackVideoDateAdapter(context: Context?, dateList: List<String>?) :
RecyclerView.Adapter<CameraPlaybackVideoDateAdapter.MyViewHolder?>() {
private val mInflater: LayoutInflater
private val dateList: List<String>?
private var listener: OnTimeItemListener? = null
init {
mInflater = LayoutInflater.from(context)
this.dateList = dateList
}
fun setListener(listener: OnTimeItemListener?) {
this.listener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(mInflater.inflate(R.layout.activity_camera_video_date_tem,
parent,
false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val str = dateList?.get(position)
holder.mDate.text = str
holder.itemView.setOnClickListener(View.OnClickListener {
listener?.onClick(str)
})
}
override fun getItemCount(): Int {
return dateList?.size ?: 0
}
inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var mDate: TextView
init {
mDate = view.findViewById<TextView>(R.id.tv_date)
}
}
interface OnTimeItemListener {
fun onClick(date: String?)
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.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.thingclips.smart.camera.middleware.cloud.bean.TimePieceBean
import com.tuya.smart.android.demo.camera.R
import java.text.SimpleDateFormat
import java.util.*
/**
* Created by huangdaju on 2018/3/5.
*/
class CameraVideoTimeAdapter(context: Context?, timePieceBeans: List<TimePieceBean>) :
RecyclerView.Adapter<CameraVideoTimeAdapter.MyViewHolder?>() {
private val mInflater: LayoutInflater
private val timePieceBeans: List<TimePieceBean>
private var listener: OnTimeItemListener? = null
init {
mInflater = LayoutInflater.from(context)
this.timePieceBeans = timePieceBeans
}
fun setListener(listener: OnTimeItemListener?) {
this.listener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(mInflater.inflate(R.layout.activity_camera_video_time_tem,
parent,
false))
}
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val ipcVideoBean: TimePieceBean = timePieceBeans[position]
holder.mTvStartTime.text = timeFormat(ipcVideoBean.startTime * 1000L)
val lastTime: Int = ipcVideoBean.endTime - ipcVideoBean.startTime
holder.mTvDuration.text = holder.mTvDuration.context
.getString(R.string.duration) + changeSecond(lastTime)
holder.itemView.setOnClickListener(View.OnClickListener {
listener?.onClick(ipcVideoBean)
})
holder.itemView.setOnLongClickListener {
listener?.onLongClick(ipcVideoBean)
true
}
}
override fun getItemCount(): Int {
return timePieceBeans.size
}
inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var mTvStartTime: TextView
var mTvDuration: TextView
init {
mTvStartTime = view.findViewById(R.id.time_start)
mTvDuration = view.findViewById(R.id.time_duration)
}
}
interface OnTimeItemListener {
fun onClick(o: TimePieceBean)
fun onLongClick(o: TimePieceBean)
}
companion object {
fun timeFormat(time: Long): String {
val sdf = SimpleDateFormat("HH:mm:ss")
val date = Date(time)
return sdf.format(date)
}
fun changeSecond(seconds: Int): String {
val timer = StringBuilder()
var temp: Int = seconds / 3600
timer.append(if (temp < 10) "0$temp:" else "$temp:")
temp = seconds % 3600 / 60
timer.append(if (temp < 10) "0$temp:" else "$temp:")
temp = seconds % 3600 % 60
timer.append(if (temp < 10) "0$temp" else "" + temp)
return timer.toString()
}
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.bean
/**
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 4:57 PM
*/
data class RecordInfoBean (val count:Int, val items:MutableList<TimePieceBean>)
\ No newline at end of file
package com.tuya.smart.android.demo.camera.bean
/**
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 4:53 PM
*/
data class TimePieceBean(val startTime:Int,val endTime:Int,val playTime:Int,val prefix:Int) :Comparable<TimePieceBean> {
override fun compareTo(other: TimePieceBean): Int {
return if (endTime >= other.endTime) 1 else -1
}
override fun toString(): String {
return "TimePieceBean(startTime=$startTime, endTime=$endTime, playTime=$playTime, prefix=$prefix)"
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
import android.graphics.Bitmap
import android.util.Log
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
object BitmapUtils {
private const val TAG = "BitmapUtils"
@JvmOverloads
fun savePhotoToSDCard(
photoBitmap: Bitmap?,
path: String,
name: String? = System.currentTimeMillis().toString() + ".png"
): Boolean {
var isSave = false
val dir = File(path)
if (!dir.exists()) {
if (!dir.mkdirs()) {
Log.e(TAG, "savePhotoToSDCard create file fail, path: $path")
}
}
val photoFile = File(path, name)
var fileOutputStream: FileOutputStream? = null
try {
fileOutputStream = FileOutputStream(photoFile)
if (photoBitmap != null) {
if (photoBitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)) {
fileOutputStream.flush()
}
isSave = true
}
} catch (e: FileNotFoundException) {
if (!photoFile.delete()) {
Log.e(TAG, "savePhotoToSDCard delete photoFile fail, path: $path")
}
e.printStackTrace()
isSave = false
} catch (e: IOException) {
if (!photoFile.delete()) {
Log.e(TAG, "savePhotoToSDCard try catch delete file fail, path: $path")
}
e.printStackTrace()
isSave = false
} finally {
try {
fileOutputStream?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
return isSave
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
import android.app.Application
import android.content.Intent
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.thingclips.smart.android.camera.sdk.api.IThingIPCDoorBellManager
import com.thingclips.smart.android.camera.sdk.bean.ThingDoorBellCallModel
import com.thingclips.smart.android.camera.sdk.callback.ThingSmartDoorBellObserver
import com.thingclips.smart.android.common.utils.L
import com.tuya.smart.android.demo.camera.CameraDoorBellActivity
import com.thingclips.smart.sdk.bean.DeviceBean
/**
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 3:36 PM
*/
class CameraDoorbellManager {
companion object {
private const val TAG = "CameraDoorbellManager"
const val EXTRA_AC_DOORBELL = "ac_doorbell"
private val INSTANCE: CameraDoorbellManager by lazy { CameraDoorbellManager() }
fun getInstance(): CameraDoorbellManager {
return INSTANCE
}
}
private val doorBellInstance: IThingIPCDoorBellManager by lazy { ThingIPCSdk.getDoorbell().ipcDoorBellManagerInstance }
fun init(application: Application) {
doorBellInstance.addObserver(object : ThingSmartDoorBellObserver() {
override fun doorBellCallDidReceivedFromDevice(
callModel: ThingDoorBellCallModel?,
deviceModel: DeviceBean?
) {
L.d(TAG, "Receiving a doorbell call")
callModel?.let {
val type = it.type
val messageId = it.messageId
if (EXTRA_AC_DOORBELL == type) {
val intent = Intent(
application.applicationContext,
CameraDoorBellActivity::class.java
)
intent.putExtra(Constants.INTENT_MSGID, messageId)
intent.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
application.applicationContext.startActivity(intent)
}
}
}
})
}
fun deInit() {
doorBellInstance.removeAllObservers()
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.View.OnLongClickListener
import android.view.View.OnTouchListener
import android.widget.TextView
import android.widget.Toast
import com.alibaba.fastjson.JSONObject
import com.thingclips.drawee.view.DecryptImageView
import com.thingclips.smart.android.camera.sdk.ThingIPCSdk
import com.thingclips.smart.android.camera.sdk.bean.CollectionPointBean
import com.thingclips.smart.android.camera.sdk.constant.PTZDPModel
import com.tuya.smart.android.demo.camera.R
import com.thingclips.smart.android.device.bean.EnumSchemaBean
import com.thingclips.smart.home.sdk.callback.IThingResultCallback
import com.thingclips.smart.sdk.api.IResultCallback
import java.util.*
/**
* Created by HuangXin on 2021/9/25.
*/
@Suppress("DEPRECATION")
class CameraPTZHelper(devId: String) : View.OnClickListener, OnLongClickListener {
private val thingIPCPTZ = ThingIPCSdk.getPTZInstance(devId)
private var ptzBoard: View? = null
private lateinit var context: Context
private var progressDialog: android.app.ProgressDialog? = null
private var collectionPointSize = 0
@SuppressLint("ClickableViewAccessibility")
fun bindPtzBoard(ptzBoard: View) {
this.ptzBoard = ptzBoard
context = ptzBoard.context
progressDialog = android.app.ProgressDialog(ptzBoard.context)
ptzBoard.findViewById<View>(R.id.tv_ptz_close).setOnClickListener(this)
//PTZ Control
ptzBoard.findViewById<View>(R.id.tv_ptz_left)
.setOnTouchListener(PTZControlTouchListener("6"))
ptzBoard.findViewById<View>(R.id.tv_ptz_top)
.setOnTouchListener(PTZControlTouchListener("0"))
ptzBoard.findViewById<View>(R.id.tv_ptz_right)
.setOnTouchListener(PTZControlTouchListener("2"))
ptzBoard.findViewById<View>(R.id.tv_ptz_bottom)
.setOnTouchListener(PTZControlTouchListener("4"))
val isPTZControl = thingIPCPTZ.querySupportByDPCode(PTZDPModel.DP_PTZ_CONTROL)
ptzBoard.findViewById<View>(R.id.group_ptz_control).visibility =
if (isPTZControl) View.VISIBLE else View.GONE
//Focal
ptzBoard.findViewById<View>(R.id.tv_focal_increase)
.setOnTouchListener(FocalTouchListener("1"))
ptzBoard.findViewById<View>(R.id.tv_focal_reduce)
.setOnTouchListener(FocalTouchListener("0"))
val isSupportZoom = thingIPCPTZ.querySupportByDPCode(PTZDPModel.DP_ZOOM_CONTROL)
ptzBoard.findViewById<View>(R.id.group_focal).visibility =
if (isSupportZoom) View.VISIBLE else View.GONE
//Collection Point
ptzBoard.findViewById<View>(R.id.tv_collection_add).setOnClickListener(this)
ptzBoard.findViewById<View>(R.id.tv_collection_delete).setOnClickListener(this)
ptzBoard.findViewById<View>(R.id.tv_collection_item).setOnClickListener(this)
ptzBoard.findViewById<View>(R.id.tv_collection_item).setOnLongClickListener(this)
val isSupportCollection = thingIPCPTZ.querySupportByDPCode(PTZDPModel.DP_MEMORY_POINT_SET)
ptzBoard.findViewById<View>(R.id.group_collection).visibility =
if (isSupportCollection) View.VISIBLE else View.GONE
//Cruise
val tvCruiseSwitch = ptzBoard.findViewById<TextView>(R.id.tv_cruise_switch)
tvCruiseSwitch.setOnClickListener(this)
val isCruiseOpen =
thingIPCPTZ.getCurrentValue(PTZDPModel.DP_CRUISE_SWITCH, Boolean::class.java) == true
tvCruiseSwitch.text = if (isCruiseOpen) "Opened" else "closed"
ptzBoard.findViewById<View>(R.id.tv_cruise_mode).setOnClickListener(this)
ptzBoard.findViewById<View>(R.id.tv_cruise_time).setOnClickListener(this)
val isSupportCruise = thingIPCPTZ.querySupportByDPCode(PTZDPModel.DP_CRUISE_SWITCH)
ptzBoard.findViewById<View>(R.id.group_cruise).visibility =
if (isSupportCruise) View.VISIBLE else View.GONE
//Tracking
val tTrackingSwitch = ptzBoard.findViewById<TextView>(R.id.tv_tracking_switch)
tTrackingSwitch.setOnClickListener(this)
val isTrackingOpen =
thingIPCPTZ.getCurrentValue(PTZDPModel.DP_MOTION_TRACKING, Boolean::class.java) == true
tTrackingSwitch.text = if (isTrackingOpen) "Opened" else "closed"
val isSupportTracking = thingIPCPTZ.querySupportByDPCode(PTZDPModel.DP_MOTION_TRACKING)
ptzBoard.findViewById<View>(R.id.group_tracking).visibility =
if (isSupportTracking) View.VISIBLE else View.GONE
//Preset Point
ptzBoard.findViewById<View>(R.id.tv_preset_select).setOnClickListener(this)
val isSupportPreset = thingIPCPTZ.querySupportByDPCode(PTZDPModel.DP_PRESET_POINT)
ptzBoard.findViewById<View>(R.id.group_preset).visibility =
if (isSupportPreset) View.VISIBLE else View.GONE
val tvPtzEmpty = ptzBoard.findViewById<View>(R.id.tv_ptz_empty)
val isNotSupportPTZ =
!isPTZControl && !isSupportZoom && !isSupportCollection && !isSupportCruise && !isSupportTracking && !isSupportPreset
tvPtzEmpty.visibility =
if (isNotSupportPTZ) View.VISIBLE else View.GONE
}
fun show() {
ptzBoard?.let {
it.alpha = 0f
it.visibility = View.VISIBLE
it.animate().alpha(1f).setDuration(200).start()
requestCollectionPointList()
}
}
private fun dismiss() {
ptzBoard?.let {
it.animate().alpha(0f).setDuration(200).start()
it.postDelayed({ ptzBoard?.visibility = View.INVISIBLE }, 200)
}
}
override fun onClick(v: View) {
if (v.id == R.id.tv_ptz_close) {
dismiss()
} else if (v.id == R.id.tv_collection_add) {
addCollectionPoint()
} else if (v.id == R.id.tv_collection_delete) {
deleteCollectionPoint()
} else if (v.id == R.id.tv_collection_item) {
if (v.tag is CollectionPointBean) {
thingIPCPTZ.viewCollectionPoint(
(v.tag as CollectionPointBean),
ResultCallback("viewCollectionPoint")
)
}
} else if (v.id == R.id.tv_cruise_switch) {
val isOpen =
thingIPCPTZ.getCurrentValue(PTZDPModel.DP_CRUISE_SWITCH, Boolean::class.java) == true
thingIPCPTZ.publishDps(
PTZDPModel.DP_CRUISE_SWITCH,
!isOpen,
object : ResultCallback("cruise_switch") {
override fun onSuccess() {
super.onSuccess()
val value = thingIPCPTZ.getCurrentValue(
PTZDPModel.DP_CRUISE_SWITCH,
Boolean::class.java
) == true
(v as TextView).text = if (value) "Opened" else "closed"
}
})
} else if (v.id == R.id.tv_cruise_mode) {
val itemMap = mapOf(
"0" to context.getString(R.string.ipc_panoramic_cruise),
"1" to context.getString(R.string.ipc_collection_point_cruise)
)
val items = mutableListOf<String>()
thingIPCPTZ.getSchemaProperty(
PTZDPModel.DP_CRUISE_MODE,
EnumSchemaBean::class.java
)?.range?.forEach {
itemMap[it]?.let { item ->
items.add(item)
}
}
showSelectDialog(items.toTypedArray()) { _: DialogInterface?, which: Int ->
itemMap.entries.find { it.value == items[which] }?.key?.let { mode ->
thingIPCPTZ.setCruiseMode(mode, ResultCallback("setCruiseMode $mode"))
}
}
} else if (v.id == R.id.tv_cruise_time) {
val items = arrayOf(
context.getString(R.string.ipc_full_day_cruise), context.getString(
R.string.ipc_custom_cruise
)
)
showSelectDialog(items) { _: DialogInterface?, which: Int ->
if (which == 0) {
thingIPCPTZ.publishDps(
PTZDPModel.DP_CRUISE_TIME_MODE,
"0",
ResultCallback("cruise_time_mode 0")
)
} else if (which == 1) {
thingIPCPTZ.setCruiseTiming(
"09:00",
"16:00",
ResultCallback("cruise_time_mode 1")
)
}
}
} else if (v.id == R.id.tv_tracking_switch) {
onClickTracking(v as TextView)
} else if (v.id == R.id.tv_preset_select) {
onClickPreset()
}
}
private fun onClickTracking(textView: TextView) {
progressDialog?.show()
val isOpen = true == thingIPCPTZ.getCurrentValue(
PTZDPModel.DP_MOTION_TRACKING,
Boolean::class.java
)
thingIPCPTZ.publishDps(
PTZDPModel.DP_MOTION_TRACKING,
!isOpen,
object : ResultCallback("motion_tracking") {
override fun onSuccess() {
super.onSuccess()
val value = true == thingIPCPTZ.getCurrentValue(
PTZDPModel.DP_MOTION_TRACKING,
Boolean::class.java
)
textView.text = if (value) "Opened" else "closed"
progressDialog?.dismiss()
}
override fun onError(code: String, error: String) {
super.onError(code, error)
progressDialog?.dismiss()
}
})
}
private fun onClickPreset() {
val enumSchemaBean =
thingIPCPTZ.getSchemaProperty(PTZDPModel.DP_PRESET_POINT, EnumSchemaBean::class.java)
val items = enumSchemaBean.getRange().toTypedArray()
showSelectDialog(items) { _: DialogInterface?, which: Int ->
thingIPCPTZ.publishDps(
PTZDPModel.DP_PRESET_POINT,
items[which],
ResultCallback("ipc_preset_set " + items[which])
)
}
}
fun ptzControl(direction: String?) {
thingIPCPTZ.publishDps(PTZDPModel.DP_PTZ_CONTROL, direction!!, ResultCallback("ptzControl"))
}
fun ptzStop() {
thingIPCPTZ.publishDps(PTZDPModel.DP_PTZ_STOP, true, ResultCallback("ptzStop"))
}
private fun requestCollectionPointList() {
thingIPCPTZ.requestCollectionPointList(object :
IThingResultCallback<List<CollectionPointBean>?> {
override fun onSuccess(result: List<CollectionPointBean>?) {
val decryptImageView: DecryptImageView =
ptzBoard?.findViewById(R.id.iv_collection) ?: return
val tvName = ptzBoard?.findViewById<TextView>(R.id.tv_collection_item) ?: return
if (result != null && result.isNotEmpty()) {
collectionPointSize = result.size
var collectionPointBean = result[result.size - 1]
try {
for (i in result.indices) {
val item = result[i]
if (item.mpId.toInt() > collectionPointBean.mpId.toInt()) {
collectionPointBean = item
}
}
} catch (e: Exception) {
e.printStackTrace()
}
tvName.text = collectionPointBean.name
if (collectionPointBean.encryption is JSONObject) {
val jsonObject = collectionPointBean.encryption as JSONObject
val key = jsonObject["key"]
if (key == null) {
decryptImageView.setImageURI(collectionPointBean.pic)
} else {
decryptImageView.setImageURI(
collectionPointBean.pic,
key.toString().toByteArray()
)
}
} else {
decryptImageView.setImageURI(collectionPointBean.pic)
}
tvName.tag = collectionPointBean
} else {
collectionPointSize = 0
tvName.text = ""
tvName.tag = null
decryptImageView.setImageResource(0)
}
}
override fun onError(errorCode: String, errorMessage: String) {}
})
}
private fun addCollectionPoint() {
progressDialog?.show()
thingIPCPTZ.addCollectionPoint(
"Collection" + collectionPointSize++,
object : IResultCallback {
override fun onError(code: String, error: String) {
Log.d(TAG, "addCollectionPoint invoke error")
progressDialog?.dismiss()
}
override fun onSuccess() {
ptzBoard?.postDelayed({
requestCollectionPointList()
progressDialog?.dismiss()
}, 1000)
}
})
}
private fun deleteCollectionPoint() {
val tvCollectionItem = ptzBoard?.findViewById<TextView>(R.id.tv_collection_item) ?: return
if (tvCollectionItem.tag !is CollectionPointBean) {
Toast.makeText(context, "Operation failed", Toast.LENGTH_SHORT).show()
return
}
progressDialog?.show()
val items: MutableList<CollectionPointBean> = ArrayList()
val item = tvCollectionItem.tag as CollectionPointBean
items.add(item)
thingIPCPTZ.deleteCollectionPoints(items, object : IResultCallback {
override fun onError(code: String, error: String) {
Log.d(TAG, "deleteCollectionPoint invoke error")
progressDialog?.dismiss()
}
override fun onSuccess() {
requestCollectionPointList()
progressDialog?.dismiss()
}
})
}
override fun onLongClick(v: View): Boolean {
if (v.id == R.id.tv_collection_item) {
val tvCollectionItem = ptzBoard?.findViewById<TextView>(R.id.tv_collection_item)
if (tvCollectionItem?.tag is CollectionPointBean) {
val item = tvCollectionItem.tag as CollectionPointBean
val nameNew = item.name + " New"
thingIPCPTZ.modifyCollectionPoint(item, nameNew, object : IResultCallback {
override fun onError(code: String, error: String) {
Toast.makeText(context, "Operation failed", Toast.LENGTH_SHORT).show()
}
@SuppressLint("SetTextI18n")
override fun onSuccess() {
(v as TextView).text = nameNew
Toast.makeText(context, "Operation success", Toast.LENGTH_SHORT).show()
}
})
}
}
return true
}
private fun showSelectDialog(
items: Array<String>,
onClickListener: DialogInterface.OnClickListener
) {
val builder = AlertDialog.Builder(context)
builder.setItems(items, onClickListener)
builder.setNegativeButton("Close") { dialog: DialogInterface, _: Int -> dialog.dismiss() }
builder.create().show()
}
internal open inner class ResultCallback(private val method: String) : IResultCallback {
override fun onError(code: String, error: String) {
Log.d(TAG, "$method invoke error: $error")
Toast.makeText(context, "Operation failed", Toast.LENGTH_SHORT).show()
}
override fun onSuccess() {
Log.d(TAG, "$method invoke success")
Toast.makeText(context, "Operation success", Toast.LENGTH_SHORT).show()
}
}
private inner class PTZControlTouchListener(var direction: String) : OnTouchListener {
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> ptzControl(direction)
MotionEvent.ACTION_UP -> ptzStop()
else -> {
}
}
return true
}
}
private inner class FocalTouchListener(var zoom: String) : OnTouchListener {
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> thingIPCPTZ.publishDps(
PTZDPModel.DP_ZOOM_CONTROL, zoom, ResultCallback(
"zoom_control$zoom"
)
)
MotionEvent.ACTION_UP -> thingIPCPTZ.publishDps(
PTZDPModel.DP_ZOOM_STOP,
true,
ResultCallback("zoom_stop")
)
else -> {
}
}
return true
}
}
companion object {
private const val TAG = "CameraPTZHelper"
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.os.Build
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.lang.Exception
/**
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 3:36 PM
*/
class Constants {
companion object {
const val INTENT_MSGID = "msgid"
const val INTENT_DEV_ID = "intent_devId"
const val INTENT_P2P_TYPE = "intent_p2p_type"
const val EXTERNAL_STORAGE_REQ_CODE = 10
const val EXTERNAL_AUDIO_REQ_CODE = 11
const val ARG1_OPERATE_SUCCESS = 0
const val ARG1_OPERATE_FAIL = 1
const val MSG_CONNECT = 2033
const val MSG_CREATE_DEVICE = 2099
const val MSG_SET_CLARITY = 2054
const val MSG_TALK_BACK_FAIL = 2021
const val MSG_TALK_BACK_BEGIN = 2022
const val MSG_TALK_BACK_OVER = 2023
const val MSG_DATA_DATE = 2035
const val MSG_MUTE = 2024
const val MSG_SCREENSHOT = 2017
const val MSG_VIDEO_RECORD_FAIL = 2018
const val MSG_VIDEO_RECORD_BEGIN = 2019
const val MSG_VIDEO_RECORD_OVER = 2020
const val MSG_DATA_DATE_BY_DAY_SUCC = 2045
const val MSG_DATA_DATE_BY_DAY_FAIL = 2046
const val ALARM_DETECTION_DATE_MONTH_FAILED = 2047
const val ALARM_DETECTION_DATE_MONTH_SUCCESS = 2048
const val MSG_GET_ALARM_DETECTION = 2049
const val MOTION_CLASSIFY_FAILED = 2050
const val MOTION_CLASSIFY_SUCCESS = 2051
const val MSG_DELETE_ALARM_DETECTION = 2052
const val MSG_GET_VIDEO_CLARITY = 2053
fun requestPermission(
context: Context,
permission: String,
requestCode: Int,
tip: String
): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true
}
return if (ContextCompat.checkSelfPermission(
context,
permission
) != PackageManager.PERMISSION_GRANTED
) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
(context as Activity),
permission
)
) {
ToastUtil.shortToast(context, tip)
} else {
ActivityCompat.requestPermissions(
context,
arrayOf(permission),
requestCode
)
}
false
} else {
true
}
}
@SuppressLint("all")
fun hasRecordPermission(): Boolean {
val minBufferSize = AudioRecord.getMinBufferSize(
8000,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
val bufferSizeInBytes = 640
val audioData = ByteArray(bufferSizeInBytes)
var readSize = 0
var audioRecord: AudioRecord? = null
try {
audioRecord = AudioRecord(
MediaRecorder.AudioSource.DEFAULT, 8000,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBufferSize
)
// start recording
audioRecord.startRecording()
} catch (e: Exception) {
audioRecord?.release()
return false
}
return if (audioRecord.recordingState != AudioRecord.RECORDSTATE_RECORDING) {
audioRecord?.stop()
audioRecord?.release()
false
} else {
readSize = audioRecord.read(audioData, 0, bufferSizeInBytes)
// Check whether the recording result can be obtained
audioRecord?.stop()
audioRecord?.release()
return readSize>0
}
}
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
/**
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 3:37 PM
*/
class DPConstants {
companion object{
//Data type: enum
const val PTZ_CONTROL = "119"
//Data type: boolean
const val PTZ_STOP = "116"
//Data type: boolean
const val WATERMARK = "104"
//Data type: boolean
const val SD_CARD_RECORD_SWITCH = "150"
const val SD_STATUS = "110"
const val SD_STORAGE = "109"
const val SD_FORMAT = "111"
const val SD_FORMAT_STATUS = "117"
//DP Data type
const val SCHEMA_TYPE_RAW = "raw"
const val SCHEMA_TYPE_BOOL = "bool"
const val SCHEMA_TYPE_ENUM = "enum"
const val SCHEMA_TYPE_VALUE = "value"
const val SCHEMA_TYPE_STRING = "string"
const val PTZ_UP = "0"
const val PTZ_LEFT = "2"
const val PTZ_DOWN = "4"
const val PTZ_RIGHT = "6"
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
/**
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 3:37 PM
*/
class DateUtils {
companion object{
/**
* Get the start of the day(00:00:00)
*
* @param currentTime
* @return
*/
fun getTodayStart(currentTime: Long): Int {
val calendar: Calendar = GregorianCalendar()
calendar.timeInMillis = currentTime
calendar[Calendar.HOUR_OF_DAY] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.SECOND] = 0
val value = calendar.timeInMillis / 1000L
return value.toInt()
}
/**
* Get the end of the day(00:00:00)
*
* @param currentTime
* @return
*/
fun getTodayEnd(currentTime: Long): Int {
val calendar = Calendar.getInstance()
calendar.time = Date(currentTime)
calendar[Calendar.HOUR_OF_DAY] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.SECOND] = 0
calendar.add(Calendar.DAY_OF_MONTH, 1)
val value = calendar.timeInMillis / 1000L
return value.toInt()
}
/**
*
* @param year
* @param month
* @param day
* @return
*/
fun getCurrentTime(year: Int, month: Int, day: Int): Long {
val monthStr = if (month < 10) "0$month" else "" + month
val dayStr = if (day < 10) "0$day" else "" + day
val currentDate = year.toString() + monthStr + dayStr
val simpleDateFormat = SimpleDateFormat("yyyyMMdd")
try {
val date = simpleDateFormat.parse(currentDate)
return date.time
} catch (px: ParseException) {
px.printStackTrace()
}
return 0
}
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
import android.content.Context
import android.os.StatFs
import com.facebook.cache.disk.DiskCacheConfig
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ExecutorSupplier
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.facebook.imagepipeline.listener.RequestListener
import com.thingclips.imagepipeline.okhttp3.OkHttpImagePipelineConfigFactory
import com.thingclips.smart.android.common.task.ThingExecutor
import okhttp3.Cache
import okhttp3.OkHttpClient
import java.io.File
import java.lang.IllegalArgumentException
import java.util.HashSet
import java.util.concurrent.Executor
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
/**
* TODO feature
*
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 3:37 PM
*/
class FrescoManager {
companion object{
fun initFresco(context: Context) {
val defaultConfig = getDefaultConfig(context, null, null)
initFresco(context, defaultConfig)
}
fun initFresco(context: Context, config: ImagePipelineConfig) {
Fresco.initialize(context, config)
}
private fun getDefaultConfig(context: Context, listener: RequestListener?, diskCacheConfig: DiskCacheConfig?): ImagePipelineConfig {
val requestListeners: HashSet<RequestListener> = HashSet<RequestListener>()
val cacheDir = File(context.cacheDir, "okhttp3")
val size = calculateDiskCacheSize(cacheDir)
val okHttpClient = OkHttpClient.Builder().cache(Cache(cacheDir, size))
.connectTimeout(0, TimeUnit.MILLISECONDS)
.readTimeout(0, TimeUnit.MILLISECONDS)
.writeTimeout(0, TimeUnit.MILLISECONDS).build()
val builder = OkHttpImagePipelineConfigFactory.newBuilder(
context.applicationContext, okHttpClient
)
builder.setDownsampleEnabled(false).setRequestListeners(requestListeners)
diskCacheConfig?.let {
builder.setMainDiskCacheConfig(it)
}
builder.setExecutorSupplier(object : ExecutorSupplier {
override fun forLocalStorageRead(): Executor {
return ThingExecutor.getInstance().thingExecutorService
}
override fun forLocalStorageWrite(): Executor {
return ThingExecutor.getInstance().thingExecutorService
}
override fun forDecode(): Executor {
return ThingExecutor.getInstance().thingExecutorService
}
override fun forBackgroundTasks(): Executor {
return ThingExecutor.getInstance().thingExecutorService
}
override fun scheduledExecutorServiceForBackgroundTasks(): ScheduledExecutorService? {
return ThingExecutor.getInstance().thingBackupService as ScheduledExecutorService?
}
override fun forLightweightBackgroundTasks(): Executor {
return ThingExecutor.getInstance().thingExecutorService
}
override fun forThumbnailProducer(): Executor {
return ThingExecutor.getInstance().thingExecutorService
}
})
return builder.build()
}
private const val MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024 // 5MB
private const val MAX_DISK_CACHE_SIZE = 10 * 1024 * 1024 // 50MB
private fun calculateDiskCacheSize(dir: File): Long {
var size = MIN_DISK_CACHE_SIZE.toLong()
try {
val statFs = StatFs(dir.absolutePath)
val available = statFs.blockCount.toLong() * statFs.blockSize
// Target 2% of the total space.
size = available / 50
} catch (ignored: IllegalArgumentException) {
}
// Bound inside min/max size for disk cache.
return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE.toLong()), MIN_DISK_CACHE_SIZE.toLong())
}
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
import android.content.Context
import android.os.Build
import android.os.Environment
import com.tuya.smart.android.demo.camera.utils.IPCSavePathUtils
import java.io.File
class IPCSavePathUtils(context: Context) {
init {
//初始化外部存储根目录
ROOT_PATH = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
//android11及以上设备
if (null == context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)) {
context.filesDir.absolutePath
} else {
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!
.path
}
} else {
//android11以下设备
Environment.getExternalStorageDirectory().absolutePath
}
DOWNLOAD_PATH = "$ROOT_PATH/Camera/Thumbnail/"
DOWNLOAD_PATH_Q = ROOT_PATH + "/Camera/" + Environment.DIRECTORY_DCIM + "/Thumbnail/"
}
fun recordPathSupportQ(devId: String): String {
val videoPath: String = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
//分区存储
"$DOWNLOAD_PATH_Q$devId/"
} else {
//非分区存储
"$DOWNLOAD_PATH$devId/"
}
val file = File(videoPath)
if (!file.exists()) {
if (!file.mkdirs()) {
// L.e(TAG, "recordPathQ create the directory fail, videoPath is " + videoPath);
return ""
}
}
return videoPath
}
companion object {
private var ROOT_PATH: String? = null
lateinit var DOWNLOAD_PATH: String
lateinit var DOWNLOAD_PATH_Q: String
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
import android.os.Message
/**
* TODO feature
*
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 3:37 PM
*/
class MessageUtil {
companion object{
fun getMessage(msgWhat: Int, arg: Int): Message{
val msg = Message()
msg.what = msgWhat
msg.arg1 = arg
return msg
}
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
import android.icu.util.TimeZone
import android.os.Build
import java.util.*
/**
* TODO feature
*
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 3:38 PM
*/
class TimeZoneUtils {
companion object{
fun getTimezoneGCMById(timezoneId: String?): String? {
val timeZoneByRawOffset = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val timeZone = TimeZone.getTimeZone(timezoneId)
timeZone.rawOffset + timeZone.dstSavings
} else {
val timeZone = SimpleTimeZone.getTimeZone(timezoneId)
timeZone.rawOffset + timeZone.dstSavings
}
return getTimeZoneByRawOffset(timeZoneByRawOffset)
}
private fun getTimeZoneByRawOffset(rawOffset: Int): String {
var timeDisplay = if (rawOffset >= 0) "+" else ""
val hour = rawOffset / 1000 / 3600
val minute = (rawOffset - hour * 1000 * 3600) / 1000 / 60
timeDisplay += String.format(Locale.getDefault(), "%02d:%02d", hour, minute)
return timeDisplay
}
}
}
\ No newline at end of file
package com.tuya.smart.android.demo.camera.utils
import android.content.Context
import android.widget.Toast
/**
* TODO feature
*
* @author houqing <a href="mailto:developer@tuya.com"/>
* @since 2021/7/26 3:38 PM
*/
class ToastUtil {
companion object{
fun shortToast(context: Context?, tips: String?) {
Toast.makeText(context, tips, Toast.LENGTH_SHORT).show()
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#1585fb" android:state_enabled="true" android:state_selected="true" />
<item android:color="#989898" android:state_enabled="true" android:state_selected="false" />
<item android:color="#33989898" android:state_enabled="false" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<solid android:color="@color/white" />
<corners android:radius="@dimen/button_radius" />
<stroke
android:width="1px"
android:color="@color/line" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_button" android:state_selected="true"/>
<item android:drawable="@drawable/bg_button" android:state_selected="false"/>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/camera_tab_speak_btn_on" android:state_enabled="true" android:state_selected="true" />
<item android:drawable="@drawable/camera_tab_speak_btn_nomal" android:state_enabled="true" android:state_selected="false" />
<item android:drawable="@drawable/camera_tab_speak_btn_unenable" android:state_enabled="false" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/camera_icon_photo_btn_normal" android:state_enabled="true" />
<item android:drawable="@drawable/camera_icon_photo_btn_unenable" android:state_enabled="false" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/camera_icon_playback_btn_normal" android:state_enabled="true" />
<item android:drawable="@drawable/camera_icon_playback_btn_unable" android:state_enabled="false" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/camera_icon_rec_btn_on" android:state_enabled="true" android:state_selected="true" />
<item android:drawable="@drawable/camera_icon_rec_btn_normal" android:state_enabled="true" android:state_selected="false" />
<item android:drawable="@drawable/camera_icon_rec_btn_unenable" android:state_enabled="false" android:state_selected="false" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/default_bg" android:state_pressed="true" />
<item android:drawable="@color/white" android:state_focused="true" />
<item android:drawable="@color/white" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/camera_preview_sound_btn_off" android:state_selected="true" />
<item android:drawable="@drawable/camera_preview_sound_btn_on" android:state_selected="false" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/mg_100" />
<solid android:color="#0A0B11" />
<stroke
android:width="1dp"
android:color="#1585FB" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_view"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/cloud_video_title" />
<RelativeLayout
android:id="@+id/camera_video_view_Rl"
android:layout_width="match_parent"
android:layout_height="240dp"
android:layout_below="@id/toolbar_view">
<com.thingclips.smart.camera.middleware.widget.ThingCameraView
android:id="@+id/camera_cloud_video_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/camera_mute"
android:layout_width="@dimen/wh_28"
android:layout_height="@dimen/wh_28"
android:layout_alignParentBottom="true"
android:layout_gravity="center"
android:layout_margin="10dp"
android:gravity="center"
android:src="@drawable/camera_mute_btn" />
</RelativeLayout>
<TextView
android:id="@+id/status_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/camera_video_view_Rl"
android:layout_marginLeft="@dimen/mg_10"
android:layout_marginRight="@dimen/mg_10"
android:padding="10dp"
android:textColor="@color/black"
android:textSize="15sp" />
<Button
android:id="@+id/query_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/status_tv"
android:layout_marginLeft="@dimen/mg_10"
android:layout_marginRight="@dimen/mg_10"
android:text="@string/query_cloud"
android:textColor="@color/white"
android:textSize="15sp"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dateRv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/query_btn"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginRight="10dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/timeRv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/ll_bottom"
android:layout_below="@+id/dateRv"
android:layout_margin="10dp" />
<LinearLayout
android:id="@+id/ll_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<Button
android:id="@+id/pause_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/pause"
android:textColor="@color/white"
android:textSize="15sp" />
<Space
android:layout_width="5dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/resume_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/resume"
android:textColor="@color/white"
android:textSize="15sp" />
<Space
android:layout_width="5dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/stop_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/stop"
android:textColor="@color/white"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<Button
android:id="@+id/snapshot_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/snapshot"
android:textColor="@color/white"
android:textSize="15sp" />
<Space
android:layout_width="5dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/record_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/record_start"
android:textColor="@color/white"
android:textSize="15sp" />
<Space
android:layout_width="5dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/record_end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/record_stop"
android:textColor="@color/white"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraCloudVideoActivity">
<com.thingclips.smart.camera.middleware.widget.ThingCameraView
android:id="@+id/camera_cloud_video_view"
android:layout_width="match_parent"
android:layout_height="200dp" />
<ImageView
android:id="@+id/camera_mute"
android:layout_width="@dimen/wh_28"
android:layout_height="@dimen/wh_28"
android:layout_alignBottom="@+id/camera_cloud_video_view"
android:layout_alignLeft="@+id/camera_cloud_video_view"
android:layout_gravity="center"
android:layout_margin="10dp"
android:gravity="center"
android:src="@drawable/camera_mute_btn" />
<ProgressBar
android:id="@+id/camera_cloud_video_progressbar"
android:layout_alignBottom="@+id/camera_cloud_video_view"
android:layout_width="match_parent"
android:layout_height="10dp" />
<Button
android:id="@+id/btn_pause_video_msg"
android:layout_below="@+id/camera_cloud_video_progressbar"
android:layout_alignParentLeft="true"
android:text="@string/pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></Button>
<Button
android:id="@+id/btn_resume_video_msg"
android:layout_below="@+id/camera_cloud_video_progressbar"
android:layout_toRightOf="@+id/btn_pause_video_msg"
android:text="@string/resume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></Button>
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_view"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/doorbell_title" />
<TextView
android:id="@+id/tv_state"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center"
android:textColor="@color/black"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/cl_bottom"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar_view"
tools:text="Current state:\nRinging" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/mg_40"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent">
<Button
android:id="@+id/btn_refuse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ipc_doorbell_refuse"
android:textColor="@color/white"
android:textSize="15sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_accept"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ipc_doorbell_accept"
android:textColor="@color/white"
android:textSize="15sp"
app:layout_constraintLeft_toRightOf="@+id/btn_refuse"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context=".CameraInfoActivity">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_view"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/info_title"
app:navigationIcon="?attr/homeAsUpIndicator" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/camera_info_ry"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_view"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/message_title"
app:navigationIcon="?attr/homeAsUpIndicator" />
<LinearLayout
android:id="@+id/query_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/toolbar_view"
android:orientation="horizontal"
android:padding="@dimen/mg_10">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/playback_input_hint"
android:textColor="@color/black"
android:textSize="15sp" />
<EditText
android:id="@+id/date_input_edt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="2019/11/12"
android:textColor="@color/black"
android:textColorHint="@color/gray"
android:textSize="15sp" />
</LinearLayout>
<Button
android:id="@+id/query_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/query_ll"
android:layout_marginLeft="@dimen/mg_10"
android:layout_marginRight="@dimen/mg_10"
android:text="@string/query"
android:textColor="@color/white"
android:textSize="15sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/query_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/query_btn"
android:layout_marginLeft="@dimen/mg_10"
android:layout_marginRight="@dimen/mg_10" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_view"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/panel_title"
app:navigationIcon="?attr/homeAsUpIndicator" />
<RelativeLayout
android:id="@+id/camera_video_view_Rl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/toolbar_view"
tools:layout_height="250dp">
<com.thingclips.smart.camera.middleware.widget.ThingCameraView
android:id="@+id/camera_video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ImageView
android:id="@+id/camera_mute"
android:layout_width="@dimen/wh_28"
android:layout_height="@dimen/wh_28"
android:layout_alignParentBottom="true"
android:layout_gravity="center"
android:layout_margin="10dp"
android:gravity="center"
android:src="@drawable/camera_mute_btn" />
<TextView
android:id="@+id/camera_quality"
android:layout_width="@dimen/wh_60"
android:layout_height="@dimen/wh_28"
android:layout_alignParentBottom="true"
android:layout_gravity="center"
android:layout_margin="10dp"
android:layout_toRightOf="@+id/camera_mute"
android:background="@drawable/camera_shape_wirlesswake"
android:gravity="center"
android:text="@string/hd"
android:textColor="@color/white"
android:textSize="@dimen/ts_12" />
</RelativeLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/camera_video_view_Rl">
<include
android:id="@+id/camera_control_board"
layout="@layout/camera_panel_control_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
<ScrollView
android:id="@+id/sv_ptz_board"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/camera_video_view_Rl"
android:visibility="invisible"
android:background="@color/white">
<include
android:id="@+id/camera_ptz_board"
layout="@layout/camera_ptz_control_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_view"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/playback_title"
app:navigationIcon="?attr/homeAsUpIndicator" />
<RelativeLayout
android:id="@+id/camera_video_view_Rl"
android:layout_width="match_parent"
android:layout_height="240dp"
android:layout_below="@id/toolbar_view">
<com.thingclips.smart.camera.middleware.widget.ThingCameraView
android:id="@+id/camera_video_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/camera_mute"
android:layout_width="@dimen/wh_28"
android:layout_height="@dimen/wh_28"
android:layout_alignParentBottom="true"
android:layout_gravity="center"
android:layout_margin="10dp"
android:gravity="center"
android:src="@drawable/camera_mute_btn" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/timeline_layout"
android:layout_below="@+id/camera_video_view_Rl"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.thingclips.smart.android.camera.timeline.ThingTimelineView
android:id="@+id/timeline"
android:layout_width="match_parent"
android:layout_height="60dp"
app:bottomTextMargin="5dp"
app:bubbleColor="@color/gray"
app:bubbleTextColor="@color/white"
app:linesColor="@color/black"
app:timeScaleColor="@color/black"
app:topTextMargin="10dp" />
</RelativeLayout>
<LinearLayout
android:id="@+id/query_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/timeline_layout"
android:orientation="horizontal"
android:padding="@dimen/mg_10">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/playback_input_hint"
android:textColor="@color/black"
android:textSize="15sp" />
<EditText
android:id="@+id/date_input_edt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="2019/3"
android:textColor="@color/black"
android:textColorHint="@color/gray"
android:textSize="15sp" />
<Button
android:id="@+id/query_btn"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_below="@+id/query_ll"
android:layout_marginLeft="@dimen/mg_10"
android:layout_marginRight="@dimen/mg_10"
android:text="@string/query"
android:textColor="@color/white"
android:textSize="15sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_month"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/query_ll"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/query_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/rv_month"
android:layout_above="@+id/ll_btn"
android:layout_margin="@dimen/mg_10" />
<LinearLayout
android:id="@+id/ll_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:padding="@dimen/mg_10">
<Button
android:id="@+id/pause_btn"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/pause"
android:textColor="@color/white"
android:textSize="15sp" />
<Space
android:layout_width="5dp"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/resume_btn"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/resume"
android:textColor="@color/white"
android:textSize="15sp" />
<Space
android:layout_width="5dp"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/stop_btn"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/stop"
android:textColor="@color/white"
android:textSize="15sp" />
</LinearLayout>
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_button_selector"
android:orientation="vertical"
android:padding="@dimen/mg_10">
<TextView
android:id="@+id/time_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="15sp" />
<TextView
android:id="@+id/time_duration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="@string/duration"
android:textColor="@color/black_60"
android:textSize="13sp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical"
>
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_view"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/settings_title"
app:navigationIcon="?attr/homeAsUpIndicator" />
<ScrollView
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:text="SD Card Status"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_sd_status"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="SD Card Format"
android:textStyle="bold"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_sd_format"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_sd_format"
android:visibility="gone"
android:text="FORMAT"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<TextView
android:text="Watermark"
android:textStyle="bold"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_watermark"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_watermark"
android:visibility="gone"
android:text="CHANGE"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<TextView
android:text="SDCARD Save Video"
android:textStyle="bold"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_sd_save_video"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/open_sd_save_video"
android:visibility="visible"
android:text="CHANGE"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<TextView
android:text="SDCARD Save Video Model"
android:textStyle="bold"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_sd_save_video_model"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<TextView
android:text="Record"
android:textStyle="bold"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_record"
android:textColor="@color/black"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_record"
android:visibility="gone"
android:text="CHANGE"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/bg_button_selector"
android:orientation="vertical"
android:padding="@dimen/mg_10">
<TextView
android:id="@+id/tv_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
tools:text="2020/04/20"
android:textSize="15sp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_button_selector"
android:orientation="vertical"
android:padding="@dimen/mg_10">
<TextView
android:id="@+id/time_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="15sp" />
<TextView
android:id="@+id/time_duration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="@string/duration"
android:textColor="@color/black_60"
android:textSize="13sp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp">
<RelativeLayout
android:id="@+id/rv_lv_oval"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp">
<ImageView
android:id="@+id/rv_iv_oval"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:scaleType="centerInside" />
<ImageView
android:id="@+id/iv_select"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:visibility="gone" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"
android:layout_toLeftOf="@id/iv_select"
android:layout_toRightOf="@id/rv_iv_oval"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/tv_alarm_detection_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textColor="@color/black"
android:textSize="@dimen/ts_15" />
<TextView
android:id="@+id/tv_time_range_start_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textColor="@color/black_60"
android:textSize="@dimen/ts_12" />
</LinearLayout>
</RelativeLayout>
<com.thingclips.drawee.view.DecryptImageView
android:id="@+id/iv_time_range_snapshot"
android:layout_width="match_parent"
android:layout_height="159dp"
android:layout_below="@+id/rv_lv_oval"
android:layout_marginLeft="48dp"
android:layout_marginTop="15dp"
android:layout_marginRight="45dp"
fresco:actualImageScaleType="fitXY"
fresco:roundedCornerRadius="4dp" />
<Button
android:id="@+id/btn_download_img"
android:text="@string/download"
android:layout_margin="5dp"
android:layout_alignRight="@+id/iv_time_range_snapshot"
android:layout_alignBottom="@+id/iv_time_range_snapshot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/speak_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/camera_item_control_btn"
android:drawableTop="@drawable/camera_icon_mic_selector"
android:drawablePadding="@dimen/mg_5"
android:gravity="center"
android:padding="@dimen/mg_20"
android:text="@string/speak"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_13" />
<TextView
android:id="@+id/photo_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_weight="1"
android:background="@drawable/camera_item_control_btn"
android:drawableTop="@drawable/camera_icon_photo_selector"
android:drawablePadding="@dimen/mg_5"
android:gravity="center"
android:padding="@dimen/mg_20"
android:text="@string/snapshot"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_13" />
<TextView
android:id="@+id/record_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/camera_item_control_btn"
android:drawableTop="@drawable/camera_icon_record_selector"
android:drawablePadding="@dimen/mg_5"
android:gravity="center"
android:padding="@dimen/mg_20"
android:text="@string/record"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_13" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/replay_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/camera_item_control_btn"
android:drawableTop="@drawable/camera_icon_playback_selector"
android:drawablePadding="@dimen/mg_5"
android:gravity="center"
android:padding="@dimen/mg_20"
android:text="@string/replay"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_13" />
<TextView
android:id="@+id/cloud_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@drawable/camera_icon_cloud"
android:gravity="center"
android:padding="@dimen/mg_20"
android:text="@string/cloud_video"
android:textColor="@color/camera_panel_control_color" />
<TextView
android:id="@+id/message_center_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/camera_item_control_btn"
android:drawableTop="@drawable/camera_icon_message"
android:drawablePadding="@dimen/mg_5"
android:gravity="center"
android:padding="@dimen/mg_20"
android:text="@string/message"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_13" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/setting_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@drawable/camera_icon_setting"
android:gravity="center"
android:text="@string/settings"
android:textColor="@color/camera_panel_control_color" />
<TextView
android:id="@+id/info_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@drawable/camera_icon_info"
android:gravity="center"
android:padding="@dimen/mg_20"
android:text="@string/device_info"
android:textColor="@color/camera_panel_control_color" />
<TextView
android:id="@+id/get_clarity_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@drawable/camera_icon_hd"
android:gravity="center"
android:padding="@dimen/mg_20"
android:text="@string/get_clarity"
android:textColor="@color/camera_panel_control_color" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/debug_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@drawable/camera_icon_debug"
android:gravity="center"
android:text="@string/ipc_debug_tool"
android:textColor="@color/camera_panel_control_color" />
<TextView
android:id="@+id/ptz_Txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@drawable/camera_icon_arrow"
android:gravity="center"
android:text="@string/ipc_ptz"
android:textColor="@color/camera_panel_control_color"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@drawable/camera_icon_info"
android:gravity="center"
android:padding="@dimen/mg_20"
android:visibility="invisible"/>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingBottom="10dp">
<TextView
android:id="@+id/tv_ptz_close"
android:layout_width="@dimen/wh_55"
android:layout_height="@dimen/wh_35"
android:layout_marginTop="5dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_close"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_12"
android:textStyle="bold"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_ptz_empty"
android:layout_width="0dp"
android:layout_height="200dp"
android:gravity="center"
android:text="@string/ipc_not_support_ptz"
android:textColor="@color/camera_panel_control_color"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_ptz_close" />
<TextView
android:id="@+id/tv_ptz"
android:layout_width="0dp"
android:layout_height="@dimen/wh_35"
android:gravity="center_vertical"
android:text="@string/ipc_ptz_control"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_12"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_ptz_top"
app:layout_constraintTop_toBottomOf="@+id/tv_ptz_close" />
<TextView
android:id="@+id/tv_ptz_left"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_left"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_ptz_top"
app:layout_constraintTop_toBottomOf="@+id/tv_ptz" />
<TextView
android:id="@+id/tv_ptz_top"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_top"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toRightOf="@+id/tv_ptz_left"
app:layout_constraintRight_toLeftOf="@+id/tv_ptz_right"
app:layout_constraintTop_toBottomOf="@+id/tv_ptz" />
<TextView
android:id="@+id/tv_ptz_right"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_right"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toRightOf="@+id/tv_ptz_top"
app:layout_constraintRight_toLeftOf="@+id/tv_ptz_bottom"
app:layout_constraintTop_toBottomOf="@+id/tv_ptz" />
<TextView
android:id="@+id/tv_ptz_bottom"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_bottom"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toRightOf="@+id/tv_ptz_right"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_ptz" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_ptz_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="tv_ptz,tv_ptz_left,tv_ptz_top,tv_ptz_right,tv_ptz_bottom" />
<TextView
android:id="@+id/tv_focal_length"
android:layout_width="0dp"
android:layout_height="@dimen/wh_35"
android:gravity="center_vertical"
android:text="@string/ipc_focal_length"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_12"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_ptz_top"
app:layout_constraintTop_toBottomOf="@+id/tv_ptz_left" />
<TextView
android:id="@+id/tv_focal_increase"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_increase"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_focal_reduce"
app:layout_constraintTop_toBottomOf="@+id/tv_focal_length" />
<TextView
android:id="@+id/tv_focal_reduce"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_reduce"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toRightOf="@+id/tv_focal_increase"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_focal_length" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_focal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="tv_focal_length,tv_focal_increase,tv_focal_reduce" />
<TextView
android:id="@+id/tv_collection"
android:layout_width="0dp"
android:layout_height="@dimen/wh_35"
android:gravity="center_vertical"
android:text="@string/ipc_collection_point"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_12"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_ptz_top"
app:layout_constraintTop_toBottomOf="@+id/tv_focal_increase" />
<com.thingclips.drawee.view.DecryptImageView
android:id="@+id/iv_collection"
android:layout_width="120dp"
android:layout_height="0dp"
android:layout_margin="2dp"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_collection_add"
app:layout_constraintTop_toBottomOf="@+id/tv_collection"
tools:background="@color/black" />
<TextView
android:id="@+id/tv_collection_item"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#33989898"
android:gravity="center"
android:textColor="@color/white"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/iv_collection"
app:layout_constraintLeft_toLeftOf="@+id/iv_collection"
app:layout_constraintRight_toRightOf="@+id/iv_collection"
app:layout_constraintTop_toTopOf="@+id/iv_collection"
tools:text="name" />
<TextView
android:id="@+id/tv_collection_add"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_add"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintBottom_toBottomOf="@+id/iv_collection"
app:layout_constraintLeft_toRightOf="@+id/iv_collection"
app:layout_constraintRight_toLeftOf="@+id/tv_collection_delete"
app:layout_constraintTop_toBottomOf="@+id/tv_collection" />
<TextView
android:id="@+id/tv_collection_delete"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_delete"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintBottom_toBottomOf="@+id/iv_collection"
app:layout_constraintLeft_toRightOf="@+id/tv_collection_add"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_collection" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_collection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="tv_collection,iv_collection,
tv_collection_item,tv_collection_add,tv_collection_delete" />
<TextView
android:id="@+id/tv_cruise"
android:layout_width="0dp"
android:layout_height="@dimen/wh_35"
android:gravity="center_vertical"
android:text="@string/ipc_cruise"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_12"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_ptz_top"
app:layout_constraintTop_toBottomOf="@+id/iv_collection" />
<TextView
android:id="@+id/tv_cruise_switch"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_cruise_mode"
app:layout_constraintTop_toBottomOf="@+id/tv_cruise"
tools:text="Opened" />
<TextView
android:id="@+id/tv_cruise_mode"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_cruise_mode"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toRightOf="@+id/tv_cruise_switch"
app:layout_constraintRight_toLeftOf="@+id/tv_cruise_time"
app:layout_constraintTop_toBottomOf="@+id/tv_cruise" />
<TextView
android:id="@+id/tv_cruise_time"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_cruise_time"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toRightOf="@+id/tv_cruise_mode"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_cruise" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_cruise"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="tv_cruise,tv_cruise_switch,tv_cruise_mode,tv_cruise_time" />
<TextView
android:id="@+id/tv_tracking"
android:layout_width="0dp"
android:layout_height="@dimen/wh_35"
android:gravity="center_vertical"
android:text="@string/ipc_tracking"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_12"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_ptz_top"
app:layout_constraintTop_toBottomOf="@+id/tv_cruise_switch" />
<TextView
android:id="@+id/tv_tracking_switch"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_tracking"
app:layout_constraintWidth_percent="0.25"
tools:text="Opened" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_tracking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="tv_tracking,tv_tracking_switch" />
<TextView
android:id="@+id/tv_preset"
android:layout_width="0dp"
android:layout_height="@dimen/wh_35"
android:gravity="center_vertical"
android:text="@string/ipc_preset_point"
android:textColor="@color/camera_panel_control_color"
android:textSize="@dimen/ts_12"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_ptz_top"
app:layout_constraintTop_toBottomOf="@+id/tv_tracking_switch" />
<TextView
android:id="@+id/tv_preset_select"
android:layout_width="0dp"
android:layout_height="@dimen/wh_42"
android:layout_margin="2dp"
android:background="#33989898"
android:gravity="center"
android:text="@string/ipc_preset_point"
android:textColor="@color/camera_panel_control_color"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_preset" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_preset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="tv_preset,tv_preset_select" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/recycle_item_info_text"
tools:text="text"
android:textSize="18sp"
android:layout_margin="10dp"
android:textColor="@color/black"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
\ No newline at end of file
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_remove_device"
android:title="@string/menu_remove_device"
app:showAsAction="always"/>
</menu>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="panel_title">摄像机</string>
<string name="hd">高清</string>
<string name="sd">标清</string>
<string name="other">其他</string>
<string name="speak">对讲</string>
<string name="snapshot">截图</string>
<string name="record">录制</string>
<string name="replay">回放</string>
<string name="cloud_video">云存储</string>
<string name="message">侦测消息</string>
<string name="settings">设置</string>
<string name="device_info">设备信息</string>
<string name="get_clarity">清晰度</string>
<string name="playback_title">回放</string>
<string name="doorbell_title">门铃呼叫</string>
<string name="playback_input_hint">请输入查询日期: </string>
<string name="query">查询</string>
<string name="start">开始</string>
<string name="pause">暂停</string>
<string name="resume">恢复</string>
<string name="stop">停止</string>
<string name="message_title">侦测消息</string>
<string name="info_title">设备信息</string>
<string name="low_power">是否是低功耗: </string>
<string name="video_num">支持的码流数: </string>
<string name="default_definition">默认清晰度: </string>
<string name="is_support_speaker">是否有扬声器: </string>
<string name="is_support_picK_up">是否有拾音器: </string>
<string name="is_support_talk">是否可以切换对讲方式: </string>
<string name="default_talk_mode">默认对讲方式: </string>
<string name="support_speed">支持的播放倍速: </string>
<string name="raw_data">原始数据: </string>
<string name="settings_title">设置</string>
<string name="cloud_video_title">云存储</string>
<string name="download">下载</string>
<string name="duration">时长: </string>
<string name="record_start">开始录制</string>
<string name="record_stop">停止录制</string>
<string name="cloud_status">查询云存储状态</string>
<string name="buy">购买云存储</string>
<string name="query_cloud">1. 查询云存储数据</string>
<string name="query_cloud_of_day">2. 查询某天云存储数据</string>
<string name="download_suc">下载成功</string>
<string name="operation_failed">操作失败</string>
<string name="no_data">查无数据</string>
<string name="connect_first">请连接设备</string>
<string name="input_err">输入错误</string>
<string name="operation_suc">操作成功</string>
<string name="connect_failed">连接失败</string>
<string name="not_support">不支持</string>
<string name="format_status">格式化进度: </string>
<string name="not_input_query_data">请输入查询日期</string>
<string name="get_current_clarity">当前清晰度: </string>
<string name="not_support_device">不支持该设备</string>
<string name="err_code">状态值: </string>
<string name="current_state">当前状态: </string>
<string name="ipc_doorbell_refuse">拒绝</string>
<string name="ipc_doorbell_hangup">挂断</string>
<string name="ipc_doorbell_accept">收受</string>
<string name="menu_remove_device">移除设备</string>
<string name="remove_device_dialog">确认要移除设备吗?</string>
<string name="confirm">确定</string>
<string name="cloud_start">3. 开始</string>
<string name="ipc_debug_tool">测试工具</string>
<string name="ipc_cloud_debug_tools">云存储调试工具</string>
<string name="ipc_ptz">PTZ</string>
<string name="ipc_close">关闭</string>
<string name="ipc_not_support_ptz">不支持云台</string>
<string name="ipc_ptz_control">云台控制</string>
<string name="ipc_left">LEFT</string>
<string name="ipc_top">TOP</string>
<string name="ipc_right">RIGHT</string>
<string name="ipc_bottom">BOTTOM</string>
<string name="ipc_increase">增加</string>
<string name="ipc_reduce">降低</string>
<string name="ipc_focal_length">焦距</string>
<string name="ipc_collection_point">收藏点</string>
<string name="ipc_add">添加</string>
<string name="ipc_delete">删除</string>
<string name="ipc_cruise">巡航</string>
<string name="ipc_cruise_mode">巡航模式</string>
<string name="ipc_cruise_time">巡航时间</string>
<string name="ipc_tracking">移动追踪</string>
<string name="ipc_preset_point">预设点</string>
<string name="ipc_panoramic_cruise">全景巡航</string>
<string name="ipc_collection_point_cruise">收藏点巡航</string>
<string name="ipc_full_day_cruise">全天巡航</string>
<string name="ipc_custom_cruise">自定义巡航</string>
<string name="ipc_sdk_autotest_tools">IPC SDK 自动化测试工具</string>
<string name="ipc_stop_talk">关闭对讲</string>
<string name="ipc_start_talk">开启对讲</string>
<string name="ipc_sdk_service_running">正常</string>
<string name="ipc_sdk_service_expired">已过期</string>
<string name="ipc_sdk_no_service">无服务</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- SwipeRefreshLayout -->
<color name="background_home">#F0F0F0</color>
<color name="textColor">#303030</color>
<color name="default_bg">#f0f0f0</color>
<color name="transparent">#00000000</color>
<color name="line_color">#ffdbdbdb</color>
<color name="hint">#ffb3b3b3</color>
<color name="line">#dbdbdb</color>
<color name="colorNormal">#626262</color>
<color name="edit_exit_title">#FF9B9B9B</color>
<color name="colorHighlight">#FF5800</color>
<color name="color_primary_dark">#e54000</color>
<color name="color_accent">#ffffa748</color>
<color name="color_navigationBar">#ff4800</color>
<color name="text_hint_color">#9b9b9b</color>
<color name="text_color">#303030</color>
<color name="text_color1">#626262</color>
<color name="text_color2">#9b9b9b</color>
<color name="bg_selected_gray">#ffe4e4e4</color>
<color name="black">#ff000000</color>
<color name="black_90">#f05e5e5e</color>
<color name="black_85">#D9000000</color>
<color name="black_80">#cc000000</color>
<color name="black_65">#333333</color>
<color name="color_626262">#626262</color>
<color name="black_60">#9f000000</color>
<color name="black_50">#80000000</color>
<color name="black_30">#4C000000</color>
<color name="black_40">#40444e</color>
<color name="black_10">#3e4043</color>
<color name="color_d0e1fd">#d0e1fd</color>
<color name="color_979797">#979797</color>
<color name="at_add_device_success">@color/black_80</color>
<color name="at_circleprogress_bg">#4c1CC9F4</color>
<color name="gray">#ffaaaaaa</color>
<color name="gray_5">#f4f4f2</color>
<color name="gray_10">#4c4c4c</color>
<color name="gray_85">#D9adb1b6</color>
<color name="gray_70">#B3adb1b6</color>
<color name="gray_50">#80adb1b6</color>
<color name="gray_30">#4Cadb1b6</color>
<color name="gray_99">#999999</color>
<color name="white">#ffffffff</color>
<color name="white_90">#e6ffffff</color>
<color name="orange">#ffffa748</color>
<color name="red">#FF0000</color>
<color name="green">#69c94f</color>
<color name="green_85">#D969c94f</color>
<color name="green_80">#cc42c800</color>
<color name="orange_58">#ff5800</color>
<color name="blue">#688ccc</color>
<color name="blue_30">#8b9bba</color>
<color name="blue_34">#415881</color>
<!-- SwipeRefreshLayout -->
<color name="google_red">#d62d20</color>
<color name="google_blue">#0057e7</color>
<color name="google_green">#008744</color>
<color name="google_yellow">#ffa700</color>
<color name="line_grey">#9b9b9b</color>
<color name="bottom_dialog_title_color">#9b9b9b</color>
<color name="edittext_textcolor">#626262</color>
<color name="color_primary">#F8F8F8</color>
<color name="status_font_color">#303030</color>
<color name="status_bg_color">#FAFAFA</color>
<color name="status_system_bg_color">#303030</color>
<color name="navbar_font_color">#ff5800</color>
<color name="navbar_bg_color">#FAFAFA</color>
<color name="app_bg_color">#F2F2F2</color>
<color name="colorDark">#303030</color>
<color name="list_primary_color">#303030</color>
<color name="list_sub_color">#626262</color>
<color name="list_secondary_color">#9b9b9b</color>
<color name="list_line_color">#dbdbdb</color>
<color name="list_bg_color">#ffffff</color>
<color name="notice_font_color">#ff5800</color>
<color name="notice_bg_color">#fff8d8</color>
<color name="edit_friend_text_tip">#B0B0B0</color>
<color name="primary_button_font_color">#ffffff</color>
<color name="primary_button_bg_color">#ff5800</color>
<color name="primary_button_select_color">#E54F00</color>
<color name="secondary_button_font_color">#ff5800</color>
<color name="secondary_button_bg_color">#ffffff</color>
<color name="color_CC4600">#cc4600</color>
<color name="color_bdbdbd">#bdbdbd</color>
<color name="color_303030">#303030</color>
<color name="switch_on_color">#2A87D6</color>
<color name="switch_off_color">#303030</color>
<color name="color_dddddd">#dddddd</color>
</resources>
\ No newline at end of file
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="button_height">50dp</dimen>
<dimen name="ty_cell_height">48dp</dimen>
<dimen name="ty_common_margin">16dp</dimen>
<dimen name="ty_common_line_margin">8dp</dimen>
<dimen name="ty_top_margin">7dp</dimen>
<dimen name="ty_bottom_margin">8dp</dimen>
<dimen name="toolbar_height">56dp</dimen>
<dimen name="toolbar_item_width">56dp</dimen>
<dimen name="tuya_title_size">17sp</dimen>
<dimen name="single_pix">1px</dimen>
<dimen name="double_pix">2px</dimen>
<dimen name="lose_pix">-1px</dimen>
<dimen name="ts_44">44sp</dimen>
<dimen name="ts_40">40sp</dimen>
<dimen name="ts_32">32sp</dimen>
<dimen name="ts_24">24sp</dimen>
<dimen name="ts_22">22sp</dimen>
<dimen name="ts_20">20sp</dimen>
<dimen name="ts_18">18sp</dimen>
<dimen name="ts_16">16sp</dimen>
<dimen name="ts_15">15sp</dimen>
<dimen name="ts_14">14sp</dimen>
<dimen name="ts_13">13sp</dimen>
<dimen name="ts_12">12sp</dimen>
<dimen name="ts_11">11sp</dimen>
<dimen name="ts_10">10sp</dimen>
<dimen name="line_dip">1px</dimen>
<dimen name="mg_440">440dp</dimen>
<dimen name="mg_360">360dp</dimen>
<dimen name="mg_245">245dp</dimen>
<dimen name="mg_240">240dp</dimen>
<dimen name="mg_190">190dp</dimen>
<dimen name="mg_140">140dp</dimen>
<dimen name="mg_108">108dp</dimen>
<dimen name="mg_100">100dp</dimen>
<dimen name="mg_90">90dp</dimen>
<dimen name="mg_88">88dp</dimen>
<dimen name="mg_85">85dp</dimen>
<dimen name="mg_80">80dp</dimen>
<dimen name="mg_75">75dp</dimen>
<dimen name="mg_70">70dp</dimen>
<dimen name="mg_68">68dp</dimen>
<dimen name="mg_66">66dp</dimen>
<dimen name="mg_60">60dp</dimen>
<dimen name="mg_50">50dp</dimen>
<dimen name="mg_48">48dp</dimen>
<dimen name="mg_44">44dp</dimen>
<dimen name="mg_40">40dp</dimen>
<dimen name="mg_36">36dp</dimen>
<dimen name="mg_35">35dp</dimen>
<dimen name="mg_30">30dp</dimen>
<dimen name="mg_28">28dp</dimen>
<dimen name="mg_25">25dp</dimen>
<dimen name="mg_24">24dp</dimen>
<dimen name="mg_18">18dp</dimen>
<dimen name="mg_22">22dp</dimen>
<dimen name="mg_20">20dp</dimen>
<dimen name="mg_16">16dp</dimen>
<dimen name="mg_15">15dp</dimen>
<dimen name="mg_14">14dp</dimen>
<dimen name="mg_13">13dp</dimen>
<dimen name="mg_12">12dp</dimen>
<dimen name="mg_10">10dp</dimen>
<dimen name="mg_8">8dp</dimen>
<dimen name="mg_6">6dp</dimen>
<dimen name="mg_5">5dp</dimen>
<dimen name="mg_3">3dp</dimen>
<dimen name="mg_4">4dp</dimen>
<dimen name="mg_2">2dp</dimen>
<dimen name="wh_185">185dp</dimen>
<dimen name="wh_180">180dp</dimen>
<dimen name="wh_160">160dp</dimen>
<dimen name="wh_140">140dp</dimen>
<dimen name="wh_120">120dp</dimen>
<dimen name="wh_100">100dp</dimen>
<dimen name="wh_96">96dp</dimen>
<dimen name="wh_95">95dp</dimen>
<dimen name="wh_90">90dp</dimen>
<dimen name="wh_88">88dp</dimen>
<dimen name="wh_85">85dp</dimen>
<dimen name="wh_80">80dp</dimen>
<dimen name="wh_70">70dp</dimen>
<dimen name="wh_65">65dp</dimen>
<dimen name="wh_64">64dp</dimen>
<dimen name="wh_60">60dp</dimen>
<dimen name="wh_58">55dp</dimen>
<dimen name="wh_55">55dp</dimen>
<dimen name="wh_50">50dp</dimen>
<dimen name="wh_49">49dp</dimen>
<dimen name="wh_48">48dp</dimen>
<dimen name="wh_44">44dp</dimen>
<dimen name="wh_42">42dp</dimen>
<dimen name="wh_36">36dp</dimen>
<dimen name="wh_35">35dp</dimen>
<dimen name="wh_32">32dp</dimen>
<dimen name="wh_30">30dp</dimen>
<dimen name="wh_28">28dp</dimen>
<dimen name="wh_27">27dp</dimen>
<dimen name="wh_25">25dp</dimen>
<dimen name="wh_23">23dp</dimen>
<dimen name="wh_20">20dp</dimen>
<dimen name="wh_15">15dp</dimen>
<dimen name="wh_14">14dp</dimen>
<dimen name="wh_6">6dp</dimen>
<dimen name="wh_4">4dp</dimen>
<dimen name="wh_2">2dp</dimen>
<dimen name="edit_height">44dp</dimen>
<dimen name="dialog_text_size">16sp</dimen>
<dimen name="dialog_big_text_size">18sp</dimen>
<dimen name="button_radius">1dp</dimen>
<dimen name="phone_button_height">44dp</dimen>
<dimen name="phone_button_margin_left">15dp</dimen>
<dimen name="phone_button_margin_right">15dp</dimen>
<dimen name="phone_button_text_size">15sp</dimen>
<dimen name="verification_margin_right">6dp</dimen>
<dimen name="phone_button_margin_top">20dp</dimen>
<dimen name="phone_button_padding_left">15dp</dimen>
<dimen name="verification_phone_button_margin_top">10dp</dimen>
<dimen name="three_grid_unit">24dp</dimen>
<dimen name="one_and_a_half_grid_unit">12dp</dimen>
<dimen name="mg_38">38dp</dimen>
<dimen name="mg_37">37dp</dimen>
<dimen name="mg_17">17dp</dimen>
<dimen name="ts_17">17sp</dimen>
<dimen name="mg_62">62dp</dimen>
<dimen name="mg_120">120dp</dimen>
<dimen name="mg_19">19dp</dimen>
<dimen name="mg_32">32dp</dimen>
</resources>
<resources>
<string name="panel_title">Camera</string>
<string name="hd">HD</string>
<string name="sd">SD</string>
<string name="other">Other</string>
<string name="speak">Speak</string>
<string name="snapshot">Snapshot</string>
<string name="record">Record</string>
<string name="replay">Replay</string>
<string name="cloud_video">Cloud video</string>
<string name="message">Message</string>
<string name="settings">Settings</string>
<string name="device_info">Device info</string>
<string name="get_clarity">Get clarity</string>
<string name="playback_title">Playback</string>
<string name="doorbell_title">Doorbell Call</string>
<string name="playback_input_hint">Input query date: </string>
<string name="query">Query</string>
<string name="start">Start</string>
<string name="pause">Pause</string>
<string name="resume">Resume</string>
<string name="stop">Stop</string>
<string name="message_title">Detection Alarm</string>
<string name="info_title">Device Info</string>
<string name="low_power">is low power: </string>
<string name="video_num">video num: </string>
<string name="default_definition">default definition: </string>
<string name="is_support_speaker">is support speaker: </string>
<string name="is_support_picK_up">is support pick up: </string>
<string name="is_support_talk">is support talk back mode up: </string>
<string name="default_talk_mode">default talk back mode: </string>
<string name="support_speed">support playback speed: </string>
<string name="raw_data">raw data: </string>
<string name="settings_title">Settings</string>
<string name="cloud_video_title">Cloud Video</string>
<string name="download">Download</string>
<string name="duration">Duration: </string>
<string name="record_start">Start record</string>
<string name="record_stop">Stop record</string>
<string name="cloud_status">Cloud Storage Status</string>
<string name="buy">Buy Cloud Storage</string>
<string name="query_cloud">1. Query Cloud Storage Data</string>
<string name="query_cloud_of_day">2. Query Appoint Time Data</string>
<string name="download_suc">Download success</string>
<string name="operation_failed">Operation failed</string>
<string name="no_data">No data for query date</string>
<string name="connect_first">Please connect device first</string>
<string name="input_err">Input error</string>
<string name="operation_suc">Operation success</string>
<string name="connect_failed">Connect failed</string>
<string name="not_support">Unsupported</string>
<string name="format_status">format status: </string>
<string name="not_input_query_data">Input query date</string>
<string name="get_current_clarity">Current video clarity is: </string>
<string name="not_support_device">The device is not supported</string>
<string name="err_code">Status value: </string>
<string name="current_state">Current state: </string>
<string name="ipc_doorbell_refuse">Refuse</string>
<string name="ipc_doorbell_hangup">Hang up</string>
<string name="ipc_doorbell_accept">Accept</string>
<string name="menu_remove_device">Remove Device</string>
<string name="remove_device_dialog">Are you sure remove this device?</string>
<string name="confirm">Confirm</string>
<string name="cloud_start">3. Start</string>
<string name="ipc_debug_tool">DEBUG TOOL</string>
<string name="ipc_cloud_debug_tools">Cloud storage debugging tool</string>
<string name="ipc_ptz">PTZ</string>
<string name="ipc_close">Close</string>
<string name="ipc_not_support_ptz">Not support PTZ</string>
<string name="ipc_ptz_control">PTZ Control</string>
<string name="ipc_left">LEFT</string>
<string name="ipc_top">TOP</string>
<string name="ipc_right">RIGHT</string>
<string name="ipc_bottom">BOTTOM</string>
<string name="ipc_focal_length">Focal length</string>
<string name="ipc_increase">increase</string>
<string name="ipc_reduce">reduce</string>
<string name="ipc_collection_point">Collection point</string>
<string name="ipc_add">ADD</string>
<string name="ipc_delete">DELETE</string>
<string name="ipc_cruise">cruise</string>
<string name="ipc_cruise_mode">cruise mode</string>
<string name="ipc_cruise_time">cruise time</string>
<string name="ipc_tracking">Mobile tracking</string>
<string name="ipc_preset_point">Preset point</string>
<string name="ipc_panoramic_cruise">Panoramic cruise</string>
<string name="ipc_collection_point_cruise">Collection point cruise</string>
<string name="ipc_full_day_cruise">Full day cruise</string>
<string name="ipc_custom_cruise">Custom cruise</string>
<string name="ipc_sdk_autotest_tools">IPC SDK automated test tool</string>
<string name="ipc_stop_talk">Stop talk</string>
<string name="ipc_start_talk">Start talk</string>
<string name="ipc_sdk_service_running">running</string>
<string name="ipc_sdk_service_expired">expired</string>
<string name="ipc_sdk_no_service">no service</string>
</resources>
\ No newline at end of file
include ':base_res'
include ':device_management'
include ':device_config'
include ':app'
include ':home'
include ':sweeper'
rootProject.name = "TuyaSDKSample"
include ':ipc'
/build
\ No newline at end of file
plugins {
id 'com.android.library'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
android {
compileSdkVersion 33
defaultConfig {
minSdkVersion 21
targetSdkVersion 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation project(':base_res')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation "org.jetbrains.kotlin:kotlin-reflect:1.5.21"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation "com.thingclips.smart:sweeper:5.1.0"
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.thing.smart.sweeper">
<application>
<activity
android:screenOrientation="portrait"
android:name=".SweeperActivity" />
<activity
android:screenOrientation="portrait"
android:name=".P2pConnectActivity" />
</application>
</manifest>
\ No newline at end of file
package com.thing.smart.sweeper
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.thingclips.smart.optimus.sdk.ThingOptimusSdk
import com.thingclips.smart.optimus.sweeper.api.IThingSweeperKitSdk
import com.thingclips.smart.optimus.sweeper.api.IThingSweeperP2P
import com.thingclips.smart.sweepe.p2p.bean.SweeperP2PBean
import com.thingclips.smart.sweepe.p2p.callback.SweeperP2PCallback
import com.thingclips.smart.sweepe.p2p.callback.SweeperP2PDataCallback
import com.thingclips.smart.sweepe.p2p.manager.DownloadType
import kotlinx.android.synthetic.main.activity_p2p_connect.*
/**
*
* create by nielev on 2023/2/24
*/
class P2pConnectActivity : AppCompatActivity(){
var mSweeperP2P:IThingSweeperP2P? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_p2p_connect)
setSupportActionBar(topAppBar)
topAppBar.setNavigationOnClickListener { finish() }
//get device id
val devId = intent.getStringExtra("deviceId")
//get sweeperp2p
val iThingSweeperKitSdk = ThingOptimusSdk.getManager(
IThingSweeperKitSdk::class.java
)
if (null != iThingSweeperKitSdk) {
mSweeperP2P = iThingSweeperKitSdk.getSweeperP2PInstance(devId)
//p2p connect
btnStartConnectP2pStep.setOnClickListener { v: View? ->
mSweeperP2P?.connectDeviceByP2P(object : SweeperP2PCallback {
override fun onSuccess() {
tvP2pConnectShow.text = "${getString(R.string.p2p_connect_status)} true"
//p2p connect suc, start get Sweeper data
mSweeperP2P?.startObserverSweeperDataByP2P(
DownloadType.P2PDownloadTypeStill,
object : SweeperP2PCallback {
override fun onSuccess() {
//start suc
tvP2pDownloadDataStatus.text =
"${getString(R.string.p2p_download_data_status)} true"
}
override fun onFailure(i: Int) {
//start failure
tvP2pDownloadDataStatus.text =
"${getString(R.string.p2p_download_data_status)} false"
}
},
object : SweeperP2PDataCallback {
override fun receiveData(i: Int, sweeperP2PBean: SweeperP2PBean?) {
//get Data
}
override fun onFailure(i: Int) {}
})
}
override fun onFailure(i: Int) {
tvP2pConnectShow.text = "${getString(R.string.p2p_connect_status)} false"
}
})
}
btnStopP2PData.setOnClickListener {
mSweeperP2P?.stopObserverSweeperDataByP2P(object : SweeperP2PCallback {
override fun onSuccess() {
//stop suc
}
override fun onFailure(i: Int) {
//stop suc
}
})
}
}
}
}
\ No newline at end of file
package com.thing.smart.sweeper
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import kotlinx.android.synthetic.main.activity_sweeper.*
/**
*
* create by nielev on 2023/2/24
*/
class SweeperActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sweeper)
setSupportActionBar(topAppBar)
topAppBar.setNavigationOnClickListener { finish() }
btnCommonControl.setOnClickListener { v -> // Navigate to device management
try {
val deviceControl =
Class.forName("com.tuya.appsdk.sample.device.mgt.control.activity.DeviceMgtControlActivity")
val intent = Intent(v.context, deviceControl)
intent.putExtra("deviceId", getIntent().getStringExtra("deviceId"))
v.context.startActivity(intent)
} catch (e: ClassNotFoundException) {
e.printStackTrace()
}
}
btnP2pConnect.setOnClickListener { v ->
val intent = Intent(
v.context,
P2pConnectActivity::class.java
)
intent.putExtra("deviceId", getIntent().getStringExtra("deviceId"))
v.context.startActivity(intent)
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/p2pConnect">
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/btnStartConnectP2pStep"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:textSize="20sp"
android:textStyle="bold"
android:layout_gravity="center_horizontal"
android:text="@string/p2p_step_1"/>
<TextView
android:id="@+id/tvP2pConnectShow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="@string/p2p_connect_status"/>
<TextView
android:id="@+id/tvP2pDownloadDataStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="@string/p2p_download_data_status"/>
<Button
android:id="@+id/btnStopP2PData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:textSize="20sp"
android:textStyle="bold"
android:layout_gravity="center_horizontal"
android:text="@string/p2p_stop_data"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/sweeper" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<Button
android:id="@+id/btnCommonControl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:textSize="20sp"
android:textStyle="bold"
android:layout_gravity="center_horizontal"
android:text="@string/commonControl"/>
<Button
android:id="@+id/btnP2pConnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:textSize="20sp"
android:textStyle="bold"
android:layout_gravity="center_horizontal"
android:text="@string/p2pConnect"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sweeper">扫地机</string>
<string name="commonControl">通用dp点控制</string>
<string name="p2pConnect">P2P 连接</string>
<string name="p2p_step_1">P2P 通道建立连接</string>
<string name="p2p_connect_status">p2p通道开启状态</string>
<string name="p2p_download_data_status">p2p开启数据下载状态</string>
<string name="p2p_stop_data">P2P 停止接收数据</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sweeper">Sweeper</string>
<string name="commonControl">Common Control</string>
<string name="p2pConnect">P2P Connect</string>
<string name="p2p_step_1">P2P Connect</string>
<string name="p2p_connect_status">step1:p2p connect status</string>
<string name="p2p_download_data_status">step2:p2p download data status</string>
<string name="p2p_stop_data">P2P stop get Data</string>
</resources>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment