bfzgs 2 年之前
当前提交
e20aa2db88
共有 100 个文件被更改,包括 2935 次插入0 次删除
  1. 15 0
      .gitignore
  2. 3 0
      .idea/.gitignore
  3. 6 0
      .idea/compiler.xml
  4. 18 0
      .idea/gradle.xml
  5. 10 0
      .idea/misc.xml
  6. 6 0
      .idea/vcs.xml
  7. 二进制
      FinalProject04041935.zip
  8. 二进制
      FinalProject04042024.zip
  9. 1 0
      app/.gitignore
  10. 113 0
      app/build.gradle
  11. 11 0
      app/lint-baseline.xml
  12. 21 0
      app/proguard-rules.pro
  13. 24 0
      app/src/androidTest/java/org/brynhild/graduation/ExampleInstrumentedTest.kt
  14. 75 0
      app/src/main/AndroidManifest.xml
  15. 67 0
      app/src/main/java/org/brynhild/graduation/MainActivity.kt
  16. 27 0
      app/src/main/java/org/brynhild/graduation/activity/ActivityCollector.kt
  17. 271 0
      app/src/main/java/org/brynhild/graduation/activity/common/AccountInfoActivity.kt
  18. 92 0
      app/src/main/java/org/brynhild/graduation/activity/common/BaseActivity.kt
  19. 93 0
      app/src/main/java/org/brynhild/graduation/activity/common/ForgetPasswordActivity.kt
  20. 156 0
      app/src/main/java/org/brynhild/graduation/activity/common/LoginActivity.kt
  21. 191 0
      app/src/main/java/org/brynhild/graduation/activity/common/MainPageActivity.kt
  22. 39 0
      app/src/main/java/org/brynhild/graduation/activity/common/PolicyActivity.kt
  23. 162 0
      app/src/main/java/org/brynhild/graduation/activity/common/RegisterActivity.kt
  24. 12 0
      app/src/main/java/org/brynhild/graduation/activity/common/view/factory/LoginViewModelFactory.kt
  25. 26 0
      app/src/main/java/org/brynhild/graduation/activity/common/view/model/ForgetPasswordViewModel.kt
  26. 34 0
      app/src/main/java/org/brynhild/graduation/activity/common/view/model/LoginViewModel.kt
  27. 62 0
      app/src/main/java/org/brynhild/graduation/activity/common/view/model/RegisterViewModel.kt
  28. 5 0
      app/src/main/java/org/brynhild/graduation/common/callback/DefaultCallback.java
  29. 85 0
      app/src/main/java/org/brynhild/graduation/common/config/LoginConfiguration.kt
  30. 25 0
      app/src/main/java/org/brynhild/graduation/common/config/MqttServerConfig.kt
  31. 21 0
      app/src/main/java/org/brynhild/graduation/common/config/MyApplication.kt
  32. 18 0
      app/src/main/java/org/brynhild/graduation/common/constant/AccountConstant.java
  33. 7 0
      app/src/main/java/org/brynhild/graduation/common/constant/BroadcastConstant.kt
  34. 8 0
      app/src/main/java/org/brynhild/graduation/common/constant/DeviceTypeConstant.kt
  35. 44 0
      app/src/main/java/org/brynhild/graduation/common/constant/FileStorageConstant.kt
  36. 51 0
      app/src/main/java/org/brynhild/graduation/common/entity/BaseEntity.java
  37. 3 0
      app/src/main/java/org/brynhild/graduation/common/transfer/Result.kt
  38. 10 0
      app/src/main/java/org/brynhild/graduation/common/transfer/dto/AccountBasicInfo.kt
  39. 3 0
      app/src/main/java/org/brynhild/graduation/common/transfer/dto/FileInfo.kt
  40. 5 0
      app/src/main/java/org/brynhild/graduation/common/transfer/dto/UserInfo.kt
  41. 102 0
      app/src/main/java/org/brynhild/graduation/common/transfer/handler/ResponseHandler.kt
  42. 19 0
      app/src/main/java/org/brynhild/graduation/common/transfer/utils/ServiceCreator.kt
  43. 3 0
      app/src/main/java/org/brynhild/graduation/common/transfer/vo/AccountLoginInfo.kt
  44. 5 0
      app/src/main/java/org/brynhild/graduation/common/transfer/vo/AccountName.kt
  45. 10 0
      app/src/main/java/org/brynhild/graduation/common/transfer/vo/AccountRegisterInfo.kt
  46. 5 0
      app/src/main/java/org/brynhild/graduation/common/transfer/vo/AccountToken.kt
  47. 8 0
      app/src/main/java/org/brynhild/graduation/common/transfer/vo/ModifyAccountInfo.kt
  48. 4 0
      app/src/main/java/org/brynhild/graduation/common/transfer/vo/ResendEmail.kt
  49. 7 0
      app/src/main/java/org/brynhild/graduation/common/transfer/vo/ResetPasswordByCode.kt
  50. 9 0
      app/src/main/java/org/brynhild/graduation/common/transfer/vo/UserBindDeviceInfo.kt
  51. 66 0
      app/src/main/java/org/brynhild/graduation/common/utils/EChartOptionUtil.java
  52. 29 0
      app/src/main/java/org/brynhild/graduation/common/utils/FileUploadUtil.java
  53. 29 0
      app/src/main/java/org/brynhild/graduation/common/utils/JsonUtils.kt
  54. 162 0
      app/src/main/java/org/brynhild/graduation/common/utils/Uri2PathUtil.java
  55. 104 0
      app/src/main/java/org/brynhild/graduation/common/view/ConfirmPopupWindow.kt
  56. 63 0
      app/src/main/java/org/brynhild/graduation/common/view/EChartView.java
  57. 12 0
      app/src/main/java/org/brynhild/graduation/common/view/KVInfo.kt
  58. 123 0
      app/src/main/java/org/brynhild/graduation/network/entiity/User.java
  59. 45 0
      app/src/main/java/org/brynhild/graduation/network/utils/OkHttpGlideModule.kt
  60. 61 0
      app/src/main/java/org/brynhild/graduation/network/utils/UnsafeOkHttpClient.java
  61. 38 0
      app/src/main/java/org/brynhild/graduation/service/http/AccountService.kt
  62. 45 0
      app/src/main/java/org/brynhild/graduation/service/mqtt/MqttListenService.kt
  63. 52 0
      app/src/main/java/org/brynhild/graduation/service/mqtt/PushCallback.java
  64. 66 0
      app/src/main/java/org/brynhild/graduation/service/mqtt/SubscribeClient.java
  65. 9 0
      app/src/main/res/anim/pop_in.xml
  66. 9 0
      app/src/main/res/anim/pop_out.xml
  67. 二进制
      app/src/main/res/drawable-v24/abc_btn_check_to_on_mtrl_000.png
  68. 二进制
      app/src/main/res/drawable-v24/abc_btn_check_to_on_mtrl_015.png
  69. 二进制
      app/src/main/res/drawable-v24/app_icon.png
  70. 二进制
      app/src/main/res/drawable-v24/bind.png
  71. 二进制
      app/src/main/res/drawable-v24/break0.png
  72. 二进制
      app/src/main/res/drawable-v24/break1.png
  73. 二进制
      app/src/main/res/drawable-v24/down.png
  74. 二进制
      app/src/main/res/drawable-v24/down2.png
  75. 二进制
      app/src/main/res/drawable-v24/exit.png
  76. 二进制
      app/src/main/res/drawable-v24/ic_launcher.png
  77. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  78. 二进制
      app/src/main/res/drawable-v24/left.png
  79. 二进制
      app/src/main/res/drawable-v24/left2.png
  80. 二进制
      app/src/main/res/drawable-v24/right.png
  81. 二进制
      app/src/main/res/drawable-v24/right2.png
  82. 二进制
      app/src/main/res/drawable-v24/ucrop_ic_cross.png
  83. 二进制
      app/src/main/res/drawable-v24/ucrop_ic_next.png
  84. 二进制
      app/src/main/res/drawable-v24/udp.png
  85. 二进制
      app/src/main/res/drawable-v24/udp2.png
  86. 二进制
      app/src/main/res/drawable-v24/up.png
  87. 二进制
      app/src/main/res/drawable-v24/up2.png
  88. 二进制
      app/src/main/res/drawable-xxhdpi/add.png
  89. 二进制
      app/src/main/res/drawable-xxhdpi/close.png
  90. 二进制
      app/src/main/res/drawable-xxhdpi/ic_backup.png
  91. 二进制
      app/src/main/res/drawable-xxhdpi/ic_comment.png
  92. 二进制
      app/src/main/res/drawable-xxhdpi/ic_delete.png
  93. 二进制
      app/src/main/res/drawable-xxhdpi/ic_done.png
  94. 二进制
      app/src/main/res/drawable-xxhdpi/ic_menu.png
  95. 二进制
      app/src/main/res/drawable-xxhdpi/ic_settings.png
  96. 二进制
      app/src/main/res/drawable-xxhdpi/imageres_110.png
  97. 二进制
      app/src/main/res/drawable-xxhdpi/nav_call.png
  98. 二进制
      app/src/main/res/drawable-xxhdpi/nav_computer.png
  99. 二进制
      app/src/main/res/drawable-xxhdpi/nav_favorite.png
  100. 二进制
      app/src/main/res/drawable-xxhdpi/nav_friends.png

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+*.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

+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 6 - 0
.idea/compiler.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="11" />
+  </component>
+</project>

+ 18 - 0
.idea/gradle.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 10 - 0
.idea/misc.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

二进制
FinalProject04041935.zip


二进制
FinalProject04042024.zip


+ 1 - 0
app/.gitignore

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

+ 113 - 0
app/build.gradle

@@ -0,0 +1,113 @@
+plugins {
+    id 'com.android.application'
+    id 'org.jetbrains.kotlin.android'
+//    id 'kotlin-android'
+//    id 'kotlin-android-extensions'
+    id 'kotlin-kapt'
+}
+
+android {
+    namespace 'org.brynhild.graduation'
+    compileSdk 32
+
+    defaultConfig {
+        applicationId "org.brynhild.graduation"
+        minSdk 21
+        targetSdk 28
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+        ndk {
+            abiFilters "arm64-v8a","armeabi-v7a"
+        }
+    }
+
+    android {
+        lint {
+            baseline = file("lint-baseline.xml")
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_11
+        targetCompatibility JavaVersion.VERSION_11
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    buildFeatures {
+        viewBinding true
+    }
+
+}
+
+dependencies {
+    def room_version = "2.3.0"
+    implementation "androidx.room:room-runtime:$room_version"
+    annotationProcessor "androidx.room:room-compiler:$room_version"
+    implementation "androidx.room:room-ktx:$room_version"
+//    kapt "androidx.room:room-compiler:$room_version"
+    implementation "androidx.room:room-rxjava2:$room_version"
+    implementation "androidx.room:room-rxjava3:$room_version"
+    implementation "androidx.room:room-guava:$room_version"
+    testImplementation "androidx.room:room-testing:$room_version"
+
+
+    implementation("com.squareup.retrofit2:retrofit:2.6.1")
+    implementation("com.squareup.retrofit2:converter-gson:2.6.1")
+
+    implementation 'com.github.abel533:ECharts:3.0.0.2'
+    implementation 'com.github.huangyanbin:SmartTable:2.2.0'
+
+    implementation 'commons-httpclient:commons-httpclient:3.1'
+    implementation 'org.apache.httpcomponents:httpcore:4.4.14'
+    implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
+
+
+    implementation 'de.hdodenhof:circleimageview:3.1.0'
+
+    implementation 'com.github.bumptech.glide:glide:4.12.0'
+    kapt 'com.github.bumptech.glide:compiler:4.9.0'
+
+    implementation("com.github.bumptech.glide:okhttp3-integration:4.0.0" ) {
+        exclude group: "com.android.support"
+    }
+
+
+    implementation 'androidx.core:core-ktx:1.7.0'
+    implementation 'androidx.appcompat:appcompat:1.4.1'
+    implementation 'com.google.android.material:material:1.5.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
+    implementation 'androidx.annotation:annotation:1.3.0'
+    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
+    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+
+    //noinspection GradleCompatible
+//    implementation 'com.android.support:appcompat-v7:28.0.0'
+    implementation 'com.android.support.constraint:constraint-layout:2.0.4'
+    testImplementation 'junit:junit:4.13.2'
+    //noinspection GradleCompatible
+//    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+
+    implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
+    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
+
+    implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
+    implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.1'
+
+    implementation 'org.projectlombok:lombok:1.18.8'
+    implementation 'org.projectlombok:lombok:1.18.8'
+}

+ 11 - 0
app/lint-baseline.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.2.1" type="baseline" client="gradle" dependencies="false" name="AGP (7.2.1)" variant="fatal" version="7.2.1">
+
+    <issue
+        id="DuplicatePlatformClasses"
+        message="`commons-logging` defines classes that conflict with classes now provided by Android. Solutions include finding newer versions or alternative libraries that don&apos;t have the same problem (for example, for `httpclient` use `HttpUrlConnection` or `okhttp` instead), or repackaging the library using something like `jarjar`.">
+        <location
+            file="build.gradle"/>
+    </issue>
+
+</issues>

+ 21 - 0
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

+ 24 - 0
app/src/androidTest/java/org/brynhild/graduation/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package org.brynhild.graduation
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.runner.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("org.brynhild.graduation", appContext.packageName)
+    }
+}

+ 75 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <!-- 允许应用程序多播状态 -->
+    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /> <!-- 允许应用程序改变网络状态 -->
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <!-- 允许应用程序改变WIFI连接状态 -->
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <!-- 允许应用程序访问有关的网络信息 -->
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 允许应用程序访问WIFI网卡的网络信息 -->
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 允许应用程序完全使用网络 -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
+
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@drawable/cat"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:networkSecurityConfig="@xml/network"
+        android:name=".common.config.MyApplication"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme.NoActionBar"
+        tools:targetApi="31">
+        <service
+            android:name=".service.mqtt.MqttListenService"
+            android:enabled="true"
+            android:exported="true" />
+
+        <activity
+            android:launchMode="singleTask"
+            android:name=".activity.common.AccountInfoActivity"
+            android:exported="false" />
+
+
+        <activity
+            android:name=".activity.common.MainPageActivity"
+            android:launchMode="singleTask"
+            android:exported="false" />
+        <activity
+            android:name=".activity.common.ForgetPasswordActivity"
+            android:launchMode="singleTask"
+            android:exported="false" />
+        <activity
+            android:name=".activity.common.PolicyActivity"
+            android:launchMode="singleTask"
+            android:exported="false" />
+        <activity
+            android:name=".activity.common.RegisterActivity"
+            android:launchMode="singleTask"
+            android:exported="true" />
+        <activity
+            android:name=".activity.common.LoginActivity"
+            android:exported="false"
+            android:launchMode="singleTask" />
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.app.lib_name"
+                android:value="" />
+        </activity>
+    </application>
+
+</manifest>

+ 67 - 0
app/src/main/java/org/brynhild/graduation/MainActivity.kt

@@ -0,0 +1,67 @@
+package org.brynhild.graduation
+
+import android.Manifest
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import com.example.devicemanager.view.ConfirmPopupWindow
+import org.brynhild.graduation.activity.common.BaseActivity
+import org.brynhild.graduation.activity.common.LoginActivity
+import org.brynhild.graduation.common.config.LoginConfiguration
+import org.brynhild.graduation.common.config.MyApplication
+
+class MainActivity : BaseActivity() {
+    private var init = false
+
+    fun getPermission() {
+        if (Build.VERSION.SDK_INT >= 23) {
+            val REQUEST_CODE_CONTACT = 101
+            val permissions = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+            //验证是否许可权限
+            for (str in permissions) {
+                if (this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
+                    //申请权限
+                    this.requestPermissions(permissions, REQUEST_CODE_CONTACT)
+                }
+            }
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+        supportActionBar?.hide()
+        loadLoginInfo()
+        getPermission()
+        tryLogin()
+    }
+
+    private fun loadLoginInfo() {
+        val sp = getSharedPreferences("account", Context.MODE_PRIVATE)
+        val accountName = sp.getString("accountName", "")
+        val accountPassword = sp.getString("accountPassword", "")
+        LoginConfiguration.login_username = accountName
+        LoginConfiguration.login_password = accountPassword
+    }
+
+    private fun tryLogin() {
+        if (LoginConfiguration.isValid()) {
+            LoginConfiguration.login()
+        } else {
+            val intent = Intent(MyApplication.context, LoginActivity::class.java)
+            startActivity(intent)
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        Log.d("MainActivity", "Resume...")
+        if (init) {
+            finish()
+        }
+        init = true
+    }
+}

+ 27 - 0
app/src/main/java/org/brynhild/graduation/activity/ActivityCollector.kt

@@ -0,0 +1,27 @@
+package org.brynhild.graduation.activity
+
+import android.app.Activity
+
+object ActivityCollector {
+    private val activities = ArrayList<Activity>()
+
+    var allFinished = false
+
+    fun addActivity(activity: Activity) {
+        activities.add(activity)
+    }
+
+    fun removeActivity(activity: Activity) {
+        activities.remove(activity)
+    }
+
+    fun finishAll() {
+        for (activity in activities) {
+            if (!activity.isFinishing) {
+                activity.finish()
+            }
+        }
+        activities.clear()
+
+    }
+}

+ 271 - 0
app/src/main/java/org/brynhild/graduation/activity/common/AccountInfoActivity.kt

@@ -0,0 +1,271 @@
+package org.brynhild.graduation.activity.common
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.Message
+import android.util.Log
+import android.view.LayoutInflater
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import com.bumptech.glide.Glide
+import com.example.devicemanager.view.ConfirmPopupWindow
+import com.google.gson.Gson
+import org.brynhild.graduation.R
+import org.brynhild.graduation.activity.ActivityCollector
+import org.brynhild.graduation.common.config.LoginConfiguration
+import org.brynhild.graduation.common.config.LoginConfiguration.saveLoginInfo
+import org.brynhild.graduation.common.config.MyApplication
+import org.brynhild.graduation.common.constant.BroadcastConstant
+import org.brynhild.graduation.common.constant.FileStorageConstant
+import org.brynhild.graduation.common.transfer.Result
+import org.brynhild.graduation.common.transfer.dto.AccountBasicInfo
+import org.brynhild.graduation.common.transfer.dto.FileInfo
+import org.brynhild.graduation.common.transfer.handler.ResponseHandler
+import org.brynhild.graduation.common.transfer.utils.ServiceCreator
+import org.brynhild.graduation.common.transfer.vo.AccountToken
+import org.brynhild.graduation.common.transfer.vo.ModifyAccountInfo
+import org.brynhild.graduation.common.utils.FileUploadUtil
+import org.brynhild.graduation.common.utils.JsonUtils
+import org.brynhild.graduation.common.utils.Uri2PathUtil
+import org.brynhild.graduation.databinding.ActivityAccountInfoBinding
+import org.brynhild.graduation.databinding.ModifyAccountBasicInfoDialogBinding
+import org.brynhild.graduation.databinding.ModifyAccountPasswordInfoDialogBinding
+import org.brynhild.graduation.service.http.AccountService
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import java.io.File
+import java.io.IOException
+
+class AccountInfoActivity : BaseActivity() {
+    private val refreshBasicInfo = 1
+    private val updateAccountAvatar = 2
+    private lateinit var accountBasicInfo: AccountBasicInfo
+    private lateinit var activityAccountInfoBinding: ActivityAccountInfoBinding
+    private lateinit var modifyAccountBasicInfoDialogBinding: ModifyAccountBasicInfoDialogBinding
+    private lateinit var modifyAccountPasswordInfoDialogBinding: ModifyAccountPasswordInfoDialogBinding
+
+    val handler: Handler = Handler {
+        when (it.what) {
+            refreshBasicInfo -> {
+                activityAccountInfoBinding.email.text=LoginConfiguration.userInfo!!.user.email
+            }
+            updateAccountAvatar -> {
+                val path = it.obj as String
+                Glide.with(this).load(path).into(activityAccountInfoBinding.iconImage)
+                val service = ServiceCreator.create(AccountService::class.java)
+                val data = ModifyAccountInfo(null,null,path,null)
+                service.modifyAccountInfo(LoginConfiguration.userInfo!!.token,data).enqueue(object : Callback<Result> {
+                    override fun onResponse(call: Call<Result>, response: Response<Result>) {
+                        ResponseHandler.handle(response, { ret->
+                            Toast.makeText(
+                                MyApplication.context,
+                                ret.message,
+                                Toast.LENGTH_SHORT
+                            ).show()
+                            LoginConfiguration.userInfo!!.user.avatar=path
+                        }, {ret->
+                            Toast.makeText(
+                                MyApplication.context,
+                                ret.message,
+                                Toast.LENGTH_SHORT
+                            ).show()
+                        })
+                    }
+                    override fun onFailure(call: Call<Result>, t: Throwable) {
+                        Toast.makeText(MyApplication.context, "请求失败", Toast.LENGTH_SHORT).show()
+                    }
+                })
+            }
+        }
+        false
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        activityAccountInfoBinding=ActivityAccountInfoBinding.inflate(LayoutInflater.from(this))
+        setContentView(activityAccountInfoBinding.root)
+        activityAccountInfoBinding.mainTitle.text = "账户信息"
+        val user = LoginConfiguration.userInfo!!.user
+        activityAccountInfoBinding.username.text=user.username
+        activityAccountInfoBinding.email.text=user.email
+        activityAccountInfoBinding.lastLogin.text=user.lastLogin
+        activityAccountInfoBinding.name.text=user.name
+        activityAccountInfoBinding.sex.text=user.sex
+
+        Glide.with(this@AccountInfoActivity)
+            .load(user.avatar ?: R.drawable.nav_icon_default)
+            .into(activityAccountInfoBinding.iconImage)
+
+
+
+
+        bindObserver()
+        bindListener()
+
+    }
+
+    private fun bindObserver() {
+
+    }
+
+    private fun bindListener() {
+        activityAccountInfoBinding.backButton.setOnClickListener {
+            finish()
+        }
+
+        //有问题
+        activityAccountInfoBinding.modifyBtn.setOnClickListener {
+            modifyAccountBasicInfoDialogBinding =
+                ModifyAccountBasicInfoDialogBinding.inflate(LayoutInflater.from(this))
+            val user = LoginConfiguration.userInfo!!.user
+            modifyAccountBasicInfoDialogBinding.email.setText(user.email)
+            modifyAccountBasicInfoDialogBinding.tel.setText(user.tel)
+            AlertDialog.Builder(this)
+                .setTitle("修改联系方式")
+                .setView(modifyAccountBasicInfoDialogBinding.root)
+                .setPositiveButton("提交修改") { _, _ ->
+                    val service = ServiceCreator.create(AccountService::class.java)
+                    val data=ModifyAccountInfo(null,modifyAccountBasicInfoDialogBinding.email.text.toString(),
+                    null,modifyAccountBasicInfoDialogBinding.tel.text.toString())
+
+                    service.modifyAccountInfo(LoginConfiguration.userInfo!!.token,data).enqueue(object : Callback<Result> {
+                        override fun onResponse(call: Call<Result>, response: Response<Result>) {
+                            ResponseHandler.handle(response, {
+                                Toast.makeText(
+                                    MyApplication.context,
+                                    it.message,
+                                    Toast.LENGTH_SHORT
+                                ).show()
+                                LoginConfiguration.userInfo!!.user.tel=modifyAccountBasicInfoDialogBinding.tel.text.toString()
+                                LoginConfiguration.userInfo!!.user.email=modifyAccountBasicInfoDialogBinding.email.text.toString()
+                                handler.sendEmptyMessage(refreshBasicInfo)
+                            }, {
+                                Toast.makeText(
+                                    MyApplication.context,
+                                    it.message,
+                                    Toast.LENGTH_SHORT
+                                ).show()
+                            })
+                        }
+
+                        override fun onFailure(call: Call<Result>, t: Throwable) {
+                            Toast.makeText(MyApplication.context, "请求失败", Toast.LENGTH_SHORT).show()
+                        }
+                    })
+                }
+                .setNegativeButton("取消修改") { _, _ ->
+
+                }
+                .show()
+        }
+
+
+        activityAccountInfoBinding.modifyPassword.setOnClickListener {
+            modifyAccountPasswordInfoDialogBinding =
+                ModifyAccountPasswordInfoDialogBinding.inflate(LayoutInflater.from(this))
+            AlertDialog.Builder(this)
+                .setTitle("修改密码")
+                .setView(modifyAccountPasswordInfoDialogBinding.root)
+                .setPositiveButton("提交修改") { _, _ ->
+                    val password =
+                        modifyAccountPasswordInfoDialogBinding.newPassword.text.toString()
+                    val passwordConfirm =
+                        modifyAccountPasswordInfoDialogBinding.newPasswordConfirm.text.toString()
+                    if (password != passwordConfirm) {
+                        Toast.makeText(this, "两次密码不匹配", Toast.LENGTH_SHORT).show()
+                        return@setPositiveButton
+                    }
+                    val service = ServiceCreator.create(AccountService::class.java)
+                    val data = ModifyAccountInfo(
+                        password,null,
+                        null,null
+                    )
+                    service.modifyAccountInfo(LoginConfiguration.userInfo!!.token,data).enqueue(object : Callback<Result> {
+                        override fun onResponse(call: Call<Result>, response: Response<Result>) {
+                            ResponseHandler.handle(response, {
+                                Toast.makeText(
+                                    MyApplication.context,
+                                    it.message,
+                                    Toast.LENGTH_SHORT
+                                ).show()
+                                LoginConfiguration.login_password = ""
+                                LoginConfiguration.userInfo = null
+                                saveLoginInfo()
+
+                                val intent = Intent(BroadcastConstant.OFFLINE)
+                                MyApplication.context.sendBroadcast(intent)
+                            }, {
+
+                            })
+                        }
+
+                        override fun onFailure(call: Call<Result>, t: Throwable) {
+                            Toast.makeText(MyApplication.context, "请求失败", Toast.LENGTH_SHORT).show()
+                        }
+                    })
+                }
+                .setNegativeButton("取消修改") { _, _ ->
+
+                }
+                .show()
+        }
+
+        activityAccountInfoBinding.iconImage.setOnClickListener {
+            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+            intent.addCategory(Intent.CATEGORY_OPENABLE)
+            intent.type = "image/*"
+            startActivityForResult(intent, updateAccountAvatar)
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        when (requestCode) {
+            updateAccountAvatar -> {
+                if (resultCode == Activity.RESULT_OK && data != null) {
+                    val uri = data.data
+                    if (Uri2PathUtil.getRealPathFromUri(this, uri) != null) {
+                        //从uri得到绝对路径,并获取到file文件
+                        val file = File(Uri2PathUtil.getRealPathFromUri(this, uri))
+                        FileUploadUtil.uploadFile(file, object : okhttp3.Callback {
+                            override fun onFailure(call: okhttp3.Call, e: IOException) {
+                                println("failed")
+                            }
+
+                            override fun onResponse(
+                                call: okhttp3.Call,
+                                response: okhttp3.Response
+                            ) {
+                                if (response.isSuccessful) {
+                                    val body = response.body()?.string()
+                                    val gson = Gson()
+                                    val result = gson.fromJson(body, Result::class.java)
+                                    println(result)
+                                    var json = gson.toJson(result.data)
+                                    val fileInfoList = JsonUtils.fromJson2List<FileInfo>(json)
+                                    if(fileInfoList?.size==1) {
+                                        val message = Message()
+                                        message.what = updateAccountAvatar
+                                        json=gson.toJson(fileInfoList[0])
+                                        val info=JsonUtils.fromJson<FileInfo>(json)
+                                        println(info)
+                                        if(info!=null){
+                                            message.obj=info.path
+                                            handler.sendMessage(message)
+                                        }
+                                    }
+                                }
+                            }
+                        })
+                    } else {
+                        Toast.makeText(this, "获取文件失败", Toast.LENGTH_SHORT).show()
+                    }
+                }
+            }
+        }
+    }
+}

+ 92 - 0
app/src/main/java/org/brynhild/graduation/activity/common/BaseActivity.kt

@@ -0,0 +1,92 @@
+package org.brynhild.graduation.activity.common
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Bundle
+import android.os.PersistableBundle
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import org.brynhild.graduation.activity.ActivityCollector
+import org.brynhild.graduation.common.config.MyApplication
+import org.brynhild.graduation.common.constant.BroadcastConstant
+import org.brynhild.graduation.service.mqtt.MqttListenService
+
+
+open class BaseActivity : AppCompatActivity() {
+
+    lateinit var receiver: ForceOfflineReceiver
+    lateinit var loginSuccessReceiver: LoginSuccessReceiver
+    lateinit var loginFailedReceiver: LoginFailedReceiver
+
+    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
+        super.onCreate(savedInstanceState, persistentState)
+        ActivityCollector.addActivity(this)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        val intentFilter = IntentFilter()
+        intentFilter.addAction(BroadcastConstant.OFFLINE)
+        receiver = ForceOfflineReceiver()
+        registerReceiver(receiver, intentFilter)
+
+        val loginSuccessFilter = IntentFilter()
+        loginSuccessFilter.addAction(BroadcastConstant.LOGIN_SUCCESS)
+        loginSuccessReceiver = LoginSuccessReceiver()
+        registerReceiver(loginSuccessReceiver, loginSuccessFilter)
+
+        val loginFailedFilter = IntentFilter()
+        loginFailedFilter.addAction(BroadcastConstant.LOGIN_FAILED)
+        loginFailedReceiver = LoginFailedReceiver()
+        registerReceiver(loginFailedReceiver, loginFailedFilter)
+
+    }
+
+    override fun onPause() {
+        super.onPause()
+        unregisterReceiver(receiver)
+        unregisterReceiver(loginSuccessReceiver)
+        unregisterReceiver(loginFailedReceiver)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        ActivityCollector.removeActivity(this)
+    }
+
+    inner class ForceOfflineReceiver : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            AlertDialog.Builder(context).apply {
+                setTitle("Warning")
+                setMessage("You are forced to be offline.Please try to login again.")
+                setCancelable(false)
+                setPositiveButton("OK") { _, _ ->
+                    finishAffinity()
+                    val serviceIntent = Intent(MyApplication.context, MqttListenService::class.java)
+                    stopService(serviceIntent)
+                    val i = Intent(context, LoginActivity::class.java)
+                    context.startActivity(i)
+                }
+                show()
+            }
+        }
+    }
+
+    inner class LoginSuccessReceiver : BroadcastReceiver() {
+        override fun onReceive(context: Context?, intent: Intent) {
+            val serviceIntent = Intent(MyApplication.context, MqttListenService::class.java)
+            startService(serviceIntent)
+            val intent = Intent(MyApplication.context, MainPageActivity::class.java)
+            startActivity(intent)
+        }
+    }
+
+    inner class LoginFailedReceiver : BroadcastReceiver() {
+        override fun onReceive(context: Context?, intent: Intent) {
+            val intent = Intent(MyApplication.context, LoginActivity::class.java)
+            startActivity(intent)
+        }
+    }
+}

+ 93 - 0
app/src/main/java/org/brynhild/graduation/activity/common/ForgetPasswordActivity.kt

@@ -0,0 +1,93 @@
+package org.brynhild.graduation.activity.common
+
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.widget.Toast
+import androidx.core.view.isVisible
+import androidx.lifecycle.ViewModelProvider
+import org.brynhild.graduation.activity.common.view.model.ForgetPasswordViewModel
+import org.brynhild.graduation.common.config.MyApplication
+import org.brynhild.graduation.common.transfer.Result
+import org.brynhild.graduation.common.transfer.handler.ResponseHandler
+import org.brynhild.graduation.common.transfer.utils.ServiceCreator
+import org.brynhild.graduation.common.transfer.vo.AccountName
+import org.brynhild.graduation.common.transfer.vo.ResendEmail
+import org.brynhild.graduation.common.transfer.vo.ResetPasswordByCode
+import org.brynhild.graduation.databinding.ActivityForgetPasswordBinding
+import org.brynhild.graduation.service.http.AccountService
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+class ForgetPasswordActivity : BaseActivity() {
+
+    lateinit var viewModel: ForgetPasswordViewModel
+
+    private lateinit var activityForgetPasswordBinding: ActivityForgetPasswordBinding
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        supportActionBar?.hide()
+        activityForgetPasswordBinding =
+            ActivityForgetPasswordBinding.inflate(LayoutInflater.from(this))
+        setContentView(activityForgetPasswordBinding.root)
+        bindObserver()
+        bindOnClick()
+    }
+
+    private fun bindObserver() {
+        viewModel = ViewModelProvider(this)[ForgetPasswordViewModel::class.java]
+
+        activityForgetPasswordBinding.username.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setUsername(activityForgetPasswordBinding.username.text.toString())
+            }
+        }
+        activityForgetPasswordBinding.email.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setEmail(activityForgetPasswordBinding.email.text.toString())
+            }
+        }
+
+
+        viewModel.username.observe(this) { accountName ->
+            activityForgetPasswordBinding.username.setText(accountName)
+        }
+        viewModel.email.observe(this) { code ->
+            activityForgetPasswordBinding.email.setText(code)
+        }
+    }
+
+    private fun bindOnClick() {
+        activityForgetPasswordBinding.resetButton.setOnClickListener {
+            val service = ServiceCreator.create(AccountService::class.java)
+            val data = ResendEmail(
+                activityForgetPasswordBinding.username.text.toString(),
+                activityForgetPasswordBinding.email.text.toString(), 2
+            )
+            service.resendEmail(data).enqueue(object : Callback<Result> {
+                override fun onResponse(call: Call<Result>, response: Response<Result>) {
+                    Log.d("Forget", response.raw().toString())
+                    ResponseHandler.handle(response, {
+                        Toast.makeText(
+                            this@ForgetPasswordActivity,
+                            "${it.message}",
+                            Toast.LENGTH_SHORT
+                        ).show()
+                    }, {
+                        Toast.makeText(
+                            this@ForgetPasswordActivity,
+                            "${it.message}",
+                            Toast.LENGTH_SHORT
+                        ).show()
+                    })
+                }
+
+                override fun onFailure(call: Call<Result>, t: Throwable) {
+                    Toast.makeText(MyApplication.context, "网络错误", Toast.LENGTH_SHORT).show()
+                }
+            })
+        }
+    }
+}

+ 156 - 0
app/src/main/java/org/brynhild/graduation/activity/common/LoginActivity.kt

@@ -0,0 +1,156 @@
+package org.brynhild.graduation.activity.common
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.widget.Toast
+import androidx.lifecycle.ViewModelProvider
+import com.example.devicemanager.view.ConfirmPopupWindow
+import com.google.gson.Gson
+import org.brynhild.graduation.activity.common.view.factory.LoginViewModelFactory
+import org.brynhild.graduation.activity.common.view.model.LoginViewModel
+import org.brynhild.graduation.common.config.LoginConfiguration
+import org.brynhild.graduation.common.config.MyApplication
+import org.brynhild.graduation.common.constant.BroadcastConstant
+import org.brynhild.graduation.common.transfer.Result
+import org.brynhild.graduation.common.transfer.dto.UserInfo
+import org.brynhild.graduation.common.transfer.handler.ResponseHandler
+import org.brynhild.graduation.common.transfer.utils.ServiceCreator
+import org.brynhild.graduation.common.transfer.vo.AccountLoginInfo
+import org.brynhild.graduation.common.utils.JsonUtils
+import org.brynhild.graduation.databinding.ActivityLoginBinding
+import org.brynhild.graduation.service.http.AccountService
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+class LoginActivity : BaseActivity() {
+
+    lateinit var viewModel: LoginViewModel
+    lateinit var sp: SharedPreferences
+    private var lastBack = 0L
+    private lateinit var activityLoginBinding: ActivityLoginBinding
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        supportActionBar?.hide()
+        activityLoginBinding = ActivityLoginBinding.inflate(LayoutInflater.from(this))
+        setContentView(activityLoginBinding.root)
+
+        sp = getSharedPreferences("account", Context.MODE_PRIVATE)
+        var accountName = sp.getString("accountName", "")
+        if (accountName == null) {
+            accountName = ""
+        }
+        viewModel = ViewModelProvider(
+            this,
+            LoginViewModelFactory(accountName, "")
+        )[LoginViewModel::class.java]
+
+        activityLoginBinding.loginButton.setOnClickListener {
+            val accountService = ServiceCreator.create(AccountService::class.java)
+            val loginInfo =
+                AccountLoginInfo(
+                    activityLoginBinding.accountName.text.toString(),
+                    activityLoginBinding.accountPassword.text.toString()
+                )
+            accountService.login(loginInfo).enqueue(object : Callback<Result> {
+                override fun onResponse(call: Call<Result>, response: Response<Result>) {
+                    ResponseHandler.handle(response, { result ->
+                        Log.d(
+                            "LoginActivity",
+                            "flag is ${result.flag},message is ${result.message},token is ${result.data}"
+                        )
+                        val json = Gson().toJson(result.data)
+
+                        LoginConfiguration.userInfo = JsonUtils.fromJson<UserInfo>(json)
+                        LoginConfiguration.login_username = loginInfo.username
+                        LoginConfiguration.login_password = loginInfo.password
+                        saveLoginInfo()
+                        val b = Intent(BroadcastConstant.LOGIN_SUCCESS)
+                        MyApplication.context.sendBroadcast(b)
+                        finish()
+                    }, { result ->
+                        Log.d(
+                            "LoginActivity",
+                            "flag is ${result.flag},message is ${result.message}"
+                        )
+                        Toast.makeText(this@LoginActivity,result.message,Toast.LENGTH_SHORT).show()
+                    })
+                }
+
+                override fun onFailure(call: Call<Result>, t: Throwable) {
+                    t.printStackTrace()
+                }
+            })
+        }
+
+        activityLoginBinding.registerButton.setOnClickListener {
+            val intent = Intent(this, RegisterActivity::class.java)
+            startActivity(intent)
+        }
+
+        activityLoginBinding.forgetPasswordButton.setOnClickListener {
+            val intent = Intent(this, ForgetPasswordActivity::class.java)
+            startActivity(intent)
+        }
+
+        activityLoginBinding.accountName.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setAccountName(activityLoginBinding.accountName.text.toString())
+            }
+        }
+
+        viewModel.accountName.observe(this) { accountName ->
+            activityLoginBinding.accountName.setText(accountName)
+        }
+
+        activityLoginBinding.accountPassword.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setAccountPassword(activityLoginBinding.accountPassword.text.toString())
+            }
+        }
+
+        viewModel.accountPassword.observe(this) { accountPassword ->
+            activityLoginBinding.accountPassword.setText(accountPassword)
+        }
+
+        activityLoginBinding.readUserPolicy.setOnClickListener {
+            val intent = Intent(this, PolicyActivity::class.java).apply {
+                putExtra("type", PolicyActivity.USER_POLICY)
+            }
+            startActivity(intent)
+        }
+
+        activityLoginBinding.readPrivacyPolicy.setOnClickListener {
+            val intent = Intent(this, PolicyActivity::class.java).apply {
+                putExtra("type", PolicyActivity.PRIVACY_POLICY)
+            }
+            startActivity(intent)
+        }
+    }
+
+    private fun saveLoginInfo() {
+        val accountName = LoginConfiguration.login_username
+        val accountPassword = LoginConfiguration.login_password
+        with(sp.edit()) {
+            putString("accountName", accountName)
+            putString("accountPassword", accountPassword)
+            apply()
+        }
+        Log.d("LoginActivity", "save info:name:$accountName,password:$accountPassword")
+    }
+
+
+    override fun onBackPressed() {
+        if (lastBack == 0L || System.currentTimeMillis() - lastBack > 2000) {
+            Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show()
+            lastBack = System.currentTimeMillis()
+        } else {
+            finish()
+        }
+    }
+}

+ 191 - 0
app/src/main/java/org/brynhild/graduation/activity/common/MainPageActivity.kt

@@ -0,0 +1,191 @@
+package org.brynhild.graduation.activity.common
+
+
+import android.Manifest
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuItem
+import android.widget.RelativeLayout
+import android.widget.Toast
+import androidx.core.view.GravityCompat
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.GridLayoutManager
+import com.bumptech.glide.Glide
+import com.google.gson.Gson
+import org.brynhild.graduation.R
+import org.brynhild.graduation.common.config.LoginConfiguration
+import org.brynhild.graduation.common.config.MyApplication
+import org.brynhild.graduation.common.constant.FileStorageConstant
+import org.brynhild.graduation.common.transfer.Result
+import org.brynhild.graduation.common.transfer.dto.AccountBasicInfo
+import org.brynhild.graduation.common.transfer.handler.ResponseHandler
+import org.brynhild.graduation.common.transfer.utils.ServiceCreator
+import org.brynhild.graduation.common.transfer.vo.AccountToken
+import org.brynhild.graduation.common.utils.JsonUtils
+import org.brynhild.graduation.databinding.ActivityMainPageBinding
+import org.brynhild.graduation.databinding.NavHeaderBinding
+import org.brynhild.graduation.network.entiity.User
+import org.brynhild.graduation.service.http.AccountService
+import org.brynhild.graduation.service.mqtt.MqttListenService
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+class MainPageActivity : BaseActivity() {
+
+    //
+    private var currentPage = 0
+
+    private var lastBack = 0L
+    private lateinit var activityMainPageBinding: ActivityMainPageBinding
+    private lateinit var navHeaderBinding: NavHeaderBinding
+
+    private fun getPermission() {
+        if (Build.VERSION.SDK_INT >= 23) {
+            val REQUEST_CODE_CONTACT = 101
+            val permissions = arrayOf<String>(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+            //验证是否许可权限
+            for (str in permissions) {
+                if (this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
+                    //申请权限
+                    this.requestPermissions(permissions, REQUEST_CODE_CONTACT)
+                }
+            }
+            FileStorageConstant.initStorage()
+        }
+    }
+
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        activityMainPageBinding = ActivityMainPageBinding.inflate(LayoutInflater.from(this))
+        setContentView(activityMainPageBinding.root)
+        setSupportActionBar(activityMainPageBinding.toolBar)
+        supportActionBar?.let {
+            it.setDisplayHomeAsUpEnabled(true)
+            it.setHomeAsUpIndicator(R.drawable.ic_menu)
+            it.title = "Favorite"
+        }
+
+        getPermission()
+
+        val layoutManager = GridLayoutManager(this, 2)
+        activityMainPageBinding.recyclerView.layoutManager = layoutManager
+        activityMainPageBinding.navView.setCheckedItem(R.id.navFavorite)
+        currentPage = R.id.navFavorite
+        activityMainPageBinding.navView.setNavigationItemSelectedListener { it ->
+            when (it.itemId) {
+                R.id.navExit -> {
+                    LoginConfiguration.login_password = ""
+                    saveLoginInfo()
+                    val i = Intent(MyApplication.context, LoginActivity::class.java)
+                    startActivity(i)
+                    finish()
+                }
+            }
+            activityMainPageBinding.drawerLayout.closeDrawers()
+            true
+        }
+        activityMainPageBinding.swipeRefresh.setOnRefreshListener {
+            Thread {
+                when (currentPage) {
+
+                    R.id.navExit -> {
+                        LoginConfiguration.login_password = ""
+                        saveLoginInfo()
+                        val serviceIntent =
+                            Intent(MyApplication.context, MqttListenService::class.java)
+                        stopService(serviceIntent)
+                        val i = Intent(MyApplication.context, LoginActivity::class.java)
+                        startActivity(i)
+                        finish()
+                    }
+                }
+
+            }.start()
+        }
+        val headerView = activityMainPageBinding.navView.inflateHeaderView(R.layout.nav_header)
+        navHeaderBinding = NavHeaderBinding.bind(headerView)
+        navHeaderBinding.navProfile.setOnClickListener{
+            val intent = Intent(this, AccountInfoActivity::class.java)
+            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            this.startActivity(intent)
+        }
+    }
+
+    private fun loadUserInfo() {
+        Thread {
+            val service = ServiceCreator.create(AccountService::class.java)
+            service.accountInfo(LoginConfiguration.userInfo!!.token).enqueue(object : Callback<Result> {
+                override fun onResponse(call: Call<Result>, response: Response<Result>) {
+                    ResponseHandler.handle(response, {
+                        val json= Gson().toJson(it.data)
+                        val user=JsonUtils.fromJson<User>(json)
+                        if (user != null) {
+                            navHeaderBinding.userText.text = user.name
+                            navHeaderBinding.mailText.text = user.email
+                            Glide.with(this@MainPageActivity)
+                                .load(user.avatar ?: R.drawable.nav_icon_default)
+                                .into(navHeaderBinding.iconImage)
+                        }
+                    }, {
+                        Toast.makeText(MyApplication.context, "请求用户基本信息失败", Toast.LENGTH_SHORT)
+                            .show()
+                    })
+                }
+
+                override fun onFailure(call: Call<Result>, t: Throwable) {
+                    Toast.makeText(MyApplication.context, "请求用户基本信息失败", Toast.LENGTH_SHORT).show()
+                }
+            })
+        }.start()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        loadUserInfo()
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+        return true
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        when (item.itemId) {
+            android.R.id.home -> activityMainPageBinding.drawerLayout.openDrawer(GravityCompat.START)
+            R.id.backup -> Toast.makeText(this, "You clicked Backup", Toast.LENGTH_SHORT).show()
+            R.id.delete -> Toast.makeText(this, "You clicked Delete", Toast.LENGTH_SHORT).show()
+            R.id.settings -> Toast.makeText(this, "You clicked Settings", Toast.LENGTH_SHORT).show()
+        }
+        return true
+    }
+
+    override fun onBackPressed() {
+        if (lastBack == 0L || System.currentTimeMillis() - lastBack > 2000) {
+            Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show()
+            lastBack = System.currentTimeMillis()
+        } else {
+            val serviceIntent = Intent(MyApplication.context, MqttListenService::class.java)
+            stopService(serviceIntent)
+            finish()
+        }
+    }
+
+    private fun saveLoginInfo() {
+        val accountName = LoginConfiguration.login_username
+        val accountPassword = LoginConfiguration.login_password
+        val sp = getSharedPreferences("account", Context.MODE_PRIVATE)
+        with(sp.edit()) {
+            putString("accountName", accountName)
+            putString("accountPassword", accountPassword)
+            apply()
+        }
+        Log.d("LoginActivity", "save info:name:$accountName,password:$accountPassword")
+    }
+}

+ 39 - 0
app/src/main/java/org/brynhild/graduation/activity/common/PolicyActivity.kt

@@ -0,0 +1,39 @@
+package org.brynhild.graduation.activity.common
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import org.brynhild.graduation.databinding.ActivityPolicyBinding
+
+class PolicyActivity : BaseActivity() {
+
+    private lateinit var activityPolicyBinding: ActivityPolicyBinding
+
+    companion object {
+        const val USER_POLICY = "用户协议"
+        const val PRIVACY_POLICY = "隐私政策"
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        activityPolicyBinding = ActivityPolicyBinding.inflate(LayoutInflater.from(this))
+        setContentView(activityPolicyBinding.root)
+        setSupportActionBar(activityPolicyBinding.toolBar)
+        supportActionBar?.let {
+            it.title = ""
+        }
+
+        activityPolicyBinding.policyTitle.text = intent.getStringExtra("type")
+
+        activityPolicyBinding.policyContent.text = with(StringBuilder()) {
+            append("这是协议与政策部分,里面还支持\\n与\\t,例如:\n你好\n\t一级标题:\n\t\t二级标题\n")
+            repeat(1000) {
+                append(intent.getStringExtra("type"))
+            }
+            toString()
+        }
+
+        activityPolicyBinding.backButton.setOnClickListener {
+            finish()
+        }
+    }
+}

+ 162 - 0
app/src/main/java/org/brynhild/graduation/activity/common/RegisterActivity.kt

@@ -0,0 +1,162 @@
+package org.brynhild.graduation.activity.common
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import androidx.lifecycle.ViewModelProvider
+import com.example.devicemanager.view.ConfirmPopupWindow
+import org.brynhild.graduation.activity.common.view.model.RegisterViewModel
+import org.brynhild.graduation.common.transfer.Result
+import org.brynhild.graduation.common.transfer.handler.ResponseHandler
+import org.brynhild.graduation.common.transfer.utils.ServiceCreator
+import org.brynhild.graduation.common.transfer.vo.AccountRegisterInfo
+import org.brynhild.graduation.databinding.ActivityRegisterBinding
+import org.brynhild.graduation.service.http.AccountService
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+class RegisterActivity : BaseActivity() {
+
+    lateinit var viewModel: RegisterViewModel
+    private lateinit var activityRegisterBinding: ActivityRegisterBinding
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+//        setContentView(R.layout.activity_register)
+        supportActionBar?.hide()
+        activityRegisterBinding = ActivityRegisterBinding.inflate(LayoutInflater.from(this))
+        setContentView(activityRegisterBinding.root)
+        viewModel = ViewModelProvider(this)[RegisterViewModel::class.java]
+
+        activityRegisterBinding.username.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setAccountName(activityRegisterBinding.username.text.toString())
+            }
+        }
+        viewModel.accountName.observe(this) { accountName ->
+            activityRegisterBinding.username.setText(accountName)
+        }
+
+        activityRegisterBinding.password.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setAccountPassword(activityRegisterBinding.password.text.toString())
+            }
+        }
+        viewModel.accountPassword.observe(this) { accountPassword ->
+            activityRegisterBinding.password.setText(accountPassword)
+        }
+
+        activityRegisterBinding.agreePolicy.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setAgree(activityRegisterBinding.agreePolicy.isChecked)
+            }
+        }
+        viewModel.agree.observe(this) { agree ->
+            activityRegisterBinding.agreePolicy.isChecked = agree
+        }
+
+        activityRegisterBinding.email.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setEmail(activityRegisterBinding.email.text.toString())
+            }
+        }
+        viewModel.email.observe(this) { email ->
+            activityRegisterBinding.email.setText(email)
+        }
+
+        activityRegisterBinding.name.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setName(activityRegisterBinding.name.text.toString())
+            }
+        }
+        viewModel.name.observe(this) { name ->
+            activityRegisterBinding.name.setText(name)
+        }
+
+        activityRegisterBinding.tel.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setTel(activityRegisterBinding.tel.text.toString())
+            }
+        }
+        viewModel.tel.observe(this) { tel ->
+            activityRegisterBinding.tel.setText(tel)
+        }
+
+        activityRegisterBinding.no.setOnFocusChangeListener { _, hasFocus ->
+            if (!hasFocus) {
+                viewModel.setNo(activityRegisterBinding.no.text.toString())
+            }
+        }
+        viewModel.no.observe(this) { no ->
+            activityRegisterBinding.no.setText(no)
+        }
+
+        activityRegisterBinding.readUserPolicy.setOnClickListener {
+            val intent = Intent(this, PolicyActivity::class.java).apply {
+                putExtra("type", PolicyActivity.USER_POLICY)
+            }
+            startActivity(intent)
+        }
+
+        activityRegisterBinding.readPrivacyPolicy.setOnClickListener {
+            val intent = Intent(this, PolicyActivity::class.java).apply {
+                putExtra("type", PolicyActivity.PRIVACY_POLICY)
+            }
+            startActivity(intent)
+        }
+
+        activityRegisterBinding.registerButton.setOnClickListener {
+            if (!activityRegisterBinding.agreePolicy.isChecked) {
+                ConfirmPopupWindow.ConfirmPopupWindowBuilder.init(this@RegisterActivity)
+                    .setContent("请阅读并同意隐私政策与用户协议")
+                    .build().show()
+            }
+
+            val accountService = ServiceCreator.create(AccountService::class.java)
+            val registerInfo = AccountRegisterInfo(
+                activityRegisterBinding.username.text.toString(),
+                activityRegisterBinding.password.text.toString(),
+                activityRegisterBinding.email.text.toString(),
+                activityRegisterBinding.name.text.toString(),
+                activityRegisterBinding.tel.text.toString(),
+                activityRegisterBinding.no.text.toString(),
+            )
+            accountService.accountRegister(registerInfo).enqueue(object : Callback<Result> {
+                override fun onResponse(call: Call<Result>, response: Response<Result>) {
+                    ResponseHandler.handle(response, { result ->
+                        Log.d(
+                            "RegisterActivity",
+                            "flag is ${result.flag},message is ${result.message},data is ${result.data}"
+                        )
+                        ConfirmPopupWindow.ConfirmPopupWindowBuilder.init(this@RegisterActivity)
+                            .setContent("注册成功,请前往邮箱,并点击邮箱中的激活链接完成激活")
+                            .setCancelText("取消")
+                            .setEnsureText("返回登录页")
+                            .setEnsureListener {
+                                val intent =
+                                    Intent(this@RegisterActivity, LoginActivity::class.java)
+                                startActivity(intent)
+                            }
+                            .build().show()
+                    }, { result ->
+                        Log.d(
+                            "RegisterActivity",
+                            "flag is ${result.flag},message is ${result.message}"
+                        )
+                        ConfirmPopupWindow.ConfirmPopupWindowBuilder.init(this@RegisterActivity)
+                            .setContent("注册失败:${result.message}")
+                            .setCancelText("取消")
+                            .setEnsureText("确定")
+                            .build().show()
+                    })
+                }
+
+                override fun onFailure(call: Call<Result>, t: Throwable) {
+                    t.printStackTrace()
+                }
+            })
+        }
+    }
+}

+ 12 - 0
app/src/main/java/org/brynhild/graduation/activity/common/view/factory/LoginViewModelFactory.kt

@@ -0,0 +1,12 @@
+package org.brynhild.graduation.activity.common.view.factory
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import org.brynhild.graduation.activity.common.view.model.LoginViewModel
+
+class LoginViewModelFactory(private val accountName: String, private val accountPassword: String) :
+    ViewModelProvider.Factory {
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        return LoginViewModel(accountName, accountPassword) as T
+    }
+}

+ 26 - 0
app/src/main/java/org/brynhild/graduation/activity/common/view/model/ForgetPasswordViewModel.kt

@@ -0,0 +1,26 @@
+package org.brynhild.graduation.activity.common.view.model
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class ForgetPasswordViewModel : ViewModel() {
+    private val _username = MutableLiveData<String>()
+    private val _email = MutableLiveData<String>()
+
+    val username: LiveData<String>
+        get() = _username
+
+    val email: LiveData<String>
+        get() = _email
+
+    fun setUsername(username: String) {
+        _username.value = username
+    }
+
+    fun setEmail(email: String) {
+        _email.value = email
+    }
+
+
+}

+ 34 - 0
app/src/main/java/org/brynhild/graduation/activity/common/view/model/LoginViewModel.kt

@@ -0,0 +1,34 @@
+package org.brynhild.graduation.activity.common.view.model
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class LoginViewModel(accountName: String, accountPassword: String) : ViewModel() {
+    //internal data
+    private val _accountName = MutableLiveData<String>()
+    private val _accountPassword = MutableLiveData<String>()
+
+    //public data
+    val accountName: LiveData<String>
+        get() = _accountName
+
+    val accountPassword: LiveData<String>
+        get() = _accountPassword
+
+
+    init {
+        _accountName.value = accountName
+        _accountPassword.value = accountPassword
+    }
+
+    fun setAccountName(accountName: String) {
+        this._accountName.value = accountName
+    }
+
+    fun setAccountPassword(accountPassword: String) {
+        this._accountPassword.value = accountPassword
+    }
+
+
+}

+ 62 - 0
app/src/main/java/org/brynhild/graduation/activity/common/view/model/RegisterViewModel.kt

@@ -0,0 +1,62 @@
+package org.brynhild.graduation.activity.common.view.model
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class RegisterViewModel : ViewModel() {
+    private val _accountName = MutableLiveData<String>()
+    private val _accountPassword = MutableLiveData<String>()
+    private val _agree = MutableLiveData<Boolean>()
+    private val _email = MutableLiveData<String>()
+
+    private val _name=MutableLiveData<String>()
+    private val _tel=MutableLiveData<String>()
+    private val _no=MutableLiveData<String>()
+
+    val accountName: LiveData<String>
+        get() = _accountName
+    val accountPassword: LiveData<String>
+        get() = _accountPassword
+
+    val agree: LiveData<Boolean>
+        get() = _agree
+
+    val email: LiveData<String>
+        get() = _email
+
+    val name: LiveData<String>
+        get() = _name
+    val tel: LiveData<String>
+        get() = _tel
+    val no: LiveData<String>
+        get() = _no
+
+    fun setAccountName(accountName: String) {
+        this._accountName.value = accountName
+    }
+
+    fun setAccountPassword(accountPassword: String) {
+        this._accountPassword.value = accountPassword
+    }
+
+    fun setAgree(agree: Boolean) {
+        this._agree.value = agree
+    }
+
+    fun setEmail(email: String) {
+        this._email.value = email
+    }
+
+    fun setName(name: String) {
+        this._name.value = name
+    }
+
+    fun setTel(tel: String) {
+        this._tel.value = tel
+    }
+
+    fun setNo(no: String) {
+        this._no.value = no
+    }
+}

+ 5 - 0
app/src/main/java/org/brynhild/graduation/common/callback/DefaultCallback.java

@@ -0,0 +1,5 @@
+package org.brynhild.graduation.common.callback;
+
+public interface DefaultCallback {
+    void execute();
+}

+ 85 - 0
app/src/main/java/org/brynhild/graduation/common/config/LoginConfiguration.kt

@@ -0,0 +1,85 @@
+package org.brynhild.graduation.common.config
+
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import android.widget.Toast
+import com.google.gson.Gson
+import org.brynhild.graduation.common.constant.BroadcastConstant
+import org.brynhild.graduation.common.transfer.Result
+import org.brynhild.graduation.common.transfer.dto.UserInfo
+import org.brynhild.graduation.common.transfer.handler.ResponseHandler
+import org.brynhild.graduation.common.transfer.utils.ServiceCreator
+import org.brynhild.graduation.common.transfer.vo.AccountLoginInfo
+import org.brynhild.graduation.common.utils.JsonUtils
+import org.brynhild.graduation.service.http.AccountService
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+object LoginConfiguration {
+
+    var login_username: String? = null
+    var login_password: String? = null
+    var userInfo: UserInfo? = null
+
+    fun isValid(): Boolean {
+        return login_username?.trim()?.isNotBlank() ?: false && login_password?.trim()
+            ?.isNotBlank() ?: false
+    }
+
+    fun login() {
+        val accountService = ServiceCreator.create(AccountService::class.java)
+        val loginInfo =
+            login_password?.let { login_username?.let { it1 -> AccountLoginInfo(it1, it) } }
+        if (loginInfo != null) {
+            accountService.login(loginInfo).enqueue(object : Callback<Result> {
+                override fun onResponse(call: Call<Result>, response: Response<Result>) {
+                    ResponseHandler.handle(response, { result ->
+                        Log.d(
+                            "LoginConfiguration",
+                            "flag is ${result.flag},message is ${result.message},token is ${result.data}"
+                        )
+                        val json = Gson().toJson(result.data)
+
+                        userInfo = JsonUtils.fromJson<UserInfo>(json)
+                        val b = Intent(BroadcastConstant.LOGIN_SUCCESS)
+                        MyApplication.context.sendBroadcast(b)
+                    }, { result ->
+                        Log.d(
+                            "LoginConfiguration",
+                            "flag is ${result.flag},message is ${result.message}"
+                        )
+                        Toast.makeText(MyApplication.context,result.message, Toast.LENGTH_SHORT).show()
+                        val b = Intent(BroadcastConstant.LOGIN_FAILED)
+                        MyApplication.context.sendBroadcast(b)
+                    }, {
+                        val b = Intent(BroadcastConstant.LOGIN_FAILED)
+                        MyApplication.context.sendBroadcast(b)
+                    }, {
+                        val b = Intent(BroadcastConstant.LOGIN_FAILED)
+                        MyApplication.context.sendBroadcast(b)
+                    })
+                }
+
+                override fun onFailure(call: Call<Result>, t: Throwable) {
+                    t.printStackTrace()
+                    val b = Intent(BroadcastConstant.LOGIN_FAILED)
+                    MyApplication.context.sendBroadcast(b)
+                }
+            })
+        }
+    }
+
+    fun saveLoginInfo() {
+        val accountName = login_username
+        val accountPassword = login_password
+        val sp = MyApplication.context.getSharedPreferences("account", Context.MODE_PRIVATE)
+        with(sp.edit()) {
+            putString("accountName", accountName)
+            putString("accountPassword", accountPassword)
+            apply()
+        }
+        Log.d("LoginActivity", "save info:name:$accountName,password:$accountPassword")
+    }
+}

+ 25 - 0
app/src/main/java/org/brynhild/graduation/common/config/MqttServerConfig.kt

@@ -0,0 +1,25 @@
+package org.brynhild.graduation.common.config
+
+object MqttServerConfig {
+    fun getListenTopic(): String? {
+        if (LoginConfiguration.login_username != null) {
+            return LoginConfiguration.login_username + "-notifyService"
+        }
+        return null
+    }
+
+    fun getClientId(): String? {
+        if (LoginConfiguration.login_username != null) {
+            return LoginConfiguration.login_username + "-androidClient"
+        }
+        return null
+    }
+
+    const val broker = "tcp://47.94.15.64:1883"
+
+    const val password = "client"
+    const val username = "client"
+    const val qos = 2
+    const val registerTopic = "android"
+
+}

+ 21 - 0
app/src/main/java/org/brynhild/graduation/common/config/MyApplication.kt

@@ -0,0 +1,21 @@
+package org.brynhild.graduation.common.config
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.app.Service
+import android.content.Context
+
+class MyApplication : Application() {
+    companion object {
+        @SuppressLint("StaticFieldLeak")
+        lateinit var context: Context
+
+        @SuppressLint("StaticFieldLeak")
+        lateinit var serviceContext: Service
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+        context = applicationContext
+    }
+}

+ 18 - 0
app/src/main/java/org/brynhild/graduation/common/constant/AccountConstant.java

@@ -0,0 +1,18 @@
+package org.brynhild.graduation.common.constant;
+
+public class AccountConstant {
+    public static final Integer ADMIN = 0;
+    public static final Integer SYSTEM = 1;
+    public static final Integer USER = 2;
+    public static final String DEFAULT_AVATAR = "";
+    public static final String DEFAULT_LOGIN_TIME = "";
+
+    public static final String ACCOUNT_HEADER = "brynhild_account";
+    public static final String TOKEN_ROLE = "roles";
+    public static final String TOKEN_NAME = "name";
+    public static final String TOKEN_ID = "id";
+    public static final String TOKEN_USERNAME = "username";
+
+    private AccountConstant() {
+    }
+}

+ 7 - 0
app/src/main/java/org/brynhild/graduation/common/constant/BroadcastConstant.kt

@@ -0,0 +1,7 @@
+package org.brynhild.graduation.common.constant
+
+object BroadcastConstant {
+    const val OFFLINE = "com.example.devicemanager.FORCE_OFFLINE"
+    const val LOGIN_SUCCESS = "com.example.devicemanager.LOGIN_SUCCESS"
+    const val LOGIN_FAILED = "com.example.devicemanager.LOGIN_FAILED"
+}

+ 8 - 0
app/src/main/java/org/brynhild/graduation/common/constant/DeviceTypeConstant.kt

@@ -0,0 +1,8 @@
+package org.brynhild.graduation.common.constant
+
+object DeviceTypeConstant {
+    const val SENSOR = 1
+    const val CAMERA = 2
+    const val COMPUTER = 3
+    const val POWER = 4
+}

+ 44 - 0
app/src/main/java/org/brynhild/graduation/common/constant/FileStorageConstant.kt

@@ -0,0 +1,44 @@
+package org.brynhild.graduation.common.constant
+
+import android.os.Environment
+import java.io.File
+
+object FileStorageConstant {
+    private val ROOT_PATH =
+        "${Environment.getExternalStorageDirectory().absolutePath}" + File.separator + "FinalProject"
+
+    //TODO 服务器文件上传下载的路径
+    private val SERVER_UPLOAD_PATH = "http://192.168.137.1:8100/file/upload/single"
+    private val SERVER_DOWNLOAD_PATH_ID = "http://192.168.137.1:8100/file/download/id/"
+
+    private val PICTURE_PATH = "$ROOT_PATH${File.separator}Pictures"
+    private val VIDEO_PATH = "$ROOT_PATH${File.separator}Videos"
+
+    fun getNewPicturePath(name: String): String {
+        initStorage()
+        return PICTURE_PATH + File.separator + name
+    }
+
+    fun getNewVideoPath(name: String): String {
+        initStorage()
+        return VIDEO_PATH + File.separator + name
+    }
+
+    fun getServerUploadPath(): String {
+        return SERVER_UPLOAD_PATH
+    }
+
+    fun getServerDownloadPathById(id: Int): String {
+        return SERVER_DOWNLOAD_PATH_ID + id
+    }
+
+    fun initStorage() {
+        val storagePath = listOf(ROOT_PATH, PICTURE_PATH, VIDEO_PATH)
+        for (path in storagePath) {
+            val file = File(path)
+            if (!file.exists()) {
+                file.mkdirs()
+            }
+        }
+    }
+}

+ 51 - 0
app/src/main/java/org/brynhild/graduation/common/entity/BaseEntity.java

@@ -0,0 +1,51 @@
+package org.brynhild.graduation.common.entity;
+
+import lombok.Data;
+
+public class BaseEntity {
+    private Long id;
+    private String deleteAt;
+    private Long version ;
+    private String createAt ;
+    private String updateAt ;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getDeleteAt() {
+        return deleteAt;
+    }
+
+    public void setDeleteAt(String deleteAt) {
+        this.deleteAt = deleteAt;
+    }
+
+    public Long getVersion() {
+        return version;
+    }
+
+    public void setVersion(Long version) {
+        this.version = version;
+    }
+
+    public String getCreateAt() {
+        return createAt;
+    }
+
+    public void setCreateAt(String createAt) {
+        this.createAt = createAt;
+    }
+
+    public String getUpdateAt() {
+        return updateAt;
+    }
+
+    public void setUpdateAt(String updateAt) {
+        this.updateAt = updateAt;
+    }
+}

+ 3 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/Result.kt

@@ -0,0 +1,3 @@
+package org.brynhild.graduation.common.transfer
+
+data class Result(val flag: Boolean, val message: String, val data: Any)

+ 10 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/dto/AccountBasicInfo.kt

@@ -0,0 +1,10 @@
+package org.brynhild.graduation.common.transfer.dto
+
+data class AccountBasicInfo(
+    val username: String,
+    val email: String,
+    val lastLoginTime: String?,
+    var nickname: String?,
+    var sex: String?,
+    val avatar: String?
+)

+ 3 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/dto/FileInfo.kt

@@ -0,0 +1,3 @@
+package org.brynhild.graduation.common.transfer.dto
+
+data class FileInfo(val id:Long,val path:String)

+ 5 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/dto/UserInfo.kt

@@ -0,0 +1,5 @@
+package org.brynhild.graduation.common.transfer.dto
+
+import org.brynhild.graduation.network.entiity.User
+
+data class UserInfo(var user:User, val token:String  )

+ 102 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/handler/ResponseHandler.kt

@@ -0,0 +1,102 @@
+package org.brynhild.graduation.common.transfer.handler
+
+import androidx.appcompat.app.AlertDialog
+import org.brynhild.graduation.common.config.MyApplication
+import org.brynhild.graduation.common.transfer.Result
+import retrofit2.Response
+
+object ResponseHandler {
+    fun handle(
+        response: Response<Result>,
+        returnTrue: (Result) -> Unit,
+        returnFalse: (Result) -> Unit,
+        emptyBody: () -> Unit,
+        failed: () -> Unit
+    ) {
+        if (response.isSuccessful) {
+            val result = response.body()
+            if (result != null) {
+                if (result.flag) {
+                    returnTrue(result)
+                } else {
+                    returnFalse(result)
+                }
+            } else {
+                emptyBody()
+            }
+        } else {
+            failed()
+        }
+    }
+
+    fun handle(
+        response: Response<Result>,
+        returnTrue: (Result) -> Unit,
+        returnFalse: (Result) -> Unit,
+        emptyBody: () -> Unit
+    ) {
+        handle(response, returnTrue, returnFalse, emptyBody) {
+            ignoreWhenFailed()
+        }
+    }
+
+    fun handle(
+        response: Response<Result>,
+        returnTrue: (Result) -> Unit,
+        returnFalse: (Result) -> Unit
+    ) {
+        handle(response, returnTrue, returnFalse) {
+            ignoreWhenEmpty()
+        }
+    }
+
+    fun handle(response: Response<Result>, returnTrue: (Result) -> Unit) {
+        handle(response, returnTrue) {
+            ignoreWhenReturnFalse()
+        }
+    }
+
+    fun handle(response: Response<Result>) {
+        handle(response) {
+            ignoreWhenReturnTrue()
+        }
+    }
+
+    fun showWarningDialogWhenFailed() {
+        AlertDialog.Builder(MyApplication.context).apply {
+            setTitle("发生错误")
+            setMessage("连接服务器超时,请检查网络连接")
+            setCancelable(false)
+            setPositiveButton("OK") { _, _ ->
+            }
+            show()
+        }
+    }
+
+    fun showWarningDialogWhenEmpty() {
+        AlertDialog.Builder(MyApplication.context).apply {
+            setTitle("发生错误")
+            setMessage("服务器未返回数据")
+            setCancelable(false)
+            setPositiveButton("OK") { _, _ ->
+            }
+            show()
+        }
+    }
+
+    fun showWarningDialogWhenFalse() {
+        AlertDialog.Builder(MyApplication.context).apply {
+            setTitle("发生错误")
+            setMessage("操作未完成")
+            setCancelable(false)
+            setPositiveButton("OK") { _, _ ->
+            }
+            show()
+        }
+    }
+
+    fun ignoreWhenFailed() {}
+    fun ignoreWhenEmpty() {}
+    fun ignoreWhenReturnFalse() {}
+    fun ignoreWhenReturnTrue() {}
+}

+ 19 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/utils/ServiceCreator.kt

@@ -0,0 +1,19 @@
+package org.brynhild.graduation.common.transfer.utils
+
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+object ServiceCreator {
+    //TODO HTTP请求需要更改服务器路径
+    private const val BASE_URL = "http://192.168.137.1:8100/"
+
+    //    private const val BASE_URL = "http://192.168.8.102:8085/api/android/"
+    private val retrofit = Retrofit.Builder()
+        .baseUrl(BASE_URL)
+        .addConverterFactory(GsonConverterFactory.create())
+        .build()
+
+    fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)
+
+    inline fun <reified T> create(): T = create(T::class.java)
+}

+ 3 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/vo/AccountLoginInfo.kt

@@ -0,0 +1,3 @@
+package org.brynhild.graduation.common.transfer.vo
+
+data class AccountLoginInfo(val username: String, val password: String)

+ 5 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/vo/AccountName.kt

@@ -0,0 +1,5 @@
+package org.brynhild.graduation.common.transfer.vo
+
+data class AccountName(
+    val name: String
+)

+ 10 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/vo/AccountRegisterInfo.kt

@@ -0,0 +1,10 @@
+package org.brynhild.graduation.common.transfer.vo
+
+data class AccountRegisterInfo(
+    private val username: String,
+    private val password: String,
+    private val email: String,
+    private val name:String,
+    private val tel:String,
+    private val no:String
+)

+ 5 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/vo/AccountToken.kt

@@ -0,0 +1,5 @@
+package org.brynhild.graduation.common.transfer.vo
+
+data class AccountToken(
+    val token: String
+)

+ 8 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/vo/ModifyAccountInfo.kt

@@ -0,0 +1,8 @@
+package org.brynhild.graduation.common.transfer.vo
+
+data class ModifyAccountInfo(
+    val password:String?,
+    val email:String?,
+    val avatar:String?,
+    val tel:String?
+)

+ 4 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/vo/ResendEmail.kt

@@ -0,0 +1,4 @@
+package org.brynhild.graduation.common.transfer.vo
+
+class ResendEmail(val username:String,val email:String,val type:Int) {
+}

+ 7 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/vo/ResetPasswordByCode.kt

@@ -0,0 +1,7 @@
+package org.brynhild.graduation.common.transfer.vo
+
+data class ResetPasswordByCode(
+    val name: String,
+    val code: String,
+    val newPassword: String
+)

+ 9 - 0
app/src/main/java/org/brynhild/graduation/common/transfer/vo/UserBindDeviceInfo.kt

@@ -0,0 +1,9 @@
+package org.brynhild.graduation.common.transfer.vo
+
+data class UserBindDeviceInfo(
+    private val token: String,
+    private val code: String,
+    private val region: String,
+    private val note: String,
+    private val name: String
+)

+ 66 - 0
app/src/main/java/org/brynhild/graduation/common/utils/EChartOptionUtil.java

@@ -0,0 +1,66 @@
+package org.brynhild.graduation.common.utils;
+
+import com.github.abel533.echarts.axis.CategoryAxis;
+import com.github.abel533.echarts.axis.ValueAxis;
+import com.github.abel533.echarts.code.Trigger;
+import com.github.abel533.echarts.code.X;
+import com.github.abel533.echarts.json.GsonOption;
+import com.github.abel533.echarts.series.Line;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class EChartOptionUtil {
+
+    public static GsonOption getOptions(String title, String[] xAxis, List<LineWrapper> lineWrappers) {
+        List<Line> lines = new ArrayList<>();
+        for (LineWrapper wrapper : lineWrappers) {
+            Line line = new Line();
+            line.smooth(true).name(wrapper.name).data(wrapper.data).itemStyle().normal().lineStyle().shadowColor("rgba(0,0,0,0.4)");
+            lines.add(line);
+        }
+        return getOptionsByLine(title, xAxis, lines);
+    }
+
+    private static GsonOption getOptionsByLine(String title, @NotNull String[] xAxis, @NotNull List<Line> lines) {
+        GsonOption option = new GsonOption();
+        option.title().setText(title);
+        List<String> m = new ArrayList<>();
+        for (Line line : lines) {
+            m.add(line.name());
+        }
+
+        option.legend().data(m).x(X.center);
+        option.tooltip().trigger(Trigger.axis);
+        ValueAxis valueAxis = new ValueAxis();
+        option.yAxis(valueAxis);
+        CategoryAxis categorxAxis = new CategoryAxis();
+        categorxAxis.axisLine().onZero(false);
+        categorxAxis.boundaryGap(false);
+        categorxAxis.data((Object) xAxis);
+        option.xAxis(categorxAxis);
+
+        for (Line line : lines) {
+            option.series(line);
+        }
+
+        return option;
+    }
+
+    public static class LineWrapper {
+        public String name;
+        public Object[] data;
+
+        public LineWrapper(String name, Object[] data) {
+            this.name = name;
+            this.data = data;
+        }
+
+        public LineWrapper(String name, List<Object> data) {
+            this.name = name;
+            this.data = data.toArray();
+        }
+    }
+}

+ 29 - 0
app/src/main/java/org/brynhild/graduation/common/utils/FileUploadUtil.java

@@ -0,0 +1,29 @@
+package org.brynhild.graduation.common.utils;
+
+import org.brynhild.graduation.common.constant.FileStorageConstant;
+
+import java.io.File;
+
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+
+public class FileUploadUtil {
+
+    public static void uploadFile(File file, Callback callback) {
+        MultipartBody requestBody = new MultipartBody.Builder()
+                .setType(MultipartBody.FORM)
+                .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("application/octet-stream"), file))
+                .build();
+        Request request = new Request.Builder()
+                .url(FileStorageConstant.INSTANCE.getServerUploadPath())
+                .post(requestBody)
+                .build();
+        OkHttpClient client = new OkHttpClient();
+        client.newCall(request).enqueue(callback);
+
+    }
+}

+ 29 - 0
app/src/main/java/org/brynhild/graduation/common/utils/JsonUtils.kt

@@ -0,0 +1,29 @@
+package org.brynhild.graduation.common.utils
+
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+
+/**
+ *
+ * 参考文章:https://juejin.cn/post/7049983952496361503
+ */
+object JsonUtils {
+
+    /**
+     * fromJson2List
+     */
+    inline fun <reified T> fromJson2List(json: String) = fromJson<List<T>>(json)
+
+    /**
+     * fromJson
+     */
+    inline fun <reified T> fromJson(json: String): T? {
+        return try {
+            val type = object : TypeToken<T>() {}.type
+            return Gson().fromJson(json, type)
+        } catch (e: Exception) {
+            println("try exception,${e.message}")
+            null
+        }
+    }
+}

+ 162 - 0
app/src/main/java/org/brynhild/graduation/common/utils/Uri2PathUtil.java

@@ -0,0 +1,162 @@
+package org.brynhild.graduation.common.utils;
+
+import android.annotation.TargetApi;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+
+import androidx.loader.content.CursorLoader;
+
+public class Uri2PathUtil {
+
+    //复杂版处理  (适配多种API)
+    public static String getRealPathFromUri(Context context, Uri uri) {
+        int sdkVersion = Build.VERSION.SDK_INT;
+        if (sdkVersion < 11) return getRealPathFromUri_BelowApi11(context, uri);
+        if (sdkVersion < 19) return getRealPathFromUri_Api11To18(context, uri);
+        else return getRealPathFromUri_AboveApi19(context, uri);
+    }
+
+    /**
+     * 适配api19以上,根据uri获取图片的绝对路径
+     */
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    private static String getRealPathFromUri_AboveApi19(Context context, Uri uri) {
+        if (DocumentsContract.isDocumentUri(context, uri)) {
+            if (isExternalStorageDocument(uri)) {
+                final String docId = DocumentsContract.getDocumentId(uri);
+                final String[] split = docId.split(":");
+                final String type = split[0];
+                if ("primary".equalsIgnoreCase(type)) {
+                    return Environment.getExternalStorageDirectory() + "/" + split[1];
+                }
+            } else if (isDownloadsDocument(uri)) {
+                final String id = DocumentsContract.getDocumentId(uri);
+                final Uri contentUri = ContentUris.withAppendedId(
+                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+
+                return getDataColumn(context, contentUri, null, null);
+            } else if (isMediaDocument(uri)) {
+                final String docId = DocumentsContract.getDocumentId(uri);
+                final String[] split = docId.split(":");
+                final String type = split[0];
+
+                Uri contentUri;
+                if ("image".equals(type)) {
+                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+                } else if ("video".equals(type)) {
+                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+                } else if ("audio".equals(type)) {
+                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+                } else {
+                    contentUri = MediaStore.Files.getContentUri("external");
+                }
+
+                final String selection = "_id=?";
+                final String[] selectionArgs = new String[]{split[1]};
+                return getDataColumn(context, contentUri, selection, selectionArgs);
+            }
+
+
+        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
+            return getDataColumn(context, uri, null, null);
+        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
+            return uri.getPath();
+        }
+
+        return null;
+    }
+
+    /**
+     * 适配api11-api18,根据uri获取图片的绝对路径
+     */
+    private static String getRealPathFromUri_Api11To18(Context context, Uri uri) {
+        String filePath = null;
+        String[] projection = {MediaStore.Images.Media.DATA};
+        //这个有两个包不知道是哪个。。。。不过这个复杂版一般用不到
+        CursorLoader loader = new CursorLoader(context, uri, projection, null, null, null);
+        Cursor cursor = loader.loadInBackground();
+
+        if (cursor != null) {
+            cursor.moveToFirst();
+            filePath = cursor.getString(cursor.getColumnIndex(projection[0]));
+            cursor.close();
+        }
+        return filePath;
+    }
+
+    /**
+     * 适配api11以下(不包括api11),根据uri获取图片的绝对路径
+     */
+    private static String getRealPathFromUri_BelowApi11(Context context, Uri uri) {
+        String filePath = null;
+        String[] projection = {MediaStore.Images.Media.DATA};
+        Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
+        if (cursor != null && cursor.moveToFirst()) {
+            filePath = cursor.getString(cursor.getColumnIndex(projection[0]));
+            cursor.close();
+        }
+        return filePath;
+    }
+
+    /**
+     * Get the value of the data column for this Uri. This is useful for
+     * MediaStore Uris, and other file-based ContentProviders.
+     *
+     * @param context       The context.
+     * @param uri           The Uri to query.
+     * @param selection     (Optional) Filter used in the query.
+     * @param selectionArgs (Optional) Selection arguments used in the query.
+     * @return The value of the _data column, which is typically a file path.
+     */
+    public static String getDataColumn(Context context, Uri uri, String selection,
+                                       String[] selectionArgs) {
+        Cursor cursor = null;
+        String column = MediaStore.MediaColumns.DATA;
+        String[] projection = {column};
+        try {
+            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+                    null);
+            if (cursor != null && cursor.moveToFirst()) {
+                int column_index = cursor.getColumnIndexOrThrow(column);
+                return cursor.getString(column_index);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (cursor != null)
+                cursor.close();
+        }
+        return null;
+    }
+
+    /**
+     * @param uri The Uri to check.
+     * @return Whether the Uri authority is ExternalStorageProvider.
+     */
+    public static boolean isExternalStorageDocument(Uri uri) {
+        return "com.android.externalstorage.documents".equals(uri.getAuthority());
+    }
+
+    /**
+     * @param uri The Uri to check.
+     * @return Whether the Uri authority is DownloadsProvider.
+     */
+    public static boolean isDownloadsDocument(Uri uri) {
+        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+    }
+
+    /**
+     * @param uri The Uri to check.
+     * @return Whether the Uri authority is MediaProvider.
+     */
+    public static boolean isMediaDocument(Uri uri) {
+        return "com.android.providers.media.documents".equals(uri.getAuthority());
+    }
+}
+ 

+ 104 - 0
app/src/main/java/org/brynhild/graduation/common/view/ConfirmPopupWindow.kt

@@ -0,0 +1,104 @@
+package com.example.devicemanager.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.PopupWindow
+import org.brynhild.graduation.R
+import org.brynhild.graduation.databinding.ConfirmPopupWindowBinding
+
+@SuppressLint("InflateParams")
+class ConfirmPopupWindow(context: Context, builder: ConfirmPopupWindowBuilder? = null) :
+    PopupWindow() {
+
+    var confirmPopupWindowBinding: ConfirmPopupWindowBinding
+
+    init {
+        val inflater = LayoutInflater.from(context)
+        confirmPopupWindowBinding = ConfirmPopupWindowBinding.inflate(inflater)
+        contentView=confirmPopupWindowBinding.root
+        this.width = LinearLayout.LayoutParams.MATCH_PARENT //父布局减去padding
+        this.height = LinearLayout.LayoutParams.MATCH_PARENT
+        this.animationStyle = R.style.pop_animation  //进入和退出动画效果
+        this.isOutsideTouchable = true //是否可以
+        this.isClippingEnabled = false //背景透明化可以铺满全屏
+        // 设置最终的背景,也可以通过context.resources.getColor(resId)设置自己的颜色
+        val colorDrawable = ColorDrawable(Color.parseColor("#00000000"))
+        this.setBackgroundDrawable(colorDrawable) //设置背景
+
+        //设置取消的点击事件
+        confirmPopupWindowBinding.tvConfirmCancel.setOnClickListener {
+            //先隐藏弹窗
+            dismiss()
+            //调用接口的具体实现
+            builder?.mCancelListener?.invoke()
+        }
+
+        //设置确认的点击事件
+        confirmPopupWindowBinding.tvConfirmEnsure.setOnClickListener {
+            //先隐藏弹窗
+            dismiss()
+            builder?.mConfirmListener?.invoke()
+        }
+    }
+
+    //创建ConfirmPopupWindow的一个内部类
+    //返回Builder就可以流水线定义
+    class ConfirmPopupWindowBuilder(val context: Context) {
+        companion object {
+            fun init(context: Context): ConfirmPopupWindowBuilder {
+                return ConfirmPopupWindowBuilder(context)
+            }
+        }
+
+        var mCancelListener: () -> Unit = {} //一个不需要参数的无返回值的函数
+        var mConfirmListener: () -> Unit = {}
+
+        private val window: ConfirmPopupWindow = ConfirmPopupWindow(context, this)
+
+        fun build(): ConfirmPopupWindow {
+            window.confirmPopupWindowBinding.tvConfirmContent.visibility = View.VISIBLE
+            window.confirmPopupWindowBinding.tvConfirmCancel.visibility = View.VISIBLE
+            window.confirmPopupWindowBinding.tvConfirmEnsure.visibility = View.VISIBLE
+            return window
+        }
+
+        fun setContent(content: String): ConfirmPopupWindowBuilder {
+            window.confirmPopupWindowBinding.tvConfirmContent.visibility = View.VISIBLE
+            window.confirmPopupWindowBinding.tvConfirmContent.text = content
+            return this
+        }
+
+        fun setCancelListener(callback: () -> Unit): ConfirmPopupWindowBuilder {
+            mCancelListener = callback
+            return this
+        }
+
+        fun setEnsureListener(callback: () -> Unit): ConfirmPopupWindowBuilder {
+            mConfirmListener = callback
+            return this
+        }
+
+        fun setEnsureText(content: String): ConfirmPopupWindowBuilder {
+            window.confirmPopupWindowBinding.tvConfirmEnsure.visibility = View.VISIBLE
+            window.confirmPopupWindowBinding.tvConfirmEnsure.text = content
+            return this
+        }
+
+        fun setCancelText(content: String): ConfirmPopupWindowBuilder {
+            window.confirmPopupWindowBinding.tvConfirmCancel.visibility = View.VISIBLE
+            window.confirmPopupWindowBinding.tvConfirmCancel.text = content
+            return this
+        }
+
+    }
+
+    fun show() {
+        showAtLocation(contentView, Gravity.CENTER, 0, 0)
+    }
+}

+ 63 - 0
app/src/main/java/org/brynhild/graduation/common/view/EChartView.java

@@ -0,0 +1,63 @@
+package org.brynhild.graduation.common.view;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.webkit.ConsoleMessage;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+
+import com.github.abel533.echarts.json.GsonOption;
+
+public class EChartView extends WebView {
+    private static final String TAG = EChartView.class.getSimpleName();
+
+    public EChartView(Context context) {
+        this(context, null);
+    }
+
+    public EChartView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public EChartView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+        Log.d("EChart", "init echarts finish");
+    }
+
+    @SuppressLint("SetJavaScriptEnabled")
+    private void init() {
+        WebSettings webSettings = getSettings();
+        webSettings.setDomStorageEnabled(true);
+        webSettings.setJavaScriptEnabled(true);
+        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
+        webSettings.setSupportZoom(false);
+        webSettings.setDisplayZoomControls(false);
+
+
+        setWebChromeClient(new WebChromeClient() {
+            @Override
+            public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+                System.out.println("....");
+                return super.onConsoleMessage(consoleMessage);
+
+            }
+        });
+
+
+        loadUrl("file:///android_asset/echarts.html");
+    }
+
+    public void refreshEchartsWithOption(GsonOption option) {
+        if (option == null) {
+            return;
+        }
+        String optionString = option.toString();
+        String call = "javascript:loadEcharts('" + optionString + "')";
+        loadUrl(call);
+        Log.d("EChart", "call refresh Echarts...in View");
+    }
+}

+ 12 - 0
app/src/main/java/org/brynhild/graduation/common/view/KVInfo.kt

@@ -0,0 +1,12 @@
+package com.example.devicemanager.view
+
+import com.bin.david.form.annotation.SmartColumn
+import com.bin.david.form.annotation.SmartTable
+
+@SmartTable(name = "设备信息")
+data class KVInfo(
+    @SmartColumn(id = 0)
+    val key: String,
+    @SmartColumn(id = 1)
+    val value: String
+)

+ 123 - 0
app/src/main/java/org/brynhild/graduation/network/entiity/User.java

@@ -0,0 +1,123 @@
+package org.brynhild.graduation.network.entiity;
+
+import androidx.annotation.NonNull;
+
+import lombok.Data;
+
+import lombok.NoArgsConstructor;
+
+import org.brynhild.graduation.common.entity.BaseEntity;
+
+
+import java.io.Serializable;
+
+public class User extends BaseEntity implements Serializable {
+    private String username;
+    private String password;
+    private String email;
+    private Integer state ;
+    private String lastLogin ;
+    private String avatar ;
+    private Integer type ;
+    private String name;
+    private String sex;
+    private Long departmentId;
+    private String tel;
+    private String no;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public Integer getState() {
+        return state;
+    }
+
+    public void setState(Integer state) {
+        this.state = state;
+    }
+
+    public String getLastLogin() {
+        return lastLogin;
+    }
+
+    public void setLastLogin(String lastLogin) {
+        this.lastLogin = lastLogin;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getSex() {
+        return sex;
+    }
+
+    public void setSex(String sex) {
+        this.sex = sex;
+    }
+
+    public Long getDepartmentId() {
+        return departmentId;
+    }
+
+    public void setDepartmentId(Long departmentId) {
+        this.departmentId = departmentId;
+    }
+
+    public String getTel() {
+        return tel;
+    }
+
+    public void setTel(String tel) {
+        this.tel = tel;
+    }
+
+    public String getNo() {
+        return no;
+    }
+
+    public void setNo(String no) {
+        this.no = no;
+    }
+}

+ 45 - 0
app/src/main/java/org/brynhild/graduation/network/utils/OkHttpGlideModule.kt

@@ -0,0 +1,45 @@
+package org.brynhild.graduation.network.utils;
+
+import android.content.Context
+import com.bumptech.glide.Glide
+import com.bumptech.glide.Registry
+import com.bumptech.glide.annotation.GlideModule;
+import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
+import com.bumptech.glide.load.model.GlideUrl
+import com.bumptech.glide.module.AppGlideModule
+import okhttp3.OkHttpClient
+import java.io.InputStream
+import java.security.SecureRandom
+import java.security.cert.X509Certificate
+import javax.net.ssl.SSLContext
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
+
+//@GlideModule
+class OkHttpGlideModule : AppGlideModule() {
+    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
+        val builder = OkHttpClient.Builder()
+        builder.sslSocketFactory(sSLSocketFactory, trustManager)
+        val okHttpClient = builder.build()
+        registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(okHttpClient))
+    }
+
+    /** 获取一个SSLSocketFactory */
+    val sSLSocketFactory: SSLSocketFactory
+        get() = try {
+            val sslContext = SSLContext.getInstance("SSL")
+            sslContext.init(null, arrayOf(trustManager), SecureRandom())
+            sslContext.socketFactory
+        } catch (e: Exception) {
+            throw RuntimeException(e)
+        }
+
+    /** 获取一个忽略证书的X509TrustManager */
+    val trustManager: X509TrustManager
+        get() = object : X509TrustManager {
+            override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { }
+            override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) { }
+            override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
+        }
+
+}

+ 61 - 0
app/src/main/java/org/brynhild/graduation/network/utils/UnsafeOkHttpClient.java

@@ -0,0 +1,61 @@
+package org.brynhild.graduation.network.utils;
+
+import java.security.cert.CertificateException;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import okhttp3.OkHttpClient;
+
+public class UnsafeOkHttpClient {
+    public static OkHttpClient getUnsafeOkHttpClient() {
+        try {
+            // Create a trust manager that does not validate certificate chains
+            final TrustManager[] trustAllCerts = new TrustManager[]{
+                    new X509TrustManager() {
+                        @Override
+                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
+                        }
+
+                        @Override
+                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
+                        }
+
+                        @Override
+                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+                            return new java.security.cert.X509Certificate[]{};
+                        }
+                    }
+            };
+
+            // Install the all-trusting trust manager
+            final SSLContext sslContext = SSLContext.getInstance("SSL");
+            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
+
+            // Create an ssl socket factory with our all-trusting manager
+            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
+
+            OkHttpClient.Builder builder = new OkHttpClient.Builder();
+            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
+            builder.hostnameVerifier(new HostnameVerifier() {
+                @Override
+                public boolean verify(String hostname, SSLSession session) {
+                    return true;
+                }
+            });
+
+            builder.connectTimeout(20, TimeUnit.SECONDS);
+            builder.readTimeout(20,TimeUnit.SECONDS);
+
+            OkHttpClient okHttpClient = builder.build();
+            return okHttpClient;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 38 - 0
app/src/main/java/org/brynhild/graduation/service/http/AccountService.kt

@@ -0,0 +1,38 @@
+package org.brynhild.graduation.service.http
+
+import org.brynhild.graduation.common.constant.AccountConstant
+import org.brynhild.graduation.common.transfer.Result
+import org.brynhild.graduation.common.transfer.vo.*
+import retrofit2.Call
+import retrofit2.http.Body
+import retrofit2.http.HTTP
+import retrofit2.http.Header
+import retrofit2.http.POST
+import retrofit2.http.PUT
+
+interface AccountService {
+
+    @POST("user/public/login")
+    fun login(@Body info: AccountLoginInfo): Call<Result>
+
+    @POST("user/public/register")
+    fun accountRegister(@Body info: AccountRegisterInfo): Call<Result>
+
+    @POST("user/public/resend")
+    fun resendEmail(@Body info:ResendEmail):Call<Result>
+
+    @POST("user/common/info")
+    fun accountInfo(@Header(AccountConstant.ACCOUNT_HEADER) token: String): Call<Result>
+
+    @PUT("user/common/info")
+    fun modifyAccountInfo(@Header(AccountConstant.ACCOUNT_HEADER) token:String,@Body info: ModifyAccountInfo): Call<Result>
+
+    @HTTP(method = "DELETE", path = "account", hasBody = true)
+    fun deleteAccount(@Body info: AccountToken): Call<Result>
+
+    @POST("account/forget")
+    fun sendForgetEmail(@Body info: AccountName): Call<Result>
+
+    @POST("account/reset")
+    fun resetAccountByCode(@Body info: ResetPasswordByCode): Call<Result>
+}

+ 45 - 0
app/src/main/java/org/brynhild/graduation/service/mqtt/MqttListenService.kt

@@ -0,0 +1,45 @@
+package org.brynhild.graduation.service.mqtt
+
+import android.app.Service
+import android.content.Intent
+import android.os.Binder
+import android.os.IBinder
+import android.util.Log
+import org.brynhild.graduation.common.config.LoginConfiguration
+import org.brynhild.graduation.common.config.MqttServerConfig
+import org.brynhild.graduation.common.config.MyApplication
+
+class MqttListenService : Service() {
+    val TAG = MqttListenService::class.java.simpleName
+
+    override fun onCreate() {
+        super.onCreate()
+        Log.e(TAG, "onCreate")
+        init()
+    }
+
+    override fun onDestroy() {
+        stopSelf()
+        super.onDestroy()
+    }
+
+    private fun init() {
+        println("ListenService创建")
+        MyApplication.serviceContext = this
+        val clientId = MqttServerConfig.getClientId() ?: ""
+        val client = SubscribeClient(clientId)
+        client.subscribe()
+        client.publish(LoginConfiguration.userInfo!!.token)
+    }
+
+    override fun onBind(intent: Intent): IBinder {
+        Log.e(TAG, "onBind")
+        return CustomBinder()
+    }
+
+    inner class CustomBinder : Binder() {
+        fun getService(): MqttListenService {
+            return this@MqttListenService
+        }
+    }
+}

+ 52 - 0
app/src/main/java/org/brynhild/graduation/service/mqtt/PushCallback.java

@@ -0,0 +1,52 @@
+package org.brynhild.graduation.service.mqtt;
+
+import com.google.gson.Gson;
+
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+// 发布/订阅消息的回调类
+public class PushCallback implements MqttCallback {
+
+
+    // 客户端ID
+    private String clientId = "";
+
+
+    // 构造方法
+    public PushCallback(String clientId) {
+        this.clientId = clientId;
+    }
+
+
+    // 在断开连接时调用
+    @Override
+    public void connectionLost(Throwable throwable) {
+        throwable.printStackTrace();
+        System.out.println(this.clientId + "连接已断开!");
+    }
+
+
+    // 接收订阅主题消息的回调
+    @Override
+    public void messageArrived(String topic, MqttMessage message) throws Exception {
+
+        System.out.println("接收消息主题 : " + topic);
+        System.out.println("接收消息Qos : " + message.getQos());
+        System.out.println("接收消息内容 : " + new String(message.getPayload(), "GBK"));
+        String content = new String(message.getPayload(), "GBK");
+        if (content.startsWith("{")) {
+            Gson gson = new Gson();
+            String channelId = "com.example.devicemanager.service";
+
+        }
+    }
+
+    // 接收发布主题消息的回调
+    @Override
+    public void deliveryComplete(IMqttDeliveryToken token) {
+        System.out.println("发布消息回调:" + token.isComplete());
+    }
+
+}

+ 66 - 0
app/src/main/java/org/brynhild/graduation/service/mqtt/SubscribeClient.java

@@ -0,0 +1,66 @@
+package org.brynhild.graduation.service.mqtt;
+
+
+import org.brynhild.graduation.common.config.MqttServerConfig;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+
+// 订阅和接受消息
+public class SubscribeClient {
+
+
+    // 连接客户端
+    private final MqttClient client;
+    private String clientId = "subscribe_client";
+
+
+    // 构造方法,实例化客户端并连接
+    public SubscribeClient(String clientId) throws MqttException {
+
+        this.clientId = clientId;
+
+        // 构建包含连接参数的连接选择对象
+        MqttConnectOptions options = new MqttConnectOptions();
+        // 设置Mqtt版本
+        options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
+        // 设置清空Session,false表示服务器会保留客户端的连接记录,true表示每次以新的身份连接到服务器
+        options.setCleanSession(true);
+        // 设置超时时间,单位为秒
+        options.setConnectionTimeout(30);
+        // 设置会话心跳时间,单位为秒,客户端每隔10秒向服务端发送心跳包判断客户端是否在线
+        options.setKeepAliveInterval(10);
+        // 客户端是否自动尝试重新连接到服务器
+        options.setAutomaticReconnect(true);
+
+        options.setUserName("client");
+        options.setPassword("client".toCharArray());
+
+        // 创建MQTT客户端并发起连接代理服务器
+        System.out.println("注册新的clientId:" + clientId);
+        client = new MqttClient(MqttServerConfig.broker, clientId, new MemoryPersistence());
+        // 设置回调
+        client.setCallback(new PushCallback(this.clientId));
+        // 发起连接
+        client.connect(options);
+    }
+
+
+    // 订阅指定主题
+    public void subscribe() throws MqttException {
+        System.out.println("MQTT subscribe on: " + MqttServerConfig.INSTANCE.getListenTopic());
+        client.subscribe(MqttServerConfig.INSTANCE.getListenTopic(), MqttServerConfig.qos);
+    }
+
+    public void publish(String content) throws MqttException {
+        // 定义消息:hello
+        MqttMessage message = new MqttMessage();
+        message.setQos(MqttServerConfig.qos);
+        message.setPayload(content.getBytes());
+        // 发布消息
+        client.publish(MqttServerConfig.registerTopic, message);
+    }
+
+}

+ 9 - 0
app/src/main/res/anim/pop_in.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:duration="200"
+        android:fillAfter="true"
+        android:fromAlpha="0"
+        android:interpolator="@android:anim/accelerate_interpolator"
+        android:toAlpha="1" />
+</set>

+ 9 - 0
app/src/main/res/anim/pop_out.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:duration="200"
+        android:fillAfter="true"
+        android:fromAlpha="1"
+        android:interpolator="@android:anim/decelerate_interpolator"
+        android:toAlpha="0" />
+</set>

二进制
app/src/main/res/drawable-v24/abc_btn_check_to_on_mtrl_000.png


二进制
app/src/main/res/drawable-v24/abc_btn_check_to_on_mtrl_015.png


二进制
app/src/main/res/drawable-v24/app_icon.png


二进制
app/src/main/res/drawable-v24/bind.png


二进制
app/src/main/res/drawable-v24/break0.png


二进制
app/src/main/res/drawable-v24/break1.png


二进制
app/src/main/res/drawable-v24/down.png


二进制
app/src/main/res/drawable-v24/down2.png


二进制
app/src/main/res/drawable-v24/exit.png


二进制
app/src/main/res/drawable-v24/ic_launcher.png


+ 30 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

二进制
app/src/main/res/drawable-v24/left.png


二进制
app/src/main/res/drawable-v24/left2.png


二进制
app/src/main/res/drawable-v24/right.png


二进制
app/src/main/res/drawable-v24/right2.png


二进制
app/src/main/res/drawable-v24/ucrop_ic_cross.png


二进制
app/src/main/res/drawable-v24/ucrop_ic_next.png


二进制
app/src/main/res/drawable-v24/udp.png


二进制
app/src/main/res/drawable-v24/udp2.png


二进制
app/src/main/res/drawable-v24/up.png


二进制
app/src/main/res/drawable-v24/up2.png


二进制
app/src/main/res/drawable-xxhdpi/add.png


二进制
app/src/main/res/drawable-xxhdpi/close.png


二进制
app/src/main/res/drawable-xxhdpi/ic_backup.png


二进制
app/src/main/res/drawable-xxhdpi/ic_comment.png


二进制
app/src/main/res/drawable-xxhdpi/ic_delete.png


二进制
app/src/main/res/drawable-xxhdpi/ic_done.png


二进制
app/src/main/res/drawable-xxhdpi/ic_menu.png


二进制
app/src/main/res/drawable-xxhdpi/ic_settings.png


二进制
app/src/main/res/drawable-xxhdpi/imageres_110.png


二进制
app/src/main/res/drawable-xxhdpi/nav_call.png


二进制
app/src/main/res/drawable-xxhdpi/nav_computer.png


二进制
app/src/main/res/drawable-xxhdpi/nav_favorite.png


二进制
app/src/main/res/drawable-xxhdpi/nav_friends.png


部分文件因为文件数量过多而无法显示