From ca1be58db2b9b93d8f8b85861112d18f826f3ef8 Mon Sep 17 00:00:00 2001
From: squallzhjch <zhangjingchao@navinfo.com>
Date: Thu, 30 Mar 2023 16:08:36 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A6=BB=E7=BA=BF=E5=9C=B0?=
 =?UTF-8?q?=E5=9B=BE=E4=B8=8B=E8=BD=BD=E6=B5=81=E7=A8=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../main/java/com/navinfo/omqs/Constant.kt    |   7 +
 .../com/navinfo/omqs/hilt/GlobalModule.kt     |  10 +-
 .../navinfo/omqs/hilt/MainActivityModule.kt   |  16 ++-
 .../OfflineMapDownloadManager.kt              |  48 +++++++
 .../OfflineMapDownloadScope.kt                |  26 ++++
 .../omqs/ui/activity/login/LoginActivity.kt   |  72 +++++++++-
 .../omqs/ui/activity/login/LoginViewModel.kt  | 128 +++++++++++++++++-
 .../omqs/ui/activity/map/MainActivity.kt      |  11 +-
 .../collect/library/map/NIMapController.kt    |   4 +-
 .../map/handler/LayerManagerHandler.kt        |   4 +-
 .../collect/library/system/Constant.java      |   9 +-
 11 files changed, 308 insertions(+), 27 deletions(-)
 create mode 100644 app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt
 create mode 100644 app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt

diff --git a/app/src/main/java/com/navinfo/omqs/Constant.kt b/app/src/main/java/com/navinfo/omqs/Constant.kt
index 73cedcea..92d50246 100644
--- a/app/src/main/java/com/navinfo/omqs/Constant.kt
+++ b/app/src/main/java/com/navinfo/omqs/Constant.kt
@@ -2,6 +2,12 @@ package com.navinfo.omqs
 
 class Constant {
     companion object {
+        /**
+         * sd卡根目录
+         */
+        lateinit var ROOT_PATH: String
+        lateinit var MAP_PATH: String
+
         /**
          * 服务器地址
          */
@@ -14,6 +20,7 @@ class Constant {
         const val message_version_right_off = "1" //立即发送
 
         const val MESSAGE_PAGE_SIZE = 30 //消息列表一页最多数量
+
     }
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt b/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt
index 34697b56..90cac2bb 100644
--- a/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt
+++ b/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt
@@ -25,11 +25,11 @@ import javax.inject.Singleton
 @InstallIn(SingletonComponent::class)
 class GlobalModule {
 
-    @Singleton
-    @Provides
-    fun provideApplication(application: Application): OMQSApplication {
-        return application as OMQSApplication
-    }
+//    @Singleton
+//    @Provides
+//    fun provideApplication(application: Application): OMQSApplication {
+//        return application as OMQSApplication
+//    }
 
     /**
      * 注入 网络OKHttp 对象
diff --git a/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt b/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt
index f081b20e..92dd5207 100644
--- a/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt
+++ b/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt
@@ -1,22 +1,34 @@
 package com.navinfo.omqs.hilt
 
-import android.util.Log
+import android.content.Context
 import com.navinfo.collect.library.map.NIMapController
-import com.navinfo.omqs.ui.activity.map.MainViewModel
+import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
 import dagger.Module
 import dagger.Provides
 import dagger.hilt.InstallIn
 import dagger.hilt.android.components.ActivityRetainedComponent
+import dagger.hilt.android.qualifiers.ActivityContext
 import dagger.hilt.android.scopes.ActivityRetainedScoped
 
 @InstallIn(ActivityRetainedComponent::class)
 @Module
 class MainActivityModule {
 
+    /**
+     * 注入地图控制器,在activity范围内使用,单例
+     */
     @ActivityRetainedScoped
     @Provides
     fun providesMapController(): NIMapController = NIMapController()
 
+    /**
+     * 注入离线地图下载管理,在activity范围内使用,单例
+     */
+    @ActivityRetainedScoped
+    @Provides
+    fun providesOfflineMapDownloadManager(@ActivityContext context: Context): OfflineMapDownloadManager =
+        OfflineMapDownloadManager(context)
+
     /**
      * 实验失败,这样创建,viewmodel不会在activity销毁的时候同时销毁
      */
diff --git a/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt
new file mode 100644
index 00000000..cc720498
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadManager.kt
@@ -0,0 +1,48 @@
+package com.navinfo.omqs.http.offlinemapdownload
+
+import android.content.Context
+import android.os.Environment
+import android.text.TextUtils
+import com.navinfo.omqs.Constant
+import com.navinfo.omqs.bean.OfflineMapCityBean
+import dagger.hilt.android.qualifiers.ActivityContext
+import java.io.Serializable
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+
+/**
+ * 管理离线地图下载
+ */
+class OfflineMapDownloadManager @Inject constructor(@ActivityContext context: Context) {
+    /**
+     * 最多同时下载数量
+     */
+    private val MAX_SCOPE = 5
+
+    /**
+     * 存储有哪些城市需要下载
+     */
+    private val scopeMap: ConcurrentHashMap<String, OfflineMapDownloadScope> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
+        ConcurrentHashMap<String, OfflineMapDownloadScope>()
+    }
+
+    val downloadFolder: String? by lazy {
+        Constant.MAP_PATH + "/offline/"
+    }
+
+
+    /**
+     * 请求一个下载任务[OfflineMapDownloadScope]
+     * 这是创建[OfflineMapDownloadScope]的唯一途径,请不要通过其他方式创建[OfflineMapDownloadScope]
+     */
+    fun request(cityBean: OfflineMapCityBean): OfflineMapDownloadScope? {
+        //没有下载连接的不能下载
+        if (TextUtils.isEmpty(cityBean.url)) return null
+//        if(scopeMap.containsKey())
+        var downloadScope = scopeMap[cityBean.id]
+        if (downloadScope == null) {
+            scopeMap[cityBean.id] = OfflineMapDownloadScope(cityBean)
+        }
+        return downloadScope
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt
new file mode 100644
index 00000000..a51f93d5
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/http/offlinemapdownload/OfflineMapDownloadScope.kt
@@ -0,0 +1,26 @@
+package com.navinfo.omqs.http.offlinemapdownload
+
+import androidx.lifecycle.MutableLiveData
+import com.navinfo.omqs.bean.OfflineMapCityBean
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlin.coroutines.EmptyCoroutineContext
+
+/**
+ * 代表一个下载任务
+ * [OfflineMapCityBean.id]将做为下载任务的唯一标识
+ * 不要直接在外部直接创建此对象,那样就可能无法统一管理下载任务,请通过[OfflineMapDownloadManager.download]获取此对象
+ * 这是一个协程作用域,
+ * EmptyCoroutineContext 表示一个不包含任何元素的协程上下文,它通常用于创建新的协程上下文,或者作为协程上下文的基础。
+ */
+class OfflineMapDownloadScope(cityBean: OfflineMapCityBean) : CoroutineScope by CoroutineScope(EmptyCoroutineContext) {
+    /**
+     *
+     */
+    private var downloadJob: Job? = null
+
+    /**
+     *
+     */
+    private val downloadData = MutableLiveData<OfflineMapCityBean>()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt
index 32c8c503..f4fc2611 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt
@@ -2,12 +2,16 @@ package com.navinfo.omqs.ui.activity.login
 
 import android.content.Intent
 import android.os.Bundle
+import android.util.Log
+import android.widget.Toast
 import androidx.activity.viewModels
+import androidx.appcompat.app.AlertDialog
 import androidx.databinding.DataBindingUtil
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.navinfo.omqs.R
 import com.navinfo.omqs.databinding.ActivityLoginBinding
-import com.navinfo.omqs.ui.activity.map.MainActivity
 import com.navinfo.omqs.ui.activity.PermissionsActivity
+import com.navinfo.omqs.ui.activity.map.MainActivity
 
 /**
  * 登陆页面
@@ -16,16 +20,72 @@ class LoginActivity : PermissionsActivity() {
 
     private lateinit var binding: ActivityLoginBinding
     private val viewModel by viewModels<LoginViewModel>()
-
+    private var loginDialog: AlertDialog? = null
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
         binding.loginUserModel = viewModel
         binding.lifecycleOwner = this
         binding.activity = this
+        initView()
     }
 
+    private fun initView() {
+        //登录校验,初始化成功
+        viewModel.loginStatus.observe(this) {
+            when (it) {
+                LoginStatus.LOGIN_STATUS_NET_LOADING -> {
+                    loginDialog("验证用户信息...")
+                }
+                LoginStatus.LOGIN_STATUS_FOLDER_INIT -> {
+                    loginDialog("检查本地数据...")
+                }
+                LoginStatus.LOGIN_STATUS_FOLDER_FAILURE -> {
+                    Toast.makeText(this, "文件夹初始化失败", Toast.LENGTH_SHORT).show()
+                    loginDialog?.dismiss()
+                    loginDialog = null
+                }
+                LoginStatus.LOGIN_STATUS_NET_FAILURE -> {
+                    Toast.makeText(this, "网络访问失败", Toast.LENGTH_SHORT).show()
+                    loginDialog?.dismiss()
+                    loginDialog = null
+                }
+                LoginStatus.LOGIN_STATUS_SUCCESS -> {
+                    val intent = Intent(this@LoginActivity, MainActivity::class.java)
+                    startActivity(intent)
+//                    finish()
+                    loginDialog?.dismiss()
+                    loginDialog = null
+                }
+                LoginStatus.LOGIN_STATUS_CANCEL -> {
+                    loginDialog?.dismiss()
+                    loginDialog = null
+                }
+            }
+        }
+    }
+
+    /**
+     * 登录dialog
+     */
+    private fun loginDialog(message: String) {
+        if (loginDialog == null) {
+            loginDialog = MaterialAlertDialogBuilder(
+                this, com.google.android.material.R.style.MaterialAlertDialog_Material3
+            ).setTitle("登录").setMessage(message).show()
+            loginDialog!!.setCanceledOnTouchOutside(true)
+            loginDialog!!.setOnCancelListener {
+                viewModel.cancelLogin()
+            }
+        } else {
+            loginDialog!!.setMessage(message)
+        }
+    }
+
+    //进应用根本不调用,待查
     override fun onPermissionsGranted() {
+        Log.e("jingo","调用了吗")
+
     }
 
     override fun onPermissionsDenied() {
@@ -35,8 +95,8 @@ class LoginActivity : PermissionsActivity() {
      * 处理登录按钮
      */
     fun onClickLoginButton() {
-        val intent = Intent(this@LoginActivity, MainActivity::class.java)
-        startActivity(intent)
-//        finish()
+        viewModel.onClickLoginBtn(
+            this, binding.loginUsername.text.toString(), binding.loginPassword.text.toString()
+        )
     }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
index 5701aa09..2d6f29b6 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
@@ -1,17 +1,65 @@
 package com.navinfo.omqs.ui.activity.login
 
+import android.content.Context
+import android.util.Log
 import android.view.View
+import android.widget.Toast
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.navinfo.omqs.Constant
 import com.navinfo.omqs.bean.LoginUserBean
+import kotlinx.coroutines.*
+import okio.IOException
+import java.io.File
 
-class LoginViewModel : ViewModel() {
+enum class LoginStatus {
+    /**
+     * 访问服务器登陆中
+     */
+    LOGIN_STATUS_NET_LOADING,
+
+    /**
+     * 初始化文件夹
+     */
+    LOGIN_STATUS_FOLDER_INIT,
+
+    /**
+     * 创建文件夹失败
+     */
+    LOGIN_STATUS_FOLDER_FAILURE,
+
+    /**
+     * 网络访问失败
+     */
+    LOGIN_STATUS_NET_FAILURE,
+
+    /**
+     * 成功
+     */
+    LOGIN_STATUS_SUCCESS,
+
+    /**
+     * 取消
+     */
+    LOGIN_STATUS_CANCEL,
+}
+
+class LoginViewModel(
+) : ViewModel() {
+    //用户信息
     val loginUser: MutableLiveData<LoginUserBean> = MutableLiveData()
 
+    //是不是登录成功
+    val loginStatus: MutableLiveData<LoginStatus> = MutableLiveData()
+
+    var jobLogin: Job? = null;
+
     init {
         loginUser.value = LoginUserBean(username = "admin", password = "123456")
     }
 
+
     /**
      * 处理注册按钮
      */
@@ -19,4 +67,82 @@ class LoginViewModel : ViewModel() {
         loginUser.value!!.username = "admin2"
         loginUser.postValue(loginUser.value)
     }
+
+    /**
+     * 点击
+     */
+    fun onClickLoginBtn(context: Context, userName: String, password: String) {
+        if (userName.isEmpty()) {
+            Toast.makeText(context, "请输入用户名", Toast.LENGTH_SHORT).show()
+        }
+        if (password.isEmpty()) {
+            Toast.makeText(context, "请输入密码", Toast.LENGTH_SHORT).show()
+        }
+        //不指定IO,会在主线程里运行
+        jobLogin = viewModelScope.launch(Dispatchers.IO) {
+            loginCheck(context, userName, password)
+            Log.e("jingo", "运行完了1?${Thread.currentThread().name}")
+        }
+    }
+
+    /**
+     * 如果不用挂起函数的方式,直接把下面这段代码替换到上面,在delay之后,线程和delay之前不是同一个,有啥影响未知。。。
+     */
+
+    private suspend fun loginCheck(context: Context, userName: String, password: String) {
+        Log.e("jingo", "我在哪个线程里?${Thread.currentThread().name}")
+        //上面调用了线程切换,这里不用调用,即使调用了还是在同一个线程中,除非自定义协程域?(待验证)
+//        withContext(Dispatchers.IO) {
+        Log.e("jingo", "delay之前?${Thread.currentThread().name}")
+        //网络访问
+        loginStatus.postValue(LoginStatus.LOGIN_STATUS_NET_LOADING)
+        //假装网络访问,等待3秒
+        delay(3000)
+        //文件夹初始化
+        try {
+            loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_INIT)
+            createRootFolder(context)
+        } catch (e: IOException) {
+            loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_FAILURE)
+        }
+        //假装解压文件等
+        delay(1000)
+        loginStatus.postValue(LoginStatus.LOGIN_STATUS_SUCCESS)
+        Log.e("jingo", "delay之后?${Thread.currentThread().name}")
+
+//        }
+    }
+
+
+    @Throws(IOException::class)
+    private fun createRootFolder(context: Context) {
+        // 在SD卡创建项目目录
+        val sdCardPath = context.getExternalFilesDir(null)
+        sdCardPath?.let {
+            Constant.ROOT_PATH = sdCardPath.absolutePath
+            Constant.MAP_PATH = Constant.ROOT_PATH + "/map/"
+            val file = File(Constant.MAP_PATH)
+            if (!file.exists()) {
+                file.mkdirs()
+            }
+        }
+    }
+
+    /**
+     * 取消登录
+     */
+    fun cancelLogin() {
+        Log.e("jingo", "取消了?${Thread.currentThread().name}")
+        jobLogin?.let {
+            it.cancel()
+            loginStatus.postValue(LoginStatus.LOGIN_STATUS_CANCEL)
+        }
+    }
+
+    override fun onCleared() {
+        super.onCleared()
+        cancelLogin()
+    }
+
+
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt
index 8eb6f6cb..cb03179e 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt
@@ -1,12 +1,14 @@
 package com.navinfo.omqs.ui.activity.map
 
 import android.os.Bundle
+import android.provider.ContactsContract.Contacts
 import android.util.Log
 import androidx.activity.viewModels
 import androidx.core.view.WindowCompat
 import androidx.databinding.DataBindingUtil
 import androidx.lifecycle.viewModelScope
 import com.navinfo.collect.library.map.NIMapController
+import com.navinfo.omqs.Constant
 import com.navinfo.omqs.R
 import com.navinfo.omqs.databinding.ActivityMainBinding
 import com.navinfo.omqs.ui.activity.BaseActivity
@@ -32,7 +34,12 @@ class MainActivity : BaseActivity() {
 
         binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
         //初始化地图
-        mapController.init(this, binding.mapView.mainActivityMap)
+        mapController.init(
+            this,
+            binding.mapView.mainActivityMap,
+            null,
+            Constant.ROOT_PATH + "/map/"
+        )
         //关联生命周期
         binding.lifecycleOwner = this
         //给xml转递对象
@@ -59,7 +66,7 @@ class MainActivity : BaseActivity() {
         super.onDestroy()
         mapController.mMapView.onDestroy()
         mapController.locationLayerHandler.stopLocation()
-        Log.e("jingo","MainActivity 销毁")
+        Log.e("jingo", "MainActivity 销毁")
     }
 
     override fun onResume() {
diff --git a/collect-library/src/main/java/com/navinfo/collect/library/map/NIMapController.kt b/collect-library/src/main/java/com/navinfo/collect/library/map/NIMapController.kt
index c959fe81..fb6e7c1f 100644
--- a/collect-library/src/main/java/com/navinfo/collect/library/map/NIMapController.kt
+++ b/collect-library/src/main/java/com/navinfo/collect/library/map/NIMapController.kt
@@ -5,6 +5,7 @@ import android.util.Log
 import com.navinfo.collect.library.map.handler.*
 import com.navinfo.collect.library.map.maphandler.MeasureLayerHandler
 import com.navinfo.collect.library.map.handler.ViewportHandler
+import com.navinfo.collect.library.system.Constant
 
 /**
  *  地图控制器
@@ -22,7 +23,8 @@ class NIMapController {
     lateinit var measureLayerHandler: MeasureLayerHandler
 
 
-    fun init(context: Context, mapView: NIMapView, options: NIMapOptions? = null) {
+    fun init(context: Context, mapView: NIMapView, options: NIMapOptions? = null, mapPath: String) {
+        Constant.MAP_PATH = mapPath
         layerManagerHandler = LayerManagerHandler(context, mapView)
         locationLayerHandler = LocationLayerHandler(context, mapView)
         animationHandler = AnimationHandler(context, mapView)
diff --git a/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt b/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt
index c5a4ca79..3db6b728 100644
--- a/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt
+++ b/collect-library/src/main/java/com/navinfo/collect/library/map/handler/LayerManagerHandler.kt
@@ -5,6 +5,7 @@ import android.os.Environment
 import com.navinfo.collect.library.map.NIMapView
 import com.navinfo.collect.library.map.NIMapView.LAYER_GROUPS
 import com.navinfo.collect.library.map.source.NavinfoMapRastorTileSource
+import com.navinfo.collect.library.system.Constant
 import okhttp3.Cache
 import okhttp3.OkHttpClient
 import org.oscim.layers.Layer
@@ -18,7 +19,6 @@ import java.io.File
  */
 class LayerManagerHandler(context: Context, mapView: NIMapView) :
     BaseHandler(context, mapView) {
-    lateinit var mLocationLayer: LocationLayer
     private var baseRasterLayer: Layer? = null
 
     init {
@@ -62,7 +62,7 @@ class LayerManagerHandler(context: Context, mapView: NIMapView) :
         // 如果使用缓存
         if (useCache) {
             val cacheDirectory: File =
-                File(Environment.getExternalStorageState() + "/" + "lalalal", "tiles-raster")
+                File(Constant.MAP_PATH, "tiles-raster")
             val cacheSize = 300 * 1024 * 1024 // 300 MB
             val cache = Cache(cacheDirectory, cacheSize.toLong())
             builder.cache(cache)
diff --git a/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java b/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java
index bb5aeeb0..d6156d20 100644
--- a/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java
+++ b/collect-library/src/main/java/com/navinfo/collect/library/system/Constant.java
@@ -11,14 +11,7 @@ import java.util.Map;
 
 public class Constant {
 
-    public static String SD_PATH = Environment.getExternalStorageDirectory() + "";
-    public static String ROOT_PATH = SD_PATH + "/NavinfoCollect";
-
-    public static String PHOTO_PATH = ROOT_PATH + "/image";
-
-
-    public static double CONVERSION_FACTOR = 1000000d;
-
+    public static String MAP_PATH = Environment.getExternalStorageDirectory() + "/map/";
 
     public static void setVisibleTypeMap(Map<String, Boolean> visibleTypeMap) {
         Map<String, Boolean> HD_LAYER_VISIABLE_MAP= new HashMap<>();