diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_toast_layout.xml b/app/src/main/res/drawable/bg_toast_layout.xml new file mode 100644 index 0000000..78ff8e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_toast_layout.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_toast_layout.xml b/app/src/main/res/drawable/bg_toast_layout.xml new file mode 100644 index 0000000..78ff8e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_toast_layout.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_toast_layout.xml b/app/src/main/res/drawable/bg_toast_layout.xml new file mode 100644 index 0000000..78ff8e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_toast_layout.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_android_studio.xml b/app/src/main/res/drawable/ic_android_studio.xml new file mode 100644 index 0000000..d454d2a --- /dev/null +++ b/app/src/main/res/drawable/ic_android_studio.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_toast_layout.xml b/app/src/main/res/drawable/bg_toast_layout.xml new file mode 100644 index 0000000..78ff8e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_toast_layout.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_android_studio.xml b/app/src/main/res/drawable/ic_android_studio.xml new file mode 100644 index 0000000..d454d2a --- /dev/null +++ b/app/src/main/res/drawable/ic_android_studio.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_audio.xml b/app/src/main/res/drawable/ic_audio.xml new file mode 100644 index 0000000..bbfe013 --- /dev/null +++ b/app/src/main/res/drawable/ic_audio.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_toast_layout.xml b/app/src/main/res/drawable/bg_toast_layout.xml new file mode 100644 index 0000000..78ff8e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_toast_layout.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_android_studio.xml b/app/src/main/res/drawable/ic_android_studio.xml new file mode 100644 index 0000000..d454d2a --- /dev/null +++ b/app/src/main/res/drawable/ic_android_studio.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_audio.xml b/app/src/main/res/drawable/ic_audio.xml new file mode 100644 index 0000000..bbfe013 --- /dev/null +++ b/app/src/main/res/drawable/ic_audio.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000..b612ce9 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_toast_layout.xml b/app/src/main/res/drawable/bg_toast_layout.xml new file mode 100644 index 0000000..78ff8e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_toast_layout.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_android_studio.xml b/app/src/main/res/drawable/ic_android_studio.xml new file mode 100644 index 0000000..d454d2a --- /dev/null +++ b/app/src/main/res/drawable/ic_android_studio.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_audio.xml b/app/src/main/res/drawable/ic_audio.xml new file mode 100644 index 0000000..bbfe013 --- /dev/null +++ b/app/src/main/res/drawable/ic_audio.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000..b612ce9 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_no_network.xml b/app/src/main/res/drawable/ic_no_network.xml new file mode 100644 index 0000000..faa9331 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_network.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_toast_layout.xml b/app/src/main/res/drawable/bg_toast_layout.xml new file mode 100644 index 0000000..78ff8e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_toast_layout.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_android_studio.xml b/app/src/main/res/drawable/ic_android_studio.xml new file mode 100644 index 0000000..d454d2a --- /dev/null +++ b/app/src/main/res/drawable/ic_android_studio.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_audio.xml b/app/src/main/res/drawable/ic_audio.xml new file mode 100644 index 0000000..bbfe013 --- /dev/null +++ b/app/src/main/res/drawable/ic_audio.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000..b612ce9 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_no_network.xml b/app/src/main/res/drawable/ic_no_network.xml new file mode 100644 index 0000000..faa9331 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_network.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_storage.xml b/app/src/main/res/drawable/ic_storage.xml new file mode 100644 index 0000000..898c1cf --- /dev/null +++ b/app/src/main/res/drawable/ic_storage.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_toast_layout.xml b/app/src/main/res/drawable/bg_toast_layout.xml new file mode 100644 index 0000000..78ff8e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_toast_layout.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_android_studio.xml b/app/src/main/res/drawable/ic_android_studio.xml new file mode 100644 index 0000000..d454d2a --- /dev/null +++ b/app/src/main/res/drawable/ic_android_studio.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_audio.xml b/app/src/main/res/drawable/ic_audio.xml new file mode 100644 index 0000000..bbfe013 --- /dev/null +++ b/app/src/main/res/drawable/ic_audio.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000..b612ce9 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_no_network.xml b/app/src/main/res/drawable/ic_no_network.xml new file mode 100644 index 0000000..faa9331 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_network.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_storage.xml b/app/src/main/res/drawable/ic_storage.xml new file mode 100644 index 0000000..898c1cf --- /dev/null +++ b/app/src/main/res/drawable/ic_storage.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..0cb3f4a --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_toast_layout.xml b/app/src/main/res/drawable/bg_toast_layout.xml new file mode 100644 index 0000000..78ff8e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_toast_layout.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_android_studio.xml b/app/src/main/res/drawable/ic_android_studio.xml new file mode 100644 index 0000000..d454d2a --- /dev/null +++ b/app/src/main/res/drawable/ic_android_studio.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_audio.xml b/app/src/main/res/drawable/ic_audio.xml new file mode 100644 index 0000000..bbfe013 --- /dev/null +++ b/app/src/main/res/drawable/ic_audio.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000..b612ce9 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_no_network.xml b/app/src/main/res/drawable/ic_no_network.xml new file mode 100644 index 0000000..faa9331 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_network.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_storage.xml b/app/src/main/res/drawable/ic_storage.xml new file mode 100644 index 0000000..898c1cf --- /dev/null +++ b/app/src/main/res/drawable/ic_storage.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..0cb3f4a --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 0000000..0ce1ac3 --- /dev/null +++ b/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac033af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/.gitignore +/.idea/compiler.xml +/.idea/gradle.xml +/.idea/jarRepositories.xml +/.idea/misc.xml +/.idea/vcs.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..aa3e500 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,111 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + signingConfigs { + release { + storeFile file('/Users/a203/Desktop/AndroidProjects/SanxiTownSmartWell/app/SmartWell.jks') + storePassword '123456789' + keyAlias 'key0' + keyPassword '123456789' + } + } + compileSdkVersion 31 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.casic.app.smartwell.sanxi" + minSdkVersion 23 + targetSdkVersion 31 + versionCode 1 + versionName "1.0.0" + + // 添加手机架构过滤,去掉老旧手机兼容 + /** + * 只适配armeabi: + * 优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64 + * 缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 + + * 只适配 armeabi-v7a + * 筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 + + * 只适配 arm64-v8 + * 优点:性能最佳(微信大哥采用的) + * 缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户 + * */ + ndk { abiFilters "armeabi", "armeabi-v7a", "arm64-v8" } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlin { + experimental { + coroutines 'enable' + } + } + + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = defaultConfig.versionName + ".apk" + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //基础依赖库 + implementation 'com.github.AndroidCoderPeng:Android-library:1.6.0' + //Google官方授权框架 + implementation 'pub.devrel:easypermissions:3.0.0' + //腾讯Android UI框架 + implementation 'com.qmuiteam:qmui:2.0.0-alpha10' + implementation 'com.qmuiteam:arch:0.3.1' + //沉浸式状态栏。基础依赖包,必须要依赖 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //fragment快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + //MVVM+LiveData + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + //Kotlin协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.9.0' + //图片选择框架 + implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0' + //返回值转换器 + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-scalars:2.3.0' + implementation 'com.squareup.retrofit2:converter-gson:2.8.1' + //okhttp3日志拦截器 + implementation 'com.squareup.okhttp3:logging-interceptor:4.6.0' + //网络请求和接口封装 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.0' + //官方Json解析库 + implementation 'com.google.code.gson:gson:2.8.6' + //上拉加载下拉刷新 + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0' + //高德导航 + implementation 'com.amap.api:navi-3dmap:8.1.0_3dmap8.1.0' + //日期选择器 + implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1' + //标签流式布局 + implementation group: 'com.hyman', name: 'flowlayout-lib', version: '1.1.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82a0cf2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt new file mode 100644 index 0000000..70e879b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.casic.app.smartwell.sanxi.base + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.casic.app.smartwell.sanxi.extensions.isNetworkConnected +import com.casic.app.smartwell.sanxi.utils.PageNavigationManager +import com.casic.app.smartwell.sanxi.utils.StatusBarColorUtil +import com.casic.app.smartwell.sanxi.widgets.NoNetworkDialog +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.app.multilib.utils.BroadcastManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel + + +/** + * 普通页面的基础类 + * */ +abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(initLayoutView()) + ImmersionBar.with(this).statusBarDarkFont(false).init() //沉浸式状态栏 + when (this.javaClass.simpleName) { + "WelcomeActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + "SplashScreenActivity" -> ImmersionBar.with(this).statusBarDarkFont(false).init() + "LoginActivity" -> ImmersionBar.with(this).statusBarDarkFont(true).init() + else -> StatusBarColorUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + } + setupTopBarLayout() + initData() + initEvent() + PageNavigationManager.addActivity(this) + BroadcastManager.getInstance(this) + .addAction(ConnectivityManager.CONNECTIVITY_ACTION, object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (!context!!.isNetworkConnected()) { + NoNetworkDialog.Builder() + .setContext(this@BaseActivity) + .setOnDialogButtonClickListener(object : + NoNetworkDialog.OnDialogButtonClickListener { + override fun onButtonClick() { + startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS)) + } + }).build().show() + } + } + }) + } + + /** + * 初始化xml布局 + */ + abstract fun initLayoutView(): Int + + /** + * 特定页面定制沉浸式状态栏 + */ + protected abstract fun setupTopBarLayout() + + /** + * 初始化默认数据 + */ + abstract fun initData() + + /** + * 初始化业务逻辑 + */ + abstract fun initEvent() + + /** + * 取消协程 + * */ + override fun onDestroy() { + BroadcastManager.getInstance(this).destroy(ConnectivityManager.CONNECTIVITY_ACTION) + cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt new file mode 100644 index 0000000..2c5ea4a --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseApplication.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.base + +import android.app.Application +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import kotlin.properties.Delegates + +class BaseApplication : Application() { + + companion object { + private var instance: BaseApplication by Delegates.notNull() + + fun obtainInstance() = instance + } + + override fun onCreate() { + super.onCreate() + instance = this + SaveKeyValues.initSharedPreferences() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt new file mode 100644 index 0000000..e0b5582 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/base/BaseViewModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.sanxi.base + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.casic.app.smartwell.sanxi.utils.LoadState + +abstract class BaseViewModel : ViewModel() { + val loadState = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt new file mode 100644 index 0000000..92ee9b2 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Any.kt @@ -0,0 +1,5 @@ +package com.casic.app.smartwell.sanxi.extensions + +import com.google.gson.Gson + +fun Any.toJson(): String = Gson().toJson(this) \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt new file mode 100644 index 0000000..e77dbaa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ArrayList.kt @@ -0,0 +1,28 @@ +package com.casic.app.smartwell.sanxi.extensions + +/** + * ArrayList扩展方法 + */ + +//将图片集合格式化成满足上传格式的数据 +fun ArrayList.reformat(): String { + if (this.isEmpty()) return "" + val builder = StringBuilder() + //循环遍历元素,同时得到元素index(下标) + this.forEachIndexed { index, s -> + if (index == this.size - 1) { + builder.append(s) + } else { + builder.append(s).append(",") + } + } + return builder.toString() +} + +fun addAll(vararg args: String): ArrayList { + val result = ArrayList() + args.forEach { + result.add(it) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt new file mode 100644 index 0000000..e73f47f --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Context.kt @@ -0,0 +1,54 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import com.casic.app.smartwell.sanxi.utils.Constant + +/** + * 判断是否有网络连接 + * @return + */ +fun Context.isNetworkConnected(): Boolean { //true是连接,false是没连接 + val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + if (manager == null) { + return false + } else { + val netWorkInfo = manager.activeNetworkInfo + if (netWorkInfo != null) { + return netWorkInfo.isAvailable + } + } + return false +} + +/** + * Context内联函数-扩展函数 + * */ +inline fun Context.getSystemService(): T? { + return this.getSystemService(T::class.java) +} + +inline fun Context.navigatePageTo() { + startActivity(Intent(this, T::class.java)) +} + +inline fun Context.navigatePageTo(value: String) { + val intent = Intent(this, T::class.java) + intent.putExtra(Constant.INTENT_PARAM, value) + startActivity(intent) +} + +inline fun Context.navigatePageTo(values: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putStringArrayListExtra(Constant.INTENT_PARAM, values) + startActivity(intent) +} + +inline fun Context.navigatePageTo(index: Int, imageList: ArrayList) { + val intent = Intent(this, T::class.java) + intent.putExtra("index", index) + intent.putStringArrayListExtra("images", imageList) + startActivity(intent) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt new file mode 100644 index 0000000..e5eb835 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Dialog.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.WindowManager +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.pengxh.app.multilib.utils.SizeUtil + +fun Dialog.initDialogLayoutParams(gravity: Int, animResId: Int, scale: Float) { + val window = window ?: return + window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + window.decorView.setBackgroundColor(Color.TRANSPARENT) + window.setGravity(gravity) + //设置Dialog出现的动画 + window.setWindowAnimations(animResId) + val params = window.attributes + params.width = (SizeUtil.getScreenWidth(BaseApplication.obtainInstance()) * scale).toInt() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + window.attributes = params +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt new file mode 100644 index 0000000..0cf73cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ImageView.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.animation.LinearInterpolator +import android.widget.ImageView + +fun ImageView.arrowAnimation(angle: Float) { + val animation = this.animate() + animation.duration = 300 + animation.interpolator = LinearInterpolator() + animation.rotation(angle) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt new file mode 100644 index 0000000..01e173b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Int.kt @@ -0,0 +1,41 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.app.Activity +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat + +//颜色扩展 +fun Int.convertColor(context: Context): Int = ContextCompat.getColor(context, this) + +//控件ID内连函数扩展 +inline fun Int.findViewById(activity: Activity): T { + return activity.findViewById(this) +} + +//窨井类型转换 +fun Int.valueToType(): String { + return when (this) { + 1 -> "雨水井" + 2 -> "污水井" + 3 -> "燃气井" + 4 -> "热力井" + 5 -> "电力井" + 6 -> "交通井" + 7 -> "路灯井" + 8 -> "通信井" + 9 -> "监控井" + 10 -> "其他" + else -> { + "未知类型" + } + } +} + +fun Int.toLevel(): String { + return if (this == 0) { + "" + } else { + this.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt new file mode 100644 index 0000000..3be08a8 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Long.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +fun Long.timestampToDate(): String = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date(this)) + +fun Long.isEarlierThanStart(date: String): Boolean { + if (date.isBlank()) { + return false + } + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) + try { + return this < dateFormat.parse(date)!!.time + } catch (e: ParseException) { + + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt new file mode 100644 index 0000000..e61367c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/Poi.kt @@ -0,0 +1,20 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.content.Context +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.navi.AmapPageType + +/** + * 步行导航扩展函数 + * */ +fun Poi.showRouteOnMap(context: Context) { + val params = AmapNaviParams( + null, null, this, + AmapNaviType.WALK, + AmapPageType.ROUTE + ) + AmapNaviPage.getInstance().showRouteActivity(context, params, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt new file mode 100644 index 0000000..0e9e95b --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/QMUIEmptyView.kt @@ -0,0 +1,12 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.view.View +import com.qmuiteam.qmui.widget.QMUIEmptyView + +fun QMUIEmptyView.showEmptyPage(onButtonClickListener: View.OnClickListener) { + this.show(false, "抱歉,无法查询到相关记录", null, "重试", onButtonClickListener) +} + +fun QMUIEmptyView.showEmptyPage(title: String, onButtonClickListener: View.OnClickListener) { + this.show(false, title, null, "刷新", onButtonClickListener) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt new file mode 100644 index 0000000..714b5fb --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/String.kt @@ -0,0 +1,171 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Color +import android.view.Gravity +import android.widget.TextView +import android.widget.Toast +import com.casic.app.smartwell.model.ErrorMessageModel +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseApplication +import com.casic.app.smartwell.sanxi.utils.Constant +import com.casic.app.smartwell.sanxi.utils.FileUtils +import com.casic.app.smartwell.sanxi.utils.IDownloadListener +import com.casic.app.smartwell.sanxi.utils.SaveKeyValues +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import okhttp3.* +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream + +/** + * String扩展方法 + */ + +//将Toast扩展到String +fun String.show() { + val context = BaseApplication.obtainInstance() + val toast = Toast(context) + val textView = TextView(context) + textView.setBackgroundResource(R.drawable.bg_toast_layout) + textView.setTextColor(Color.WHITE) + textView.textSize = 16.0f + textView.text = this + textView.setPadding( + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10), + QMUIDisplayHelper.dp2px(context, 20), + QMUIDisplayHelper.dp2px(context, 10) + ) + toast.setGravity(Gravity.BOTTOM, 0, QMUIDisplayHelper.dp2px(context, 80)) + toast.view = textView + toast.duration = Toast.LENGTH_SHORT + toast.show() +} + +fun String.isLetterAndDigit(): Boolean { + var isDigit = false + var isLetter = false + for (i in this.indices) { + if (Character.isDigit(this[i])) { + isDigit = true + } else if (Character.isLetter(this[i])) { + isLetter = true + } + } + return isDigit && isLetter +} + +//拼接图片地址 +fun String.combineImagePath(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this.replace("\\", "/")}" +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + Constant.DEFAULT_SERVER_CONFIG, + "http://111.198.10.15:11304" + ) as String + return "$defaultValue/static/${this}" +} + +//窨井类型转换 +fun String.toChinese(): String { + return when (this) { + "0" -> "全部" + "1" -> "一级" + "2" -> "二级" + "3" -> "三级" + else -> { + "未知" + } + } +} + +fun String.separateResponseCode(): Int { + if (this.isBlank()) { + return 404 + } + return JSONObject(this).getInt("code") +} + +fun String.toErrorMessage(): String { + val errorModel = Gson().fromJson( + this, object : TypeToken() {}.type + ) + return errorModel.message.toString() +} + +fun String.downloadFile(listener: IDownloadListener) { + val httpClient = OkHttpClient() + val request = Request.Builder().get().url(this).build() + val newCall = httpClient.newCall(request) + /** + * 如果已被加入下载队列,则取消之前的,重新下载 + * 断点下载以后再考虑 + * */ + if (newCall.isExecuted()) { + newCall.cancel() + } + val downloadPath = this + newCall.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + call.cancel() + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + var stream: InputStream? = null + val buf = ByteArray(2048) + var len: Int + var fos: FileOutputStream? = null + try { + val fileBody = response.body + if (fileBody != null) { + stream = fileBody.byteStream() + val total: Long = fileBody.contentLength() + listener.onDownloadStart(total) + val file = File( + FileUtils.downloadFilePath, + downloadPath.substring(downloadPath.lastIndexOf("/") + 1) + ) + fos = FileOutputStream(file) + var current: Long = 0 + while (stream.read(buf).also { len = it } != -1) { + fos.write(buf, 0, len) + current += len.toLong() + listener.onProgressChanged(current) + } + fos.flush() + listener.onDownloadEnd(file) + } + } catch (e: Exception) { + call.cancel() + e.printStackTrace() + } finally { + try { + stream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + try { + fos?.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt new file mode 100644 index 0000000..c49bff7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/View.kt @@ -0,0 +1,23 @@ +package com.casic.app.smartwell.sanxi.extensions + +import android.graphics.Bitmap +import android.view.View + +/** + * 把一个view转化成bitmap对象 + */ +fun View.toBitmap(): Bitmap? { + var bitmap: Bitmap? = null + try { + this.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + this.layout(0, 0, this.measuredWidth, this.measuredHeight) + this.buildDrawingCache() + bitmap = this.drawingCache + } catch (e: Exception) { + e.printStackTrace() + } + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt new file mode 100644 index 0000000..833b83e --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/extensions/ViewModel.kt @@ -0,0 +1,33 @@ +package com.casic.app.smartwell.sanxi.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * ViewModel扩展方法:启动协程 + * @param block 协程逻辑 + * @param onError 错误回调方法 + * @param onComplete 完成回调方法 + */ +fun ViewModel.launch( + block: suspend CoroutineScope.() -> Unit, + onError: (e: Throwable) -> Unit = {}, + onComplete: () -> Unit = {} +) { + viewModelScope.launch( + CoroutineExceptionHandler { _, throwable -> + run { + onError(throwable) + } + } + ) { + try { + block.invoke(this) + } finally { + onComplete() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt new file mode 100644 index 0000000..b6752b7 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/CommonResultModel.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.model + +/** + * 普通实体类,失败/成功数据结构一致 + */ +class CommonResultModel { + var code = 0 + var data: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt new file mode 100644 index 0000000..0b56128 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/model/ErrorMessageModel.kt @@ -0,0 +1,9 @@ +package com.casic.app.smartwell.model + +class ErrorMessageModel { + var code = 0 + var data: String? = null + var exceptionClazz: String? = null + var message: String? = null + var isSuccess = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt new file mode 100644 index 0000000..5f617b5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/Constant.kt @@ -0,0 +1,36 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.Manifest + + +object Constant { + val USER_PERMISSIONS = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + + const val INTENT_PARAM = "intentParam" + const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" + const val APP_AUTHORITY = "com.casic.app.smartwell.fileprovider" + + const val PERMISSIONS_CODE = 999 + const val FIVE_YEARS = 5L * 365 * 60 * 60 * 24 * 1000L + const val RADIUS_SIZE = 100 //相距多少米才聚合,单位:米 + const val DISTANCE = 5 //两点间距离阈值,单位:米 + const val PAGE_LIMIT = 20 + + // val HOME_ICONS = arrayOf(R.drawable.ic_well, R.drawable.ic_overtime, R.drawable.ic_bfcf) + val HOME_ITEMS = arrayOf("闸井管理", "超时工单", "布防撤防") + val SUB_PAGE_TITLES = arrayOf("待处理", "待确认", "处理中", "已完成") + val OVER_TIME_PAGE_TITLES = arrayOf("超时未接单", "超时未处理") + + // val POPUP_IMAGES = arrayOf(R.drawable.ic_menu_map, R.drawable.ic_satellite) + val POPUP_TITLES = arrayOf("标准地图", "卫星地图") +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt new file mode 100644 index 0000000..c62b362 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/DialogHelper.kt @@ -0,0 +1,29 @@ +package com.casic.app.smartwell.sanxi.utils; + +import android.app.Activity +import android.view.WindowManager +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +object DialogHelper { + private lateinit var loadingDialog: QMUITipDialog + + fun showLoadingDialog(activity: Activity, message: String?) { + loadingDialog = QMUITipDialog.Builder(activity) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message) + .create() + if (!activity.isDestroyed) { + try { + loadingDialog.show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } + } + } + + fun dismissLoadingDialog() { + if (loadingDialog.isShowing) { + loadingDialog.dismiss() + } + } +} diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt new file mode 100644 index 0000000..3e9ae35 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/FileUtils.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.os.Environment +import com.casic.app.smartwell.sanxi.base.BaseApplication +import java.io.File + +object FileUtils { + private const val Tag = "FileUtils" + private val context = BaseApplication.obtainInstance() + + //储存下载文件的目录 + val downloadFilePath: String + get() { + val downloadDir = + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "") + if (!downloadDir.exists()) { + downloadDir.mkdir() + } + return downloadDir.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt new file mode 100644 index 0000000..922cdb5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/GlideLoadEngine.kt @@ -0,0 +1,77 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.BitmapImageViewTarget +import com.casic.app.smartwell.sanxi.R +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.listener.OnImageCompleteCallback +import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView + +class GlideLoadEngine private constructor() : ImageEngine { + companion object { + private var instance: GlideLoadEngine? = null + fun createGlideEngine(): GlideLoadEngine? { + if (null == instance) { + synchronized(GlideLoadEngine::class.java) { + if (null == instance) { + instance = GlideLoadEngine() + } + } + } + return instance + } + } + + override fun loadImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).load(url).into(imageView) + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView, + callback: OnImageCompleteCallback + ) { + + } + + override fun loadImage( + context: Context, + url: String, + imageView: ImageView, + longImageView: SubsamplingScaleImageView + ) { + } + + override fun loadFolderImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .asBitmap() + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(object : BitmapImageViewTarget(imageView) { + override fun setResource(resource: Bitmap?) { + val circularBitmapDrawable = + RoundedBitmapDrawableFactory.create(context.resources, resource) + circularBitmapDrawable.cornerRadius = 8f + imageView.setImageDrawable(circularBitmapDrawable) + } + }) + } + + override fun loadAsGifImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context).asGif().load(url).into(imageView) + } + + override fun loadGridImage(context: Context, url: String, imageView: ImageView) { + Glide.with(context) + .load(url) + .apply(RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .into(imageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt new file mode 100644 index 0000000..62b3873 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/IDownloadListener.kt @@ -0,0 +1,11 @@ +package com.casic.app.smartwell.sanxi.utils + +import java.io.File + +interface IDownloadListener { + fun onDownloadStart(totalBytes: Long) + + fun onProgressChanged(currentBytes: Long) + + fun onDownloadEnd(file: File?) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt new file mode 100644 index 0000000..4d5350c --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/LoadState.kt @@ -0,0 +1,22 @@ +package com.casic.app.smartwell.sanxi.utils + +/** + * 加载状态 + * sealed 关键字表示此类仅内部继承 + */ +sealed class LoadState { + /** + * 加载中 + */ + object Loading : LoadState() + + /** + * 成功 + */ + object Success : LoadState() + + /** + * 失败 + */ + object Fail : LoadState() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt new file mode 100644 index 0000000..3062597 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/PageNavigationManager.kt @@ -0,0 +1,70 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.app.Activity +import java.util.* + +/** + * 统一管理所有Activity + * */ +object PageNavigationManager { + private var activityStack = Stack() + + /** + * 添加Activity到堆栈 + */ + fun addActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.push(activity) + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return activityStack.lastElement() + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + fun finishCurrentActivity() { + val activity = activityStack.pop() + activity.finish() + } + + /** + * 结束指定的Activity + */ + private fun finishActivity(activity: Activity?) { + if (activity == null) { + return + } + activityStack.remove(activity) + if (!activity.isFinishing) { + activity.finish() + } + } + + /** + * 结束指定类名的Activity + */ + fun finishActivity(clazz: Class) { + activityStack.forEach { + if (it.javaClass == clazz) { + finishActivity(it) + } + } + } + + /** + * 结束所有Activity + */ + fun finishAllActivity() { + activityStack.forEach { + it?.finish() + } + activityStack.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt new file mode 100644 index 0000000..36901fe --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/RSAUtils.kt @@ -0,0 +1,58 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = + processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = + Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = + X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt new file mode 100644 index 0000000..74ba912 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/SaveKeyValues.kt @@ -0,0 +1,110 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import com.casic.app.smartwell.sanxi.base.BaseApplication + +object SaveKeyValues { + private val context = BaseApplication.obtainInstance() + private var fileName: String? = null + + fun initSharedPreferences() { + val packageName = context.packageName + //获取到的包名带有“.”方便命名,取最后一个作为sp文件名,例如:com.casic.app.smartwell + val split = packageName.split(".") + fileName = split[split.size - 1] + } + + /** + * 存储 + */ + fun putValue(key: String?, obj: Any) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + val editor = sharedPreferences.edit() + when (obj) { + is String -> { + editor.putString(key, obj).apply() + } + is Int -> { + editor.putInt(key, obj).apply() + } + is Long -> { + editor.putLong(key, obj).apply() + } + is Boolean -> { + editor.putBoolean(key, obj).apply() + } + is Float -> { + editor.putFloat(key, obj).apply() + } + else -> { + editor.putString(key, obj.toString()).apply() + } + } + } + + /** + * 获取保存的数据 + */ + operator fun getValue(key: String?, defaultObject: Any?): Any? { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return when (defaultObject) { + is String -> { + sharedPreferences.getString(key, defaultObject as String?) + } + is Int -> { + sharedPreferences.getInt(key, (defaultObject as Int?)!!) + } + is Long -> { + sharedPreferences.getLong(key, (defaultObject as Long?)!!) + } + is Boolean -> { + sharedPreferences.getBoolean(key, (defaultObject as Boolean?)!!) + } + is Float -> { + sharedPreferences.getFloat(key, (defaultObject as Float?)!!) + } + else -> { + sharedPreferences.getString(key, null) + } + } + } + + /** + * 移除某个key值已经对应的值 + */ + fun removeKey(key: String?) { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().remove(key).apply() + } + + /** + * 清除所有数据 + */ + fun clearAll() { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + sharedPreferences.edit().clear().apply() + } + + /** + * 查询某个key是否存在 + */ + fun containsKey(key: String?): Boolean { + val sharedPreferences = context.getSharedPreferences( + fileName, + Context.MODE_PRIVATE + )!! + return sharedPreferences.contains(key) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt new file mode 100644 index 0000000..97530cc --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/StatusBarColorUtil.kt @@ -0,0 +1,47 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.R +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout + +object StatusBarColorUtil { + fun setColor(activity: Activity, color: Int) { //限制android系统的版本 + // 设置状态栏透明 + activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // 生成一个状态栏大小的矩形 + val statusView = createStatusView(activity, color) + // 添加 statusView 到布局中 + val decorView = activity.window.decorView as ViewGroup + decorView.addView(statusView) + // 设置根布局的参数 + val rootView = + (activity.findViewById(R.id.content) as ViewGroup).getChildAt( + 0 + ) as ViewGroup + rootView.fitsSystemWindows = true + rootView.clipToPadding = true + } + + /** + * 生成一个和状态栏大小相同的矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private fun createStatusView(activity: Activity, color: Int): View { // 获得状态栏高度 + val resourceId = + activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val statusBarHeight = activity.resources.getDimensionPixelSize(resourceId) + // 绘制一个和状态栏一样高的矩形 + val statusView = View(activity) + val params = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight) + statusView.layoutParams = params + statusView.setBackgroundColor(color) + return statusView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt new file mode 100644 index 0000000..10a1d29 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/utils/TimeLineItemDecoration.kt @@ -0,0 +1,96 @@ +package com.casic.app.smartwell.sanxi.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.convertColor +import com.pengxh.app.multilib.utils.SizeUtil + +class TimeLineItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() { + private val circlePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val radius: Int + + init { + linePaint.color = R.color.hintTextColor.convertColor(context) + linePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + radius = SizeUtil.dp2px(context, 7.5f) + } + + override fun getItemOffsets( + outRect: Rect, view: View, + parent: RecyclerView, state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + outRect.left = SizeUtil.dp2px(context, 16.5f) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + val childCount = parent.childCount + val layoutManager = parent.layoutManager + for (i in 0 until childCount) { + val childView = parent.getChildAt(i) + val childViewHeight = childView.height + val itemCount = parent.adapter!!.itemCount + val leftDecorationWidth = layoutManager!!.getLeftDecorationWidth(childView) + val topDecorationHeight = layoutManager.getTopDecorationHeight(childView) + val startX = (leftDecorationWidth shr 1).toFloat() + // 圆顶部部分竖线,起点 Y + val topStartY = (childView.top - topDecorationHeight).toFloat() + // 圆顶部部分竖线,终点 Y + val topStopY = (childView.top + (childViewHeight shr 1) - radius).toFloat() + + // 圆底部部分竖线,起点 Y + val bottomStartY = (childView.top + (childViewHeight shr 1) + radius).toFloat() + // 圆底部部分竖线,终点 Y + val bottomStopY = childView.bottom.toFloat() + + // 获取当前 item 是 recyclerview 的第几个 childView + val childPosition = parent.getChildLayoutPosition(childView) + + // 绘制圆 + when (childPosition) { + 0 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.hintTextColor.convertColor(context) + } + itemCount - 1 -> { + circlePaint.style = Paint.Style.FILL + circlePaint.color = R.color.mainThemeColor.convertColor(context) + } + else -> { + circlePaint.style = Paint.Style.STROKE + circlePaint.strokeWidth = SizeUtil.dp2px(context, 1.5f).toFloat() + } + } + canvas.drawCircle( + startX, + (childView.top + (childViewHeight shr 1)).toFloat(), + radius.toFloat(), + circlePaint + ) + + // 绘制竖线 + when (childPosition) { + // 第 0 位置上只需绘制下半部分 + 0 -> { + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + // 最后位置只需绘制上半部分 + parent.adapter!!.itemCount - 1 -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + } + // 都要绘制 + else -> { + canvas.drawLine(startX, topStartY, startX, topStopY, linePaint) + canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt new file mode 100644 index 0000000..5558062 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/MainActivity.kt @@ -0,0 +1,21 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity + +class MainActivity : BaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_main + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..4784d72 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,71 @@ +package com.casic.app.smartwell.sanxi.view + +import android.os.CountDownTimer +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo + +class SplashScreenActivity : BaseActivity() { + + private val kTag = "SplashScreenActivity" +// private lateinit var userDetailViewModel: UserDetailViewModel +// private lateinit var authenticateViewModel: AuthenticateViewModel +// private val imageModels: MutableList = ArrayList() + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + + } + + override fun initData() { +// userDetailViewModel = ViewModelProvider(this).get(UserDetailViewModel::class.java) +// authenticateViewModel = ViewModelProvider(this).get(AuthenticateViewModel::class.java) + } + + override fun initEvent() { +// Constant.NEWS_PAGE.forEach { +// WebCrawler.Builder() +// .addLifecycleCoroutineScope(lifecycleScope) +// .ignoreHttpErrors(true) +// .setTimeout(15000) +// .setTargetWebSite(it) +// .addOnWebCrawlerCallback(object : WebCrawler.OnWebCrawlerCallback { +// override fun onParseSuccess(model: BannerImageModel) { +// imageModels.add(model) +// SaveKeyValues.putValue(Constant.BANNER_MODEL, imageModels.toJson()) +// } +// +// override fun onParseFailure(e: Exception) { +// Log.e(kTag, "onParseFailure: $e", e) +// } +// }).build().start() +// } + countDownTimer.start() + } + + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ +// userDetailViewModel.obtainUserDetail() + /** + * 根据用户有权查看的设备类型 + */ +// authenticateViewModel.obtainAuthorByDept() +// userDetailViewModel.flag.observe(this@SplashScreenActivity, { +// if (it) { + navigatePageTo() +// } else { +// navigatePageTo(LoginActivity::class.java) +// } + finish() +// }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt new file mode 100644 index 0000000..11c75a5 --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/view/WelcomeActivity.kt @@ -0,0 +1,57 @@ +package com.casic.app.smartwell.sanxi.view + +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.base.BaseActivity +import com.casic.app.smartwell.sanxi.extensions.navigatePageTo +import com.casic.app.smartwell.sanxi.utils.Constant +import kotlinx.android.synthetic.main.activity_welcome.* +import pub.devrel.easypermissions.EasyPermissions + +//TODO 具体权限需要后期调整 +class WelcomeActivity : BaseActivity(), EasyPermissions.PermissionCallbacks { + + override fun initLayoutView(): Int = R.layout.activity_welcome + + override fun setupTopBarLayout() { + + } + + override fun initData() { + + } + + override fun initEvent() { + if (EasyPermissions.hasPermissions(this, *Constant.USER_PERMISSIONS)) { + navigatePageTo() + finish() + } else { + enterMainButton.setOnClickListener { + EasyPermissions.requestPermissions( + this@WelcomeActivity, + resources.getString(R.string.app_name) + "需要获取存储相关权限", + Constant.PERMISSIONS_CODE, + *Constant.USER_PERMISSIONS + ) + } + } + } + + override fun onPermissionsDenied(requestCode: Int, perms: MutableList) { + finish() + } + + override fun onPermissionsGranted(requestCode: Int, perms: MutableList) { + navigatePageTo() + finish() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + //将请求结果传递EasyPermission库处理 + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt new file mode 100644 index 0000000..ed5eefa --- /dev/null +++ b/app/src/main/java/com/casic/app/smartwell/sanxi/widgets/NoNetworkDialog.kt @@ -0,0 +1,52 @@ +package com.casic.app.smartwell.sanxi.widgets + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import com.casic.app.smartwell.sanxi.R +import com.casic.app.smartwell.sanxi.extensions.initDialogLayoutParams +import kotlinx.android.synthetic.main.dialog_no_network.* + +class NoNetworkDialog private constructor(builder: Builder) : Dialog( + builder.context, R.style.UserDefinedDialogStyle +) { + + private val listener: OnDialogButtonClickListener = builder.listener + + class Builder { + lateinit var context: Context + lateinit var listener: OnDialogButtonClickListener + + fun setContext(context: Context): Builder { + this.context = context + return this + } + + fun setOnDialogButtonClickListener(listener: OnDialogButtonClickListener): Builder { + this.listener = listener + return this + } + + fun build(): NoNetworkDialog { + return NoNetworkDialog(this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + this.initDialogLayoutParams(Gravity.CENTER, R.style.UserDefinedAnimation, 0.85f) + setContentView(R.layout.dialog_no_network) + setCancelable(false) + setCanceledOnTouchOutside(false) + + dialogButton.setOnClickListener { + listener.onButtonClick() + this.dismiss() + } + } + + interface OnDialogButtonClickListener { + fun onButtonClick() + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_hide.xml b/app/src/main/res/anim/action_dialog_hide.xml new file mode 100644 index 0000000..119726b --- /dev/null +++ b/app/src/main/res/anim/action_dialog_hide.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/action_dialog_show.xml b/app/src/main/res/anim/action_dialog_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/action_dialog_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_in.xml b/app/src/main/res/anim/activity_in.xml new file mode 100644 index 0000000..f2696ba --- /dev/null +++ b/app/src/main/res/anim/activity_in.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out.xml b/app/src/main/res/anim/activity_out.xml new file mode 100644 index 0000000..1e424a5 --- /dev/null +++ b/app/src/main/res/anim/activity_out.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_in.xml b/app/src/main/res/anim/bottom_sheet_in.xml new file mode 100644 index 0000000..276184e --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_in.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_sheet_out.xml b/app/src/main/res/anim/bottom_sheet_out.xml new file mode 100644 index 0000000..62be752 --- /dev/null +++ b/app/src/main/res/anim/bottom_sheet_out.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_hide.xml b/app/src/main/res/anim/popup_hide.xml new file mode 100644 index 0000000..55c7aca --- /dev/null +++ b/app/src/main/res/anim/popup_hide.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/popup_show.xml b/app/src/main/res/anim/popup_show.xml new file mode 100644 index 0000000..1d281ef --- /dev/null +++ b/app/src/main/res/anim/popup_show.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_relative_layout.xml b/app/src/main/res/drawable/bg_relative_layout.xml new file mode 100644 index 0000000..f00d59c --- /dev/null +++ b/app/src/main/res/drawable/bg_relative_layout.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_toast_layout.xml b/app/src/main/res/drawable/bg_toast_layout.xml new file mode 100644 index 0000000..78ff8e6 --- /dev/null +++ b/app/src/main/res/drawable/bg_toast_layout.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_android_studio.xml b/app/src/main/res/drawable/ic_android_studio.xml new file mode 100644 index 0000000..d454d2a --- /dev/null +++ b/app/src/main/res/drawable/ic_android_studio.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_audio.xml b/app/src/main/res/drawable/ic_audio.xml new file mode 100644 index 0000000..bbfe013 --- /dev/null +++ b/app/src/main/res/drawable/ic_audio.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000..b612ce9 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_no_network.xml b/app/src/main/res/drawable/ic_no_network.xml new file mode 100644 index 0000000..faa9331 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_network.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_storage.xml b/app/src/main/res/drawable/ic_storage.xml new file mode 100644 index 0000000..898c1cf --- /dev/null +++ b/app/src/main/res/drawable/ic_storage.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..0cb3f4a --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 0000000..0ce1ac3 --- /dev/null +++ b/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml new file mode 100644 index 0000000..124e5b9 --- /dev/null +++ b/app/src/main/res/layout/activity_welcome.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +