diff --git a/app/build.gradle b/app/build.gradle index 8d24081c..cbd5583f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,9 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' + id 'com.google.dagger.hilt.android' + id 'io.realm.kotlin' } android { @@ -54,4 +57,22 @@ dependencies { implementation 'com.github.K1rakishou:Fuck-Storage-Access-Framework:v1.1.3' // Android工具类库 https://blankj.com/2016/07/31/android-utils-code/ implementation 'com.blankj:utilcodex:1.30.1' + //依赖注入 + //hilt + implementation "com.google.dagger:hilt-android:2.44" + kapt "com.google.dagger:hilt-compiler:2.44" + + // Retrofit 网络请求相关 + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.squareup.okhttp3:okhttp:4.9.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.9.0") + + // Realm相关依赖 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' + implementation 'io.realm.kotlin:library-base:1.7.0' +} +//允许引用生成的代码 +kapt { + correctErrorTypes true } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 13a16152..faf0cbc9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ @@ -48,15 +50,10 @@ - + android:theme="@style/Theme.OMQualityInspection" /> \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/Constant.kt b/app/src/main/java/com/navinfo/omqs/Constant.kt new file mode 100644 index 00000000..73cedcea --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/Constant.kt @@ -0,0 +1,19 @@ +package com.navinfo.omqs + +class Constant { + companion object { + /** + * 服务器地址 + */ + const val SERVER_ADDRESS = "http://fastmap.navinfo.com/drdc/" + + const val DEBUG = true + + const val message_status_late = "预约,待发送" + const val message_status_send_over = "已发送" + 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/OMQSApplication.kt b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt new file mode 100644 index 00000000..7e85a078 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt @@ -0,0 +1,8 @@ +package com.navinfo.omqs + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class OMQSApplication : Application() { +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/model/LoginUser.kt b/app/src/main/java/com/navinfo/omqs/bean/LoginUserBean.kt similarity index 52% rename from app/src/main/java/com/navinfo/omqs/model/LoginUser.kt rename to app/src/main/java/com/navinfo/omqs/bean/LoginUserBean.kt index 77c0aceb..bfd9bcbd 100644 --- a/app/src/main/java/com/navinfo/omqs/model/LoginUser.kt +++ b/app/src/main/java/com/navinfo/omqs/bean/LoginUserBean.kt @@ -1,6 +1,6 @@ -package com.navinfo.omqs.model +package com.navinfo.omqs.bean -data class LoginUser( +data class LoginUserBean( var username: String = "", var password: String = "" ) \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt new file mode 100644 index 00000000..3fe6a890 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt @@ -0,0 +1,36 @@ +package com.navinfo.omqs.bean + +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.PrimaryKey + +data class OfflineMapCityBean( + @PrimaryKey + val id: String, + val fileName: String, + val name: String, + val url: String, + val version: Long, + val fileSize: Long, + var currentSize:Long = 0, + var status:Int = NONE +): RealmObject { + companion object Status{ + const val NONE = 0 //无状态 + const val WAITING = 1 //等待中 + const val LOADING = 2 //下载中 + const val PAUSE = 3 //暂停 + const val ERROR = 4 //错误 + const val DONE = 5 //完成 + const val UPDATE = 6 //有新版本要更新 + } + fun getFileSizeText(): String { + return if (fileSize < 1024.0) + "$fileSize B" + else if (fileSize < 1048576.0) + "%.2f K".format(fileSize / 1024.0) + else if (fileSize < 1073741824.0) + "%.2f M".format(fileSize / 1048576.0) + else + "%.2f M".format(fileSize / 1073741824.0) + } +} \ 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 new file mode 100644 index 00000000..34697b56 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/hilt/GlobalModule.kt @@ -0,0 +1,94 @@ +package com.navinfo.omqs.hilt + +import android.app.Application +import android.util.Log +import com.google.gson.Gson +import com.navinfo.omqs.Constant +import com.navinfo.omqs.OMQSApplication +import com.navinfo.omqs.http.RetrofitNetworkServiceAPI +import dagger.Lazy +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit +import javax.inject.Singleton + +/** + * 全局单例 注入对象 + */ +@Module +@InstallIn(SingletonComponent::class) +class GlobalModule { + + @Singleton + @Provides + fun provideApplication(application: Application): OMQSApplication { + return application as OMQSApplication + } + + /** + * 注入 网络OKHttp 对象 + */ + @Provides + @Singleton + fun provideOkHttpClient(interceptor: HttpLoggingInterceptor): OkHttpClient { + return OkHttpClient.Builder().addInterceptor(interceptor) + .connectTimeout(60, TimeUnit.SECONDS).writeTimeout(5, TimeUnit.MINUTES) + .readTimeout(5, TimeUnit.MINUTES).build() + } + + /** + * 注入 OHHttp log + */ + @Provides + @Singleton + fun provideLoggingInterceptor(): HttpLoggingInterceptor { + return HttpLoggingInterceptor { + if (Constant.DEBUG) { + Log.e("jingo", it) + } + }.apply { + level = if (Constant.DEBUG) { + HttpLoggingInterceptor.Level.BODY + } else { + HttpLoggingInterceptor.Level.NONE + } + } + } + + + @Provides + @Singleton + fun provideRetrofit( + client: Lazy, + converterFactory: GsonConverterFactory, + ): Retrofit { + val retrofitBuilder = Retrofit.Builder() + .baseUrl(Constant.SERVER_ADDRESS) + .client(client.get()) + .addConverterFactory(converterFactory) + + return retrofitBuilder.build() + } + + @Provides + @Singleton + fun provideGson(): Gson = Gson() + + @Provides + @Singleton + fun provideGsonConverterFactory(gson: Gson): GsonConverterFactory { + return GsonConverterFactory.create(gson) + } + + @Provides + @Singleton + fun provideNetworkService(retrofit: Retrofit): RetrofitNetworkServiceAPI { + return retrofit.create(RetrofitNetworkServiceAPI::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt b/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt new file mode 100644 index 00000000..f081b20e --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/hilt/MainActivityModule.kt @@ -0,0 +1,30 @@ +package com.navinfo.omqs.hilt + +import android.util.Log +import com.navinfo.collect.library.map.NIMapController +import com.navinfo.omqs.ui.activity.map.MainViewModel +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityRetainedComponent +import dagger.hilt.android.scopes.ActivityRetainedScoped + +@InstallIn(ActivityRetainedComponent::class) +@Module +class MainActivityModule { + + @ActivityRetainedScoped + @Provides + fun providesMapController(): NIMapController = NIMapController() + + /** + * 实验失败,这样创建,viewmodel不会在activity销毁的时候同时销毁 + */ +// @ActivityRetainedScoped +// @Provides +// fun providesMainViewModel(mapController: NIMapController): MainViewModel { +// Log.e("jingo", "MainViewModel 被创建") +// return MainViewModel(mapController) +// } + +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/hilt/NetworkServiceModule.kt b/app/src/main/java/com/navinfo/omqs/hilt/NetworkServiceModule.kt new file mode 100644 index 00000000..3ee2d9c5 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/hilt/NetworkServiceModule.kt @@ -0,0 +1,16 @@ +package com.navinfo.omqs.hilt + +import com.navinfo.omqs.http.NetworkService +import com.navinfo.omqs.http.NetworkServiceImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@InstallIn(SingletonComponent::class) +@Module +abstract class NetworkServiceModule { + + @Binds + abstract fun bindNetworkService(networkServiceImpl: NetworkServiceImpl): NetworkService +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/http/NetResult.kt b/app/src/main/java/com/navinfo/omqs/http/NetResult.kt new file mode 100644 index 00000000..4222a09f --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/http/NetResult.kt @@ -0,0 +1,53 @@ +package com.navinfo.omqs.http + +/** + * Created by Mayokun Adeniyi on 23/05/2020. + */ + +/** + * 网络返回封装 + */ + +/** + * 在类名之前使用sealed关键字将类声明为密封类。 + * 密封类仅在编译时限制类型集来确保类型安全的重要性。 + * 密封类隐式是一个无法实例化的抽象类。 + */ +/** + * 二:密封类所具有的特性和与别的类具有不同之处 + +①Sealed class(密封类) 是一个有特定数量子类的类,看上去和枚举有点类似,所不同的是,在枚举中,我们每个类型只有一个对象(实例);而在密封类中,同一个类可以拥有几个对象。 + +②Sealed class(密封类)的所有子类都必须与密封类在同一文件中 + +③Sealed class(密封类)的子类的子类可以定义在任何地方,并不需要和密封类定义在同一个文件中 + +④Sealed class(密封类)没有构造函数,不可以直接实例化,只能实例化内部的子类 + */ +/** + * 如何获取密封类里面的函数方法 + * 只能创建密封类子类对象 通过密封类的子类对象调用密封类里的函数方法 + */ +sealed class NetResult { + + data class Success(val data: T?) : NetResult() + data class Failure(val code: Int, val msg: String) : NetResult() + data class Error(val exception: Exception) : NetResult() + object Loading : NetResult() + + /** + * 密封类通常与表达when时一起使用。 由于密封类的子类将自身类型作为一种情况。 + 因此,密封类中的when表达式涵盖所有情况,从而避免使用else子句。 + */ + override fun toString(): String { + return when (this) { + is Success<*> -> "网络访问成功,返回正确结果Success[data=$data]" + is Failure -> "网络访问成功,返回错误结果Failure[$msg]" + is Error -> "网络访问出错 Error[exception=$exception]" + is Loading -> "网络访问中 Loading" + } + } +} +/** + * 密封类里面可以有若干个子类,这些子类如果要继承密封类,则必须和密封类在同一个文件里 + */ \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/http/NetworkService.kt b/app/src/main/java/com/navinfo/omqs/http/NetworkService.kt new file mode 100644 index 00000000..8ed34846 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/http/NetworkService.kt @@ -0,0 +1,13 @@ +package com.navinfo.omqs.http + +import com.navinfo.omqs.bean.OfflineMapCityBean + +/** + * 网络访问 业务接口 + */ +interface NetworkService { + /** + * 获取离线地图城市列表 + */ + suspend fun getOfflineMapCityList():NetResult> +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/http/NetworkServiceImpl.kt b/app/src/main/java/com/navinfo/omqs/http/NetworkServiceImpl.kt new file mode 100644 index 00000000..4a67bf83 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/http/NetworkServiceImpl.kt @@ -0,0 +1,35 @@ +package com.navinfo.omqs.http + +import com.navinfo.omqs.bean.OfflineMapCityBean +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import javax.inject.Inject + +/** + * 网络访问业务接口的具体实现 + */ +class NetworkServiceImpl @Inject constructor( + private val netApi: RetrofitNetworkServiceAPI, +) : NetworkService { + /** + * 获取离线地图城市列表 + */ + override suspend fun getOfflineMapCityList(): NetResult> = + //在IO线程中运行 + withContext(Dispatchers.IO) { + return@withContext try { + val result = netApi.retrofitGetOfflineMapCityList() + if (result.isSuccessful) { + if (result.code() == 200) { + NetResult.Success(result.body()) + } else { + NetResult.Failure(result.code(), result.message()) + } + } else { + NetResult.Failure(result.code(), result.message()) + } + } catch (e: Exception) { + NetResult.Error(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/http/RetrofitNetworkServiceAPI.kt b/app/src/main/java/com/navinfo/omqs/http/RetrofitNetworkServiceAPI.kt new file mode 100644 index 00000000..5267cc8c --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/http/RetrofitNetworkServiceAPI.kt @@ -0,0 +1,237 @@ +package com.navinfo.omqs.http + +import androidx.lifecycle.LiveData +import com.navinfo.omqs.bean.OfflineMapCityBean +import okhttp3.ResponseBody +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Streaming +import retrofit2.http.Url +import java.util.concurrent.Flow + +/** + * retrofit2 网络请求接口 + */ +interface RetrofitNetworkServiceAPI { + + /** + * 在 Retrofit2 中,可以使用不同类型的返回值来获取 API 调用的结果。以下是 Retrofit2 支持的一些常见返回类型: + + 1. `Call`:表示一个异步请求,其中 `T` 是 API 响应的类型,通常是一个自定义的数据类。使用 `enqueue` 方法来执行异步请求,并在回调中处理响应。 + + 2. `Response`:表示一个同步请求的响应,其中 `T` 是 API 响应的类型。使用 `execute` 方法来执行同步请求,并返回一个 `Response` 对象,其中包含了响应的状态码、响应头和响应体等信息。 + + 3. `Observable`:表示一个 RxJava 的 Observable 对象,其中 `T` 是 API 响应的类型。使用 `subscribe` 方法来执行请求,并在 onNext 回调中处理响应。 + + 4. `Single`:表示一个 RxJava 的 Single 对象,其中 `T` 是 API 响应的类型。使用 `subscribe` 方法来执行请求,并在 onSuccess 回调中处理响应。 + + 5. `Completable`:表示一个 RxJava 的 Completable 对象,用于执行没有响应体的 API 调用。使用 `subscribe` 方法来执行请求,并在 onComplete 回调中处理响应。 + + 6. `Flowable`:表示一个 RxJava 的 Flowable 对象,其中 `T` 是 API 响应的类型。类似于 Observable,但支持背压处理。 + + 7. `Deferred`:表示一个 Kotlin 的 Deferred 对象,其中 `T` 是 API 响应的类型。使用 `await` 方法来执行请求,并返回响应的结果。 + + 8. `LiveData>`:表示一个 LiveData 对象,其中 `T` 是 API 响应的类型。使用 `observe` 方法来执行请求,并在回调中处理响应。 + + 总之,Retrofit2 支持多种返回类型,开发者可以根据项目需求选择合适的方式来处理 API 响应结果。 + */ + + /** + * 获取离线地图城市列表 + */ + @GET("/drdc/MapDownload/maplist") + suspend fun retrofitGetOfflineMapCityList(): Response> + + /** + * 下载文件 + */ + @Streaming + @GET + suspend fun retrofitDownLoadFile(@Url url: String):Response + + + /** + * @FormUrlEncoded 请求格式注解,请求实体是一个From表单,每个键值对需要使用@Field注解 + @Field 请求参数注解,提交请求的表单字段,必须要添加,而且需要配合@FormUrlEncoded使用 + “token” 参数字段,与后台给的字段需要一致 + String 声明的参数类型 + token 实际参数,表示后面token的取值作为"token"的值 + Post请求如果有参数需要在头部添加@FormUrlEncoded注解,表示请求实体是一个From表单,每个键值对需要使用@Field注解,使用@Field添加参数,这是发送Post请求时,提交请求的表单字段,必须要添加的,而且需要配合@FormUrlEncoded使用,若为在头部添加@FormUrlEncoded注解,会抛出如下异常: + 当有多个不确定参数时,我们可以使用@FieldMap注解,@FieldMap与@Field的作用一致,可以用于添加多个不确定的参数,类似@QueryMap,Map的key作为表单的键,Map的value作为表单的值。 + */ +// @FormUrlEncoded +// @POST("api/dog") +// fun postCall(@Field("token") token: String?): Call? + + /** + * 当有多个不确定参数时,我们可以使用@FieldMap注解,@FieldMap与@Field的作用一致,可以用于添加多个不确定的参数,类似@QueryMap,Map的key作为表单的键,Map的value作为表单的值。 + */ +// @FormUrlEncoded +// @POST("api/dog") +// open fun postCall(@FieldMap map: Map?): Call? + /** + * @GET("v7/weather/now") + Call getCall(@QueryMap Map map); + */ +// @GET("v7/weather/now") +// open fun getCall(@QueryMap map: Map?): Call? + /** + *@Query 请求参数注解,用于Get请求中的参数 + “location”/“key” 参数字段,与后台给的字段需要一致 + */ +// @GET("v7/weather/now") +// open fun getCall(@QueryMap map: Map?): Call? + + /** + * @FieldMap 请求参数注解,与@Field作用一致,用于不确定表单参数 + Map map 通过Map将不确定的参数传入,相当于多个Field参数 + 适用于Post请求的还有一个注解@Body,@Body可以传递自定义类型数据给服务器,多用于post请求发送非表单数据,比如用传递Json格式数据,它可以注解很多东西,比如HashMap、实体类等,我们来看看它用法: + 特别注意:@Body注解不能用于表单或者支持文件上传的表单的编码,即不能与@FormUrlEncoded和@Multipart注解同时使用 + */ +// @POST("") +// open fun getPsotDataBody(@Body body: RequestBody?): Call? + + /** + * @Path + 使用第一个get请求网址 + */ +// @GET("v7/weather/{time}") +// open fun getCall(@Path("time") time: String?,@QueryMap map: Map?): Call? + + /** + * @QueryMap get请求方法参数的注解,上面已经解释了,这里就不重复讲 + @Path 请求参数注解,用于Url中的占位符{},在网址中的参数 + @Path注解用于Url中的占位符{},在网址中的参数,如上面 @GET(“v7/weather/{time}”)的time,通过{}占位符来标记time,使用@Path注解传入time的值,注意有的Url既有占位符又有"?"后面的键值对,其实@Query和@Path两者是可以共用的。在发起请求时,{time}会被替换为方法中第二个参数的值time。 + */ +// Map map = new HashMap<>()Map map = new HashMap<>(); +// map.put("location","101010100"); +// map.put("key","a5cf6ab782a14013b08fb92a57dd2f72"); +// Call call = apiService.getCall("now",map); + /** + * 最终{time}会被替换为now,此时形成的牵绊部分Url为:https://devapi.qweather.com/v7/weather/now,剩余**?号**后面的Url由@Query传入。 + */ + + /** + * @HTTP + @HTTP注解的作用是替换@GET、@POST、@PUT、@DELETE、@HEAD以及更多拓展功能 + 如可通过此注解代替@POST注解实现post请求,如下: + method 表示请求的方法,区分大小写,这里的值retrofit不会再做任何处理,必须要保证正确 + path 网络请求地址路径 + hasBody 是否有请求体,boolean类型 + 除请求接口部分的方法有变,别的部分与正常使用@POST请求一致,另外需要注意在使用@HTTP注解实现post请求时,若含有请求体,那么对应的标识注解@FormUrlEncoded一定要加上,若为别的请求体,那么就添加与之对应的标识注解。 + @HTTP注解可以通过method字段灵活设置具体请求方法,通过path设置网络请求地址,用的比较少。使用方法与设置的请求方法种类一致。 + */ + +// @FormUrlEncoded +// @POST("api/dog") +// Call postCall(@FieldMap Map map); +// +// @FormUrlEncoded +// @HTTP(method = "POST",path = "api/dog", hasBody = true) +// Call postCall1(@FieldMap Map map); + /** + * @Url + 如果需要重新地址接口地址,可以使用@Url,将地址以参数的形式传入即可。如果有@Url注解时,GET传入的Url必须省略。不然会抛出如下异常。 + */ + //错误的 +// @FormUrlEncoded +// @POST("api/dog") +// open fun postCall(@Url url: String?, @FieldMap map: Map?): Call? + //正确的 +// @FormUrlEncoded +// @POST +// open fun postCall(@Url url: String?, @FieldMap map: Map?): Call? + + /** + * @Streaming + * @Streaming 表示响应体的数据用流的方式返回,使用于返回数据比较大,该注解在下载大文件时特别有用 + */ +// @Streaming +// @POST("gists/public") +// open fun getStreamingBig(): Call? + + /** + * @Multipart、@part、@PartMap + * @Multipart 表示请求实体是一个支持文件上传的表单,需要配合@Part和@PartMap使用,适用于文件上传 + @Part 用于表单字段,适用于文件上传的情况,@Part支持三种类型:RequestBody、MultipartBody.Part、 任意类型 + @PartMap 用于多文件上传, 与@FieldMap和@QueryMap的使用类似 + */ +// @Multipart +// @POST("user/followers") +// open fun getPartData(@Part("name") name: RequestBody?, @Part file: Part?): Call? + + /** + * 代码使用逻辑: + * 首先声明类型,通过MediaType实现类型的声明,此处使用的时文本类型,然后会根据该类型转化为RequestBody对象,此处使用的参数委RequestBody对象,相当于讲周润发以文本格式转换委RequestBody对象,最终上传时与name形成键值对。 + */ +// //声明类型,这里是文字类型 +// MediaType textType = MediaType.parse("text/plain"); +// //根据声明的类型创建RequestBody,就是转化为RequestBody对象 +// RequestBody name = RequestBody.create(textType, "周润发"); + +////创建文件,这里演示图片上传 +// File file = new File("文件路径"); +// if (!file.exists()) { +// file.mkdir(); +// } +////将文件转化为RequestBody对象 +////需要在表单中进行文件上传时,就需要使用该格式:multipart/form-data +// RequestBody imgBody = RequestBody.create(MediaType.parse("image/png"), file); +////将文件转化为MultipartBody.Part +////第一个参数:上传文件的key;第二个参数:文件名;第三个参数:RequestBody对象 +// MultipartBody.Part filePart = MultipartBody.Part.createFormData("key", file.getName(), imgBody); + /** + * 对于文件类型的上传,我们一般使用MultipartBody.Part类型上传,在上面讲两个参数设置好后,然后调用接口使用请求方法即可: + * 此种格式属于文本与文件混合发送的格式。 + */ +// var partDataCall: Call? = retrofit.create(Api::class.java).getPartData(name, filePart) + + /** + * @PartMap的使用与@FieldMap和@QueryMap的使用类似,用于多文件上传,我们直接看代码: + */ +// @Multipart +// @POST("user/followers") +// open fun getPartMapData(@PartMap map: Map?): Call? + +// File file1 = new File("文件路径"); +// File file2 = new File("文件路径"); +// if (!file1.exists()) { +// file1.mkdir(); +// } +// if (!file2.exists()) { +// file2.mkdir(); +// } +// RequestBody requestBody1 = RequestBody.create(MediaType.parse("image/png"), file1); +// RequestBody requestBody2 = RequestBody.create(MediaType.parse("image/png"), file2); +// MultipartBody.Part filePart1 = MultipartBody.Part.createFormData("file1", file1.getName(), requestBody1); +// MultipartBody.Part filePart2 = MultipartBody.Part.createFormData("file2", file2.getName(), requestBody2); +// +// Map mapPart = new HashMap<>(); +// mapPart.put("file1",filePart1); +// mapPart.put("file2",filePart2); + + /** + * 有关MediaType方法 + * 概述:MediaType,即是Internet Media Type,互联网媒体类型,也叫做MIME类型,在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。(也就是说MediaType在网络协议的消息头里面叫做Content-Type)它使用两部分的标识符来确定一个类型,是为了表明我们传的东西是什么类型。 + 常见的媒体格式类型如下: + text/html : HTML格式 + text/plain :纯文本格式 + text/xml : XML格式 + image/gif :gif图片格式 + image/jpeg :jpg图片格式 + image/png:png图片格式 + 以application开头的媒体格式类型: + application/xhtml+xml :XHTML格式 + application/xml :XML数据格式 + application/atom+xml :Atom XML聚合格式 + application/json :JSON数据格式 + application/pdf :pdf格式 + application/msword : Word文档格式 + application/octet-stream : 二进制流数据(如常见的文件下载、上传) + application/x-www-form-urlencoded : 中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式 + 另外一种常见的媒体格式是上传文件之时使用的: + multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式 + */ + + +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt index 0dc22c98..b0293ec3 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt @@ -28,9 +28,9 @@ class MainActivity : PermissionsActivity(), FSAFActivityCallbacks { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - val navController = findNavController(R.id.nav_host_fragment_content_main) - appBarConfiguration = AppBarConfiguration(navController.graph) - setupActionBarWithNavController(navController, appBarConfiguration) +// val navController = findNavController(R.id.nav_host_fragment_content_main) +// appBarConfiguration = AppBarConfiguration(navController.graph) +// setupActionBarWithNavController(navController, appBarConfiguration) fileChooser.setCallbacks(this@MainActivity) // binding.fab.setOnClickListener { view -> @@ -57,27 +57,27 @@ class MainActivity : PermissionsActivity(), FSAFActivityCallbacks { override fun onPermissionsDenied() { } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - // Inflate the menu; this adds items to the action bar if it is present. - menuInflater.inflate(R.menu.menu_main, menu) - return true - } +// override fun onCreateOptionsMenu(menu: Menu): Boolean { +// // Inflate the menu; this adds items to the action bar if it is present. +// menuInflater.inflate(R.menu.menu_main, menu) +// return true +// } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - return when (item.itemId) { - R.id.action_settings -> true - else -> super.onOptionsItemSelected(item) - } - } - - override fun onSupportNavigateUp(): Boolean { - val navController = findNavController(R.id.nav_host_fragment_content_main) - return navController.navigateUp(appBarConfiguration) - || super.onSupportNavigateUp() - } +// override fun onOptionsItemSelected(item: MenuItem): Boolean { +// // Handle action bar item clicks here. The action bar will +// // automatically handle clicks on the Home/Up button, so long +// // as you specify a parent activity in AndroidManifest.xml. +// return when (item.itemId) { +// R.id.action_settings -> true +// else -> super.onOptionsItemSelected(item) +// } +// } +// +// override fun onSupportNavigateUp(): Boolean { +// val navController = findNavController(R.id.nav_host_fragment_content_main) +// return navController.navigateUp(appBarConfiguration) +// || super.onSupportNavigateUp() +// } override fun fsafStartActivityForResult(intent: Intent, requestCode: Int) { startActivityForResult(intent, requestCode) diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/LoginViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/LoginViewModel.kt deleted file mode 100644 index 29efc8fc..00000000 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/LoginViewModel.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.navinfo.omqs.ui.activity - -import android.view.View -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.navinfo.omqs.model.LoginUser - -class LoginViewModel : ViewModel() { - val loginUser: MutableLiveData = MutableLiveData() - - init { - loginUser.value = LoginUser(username = "admin", password = "123456") - } - - /** - * 处理注册按钮 - */ - fun onClick(view: View) { - loginUser.value!!.username = "admin2" - loginUser.postValue(loginUser.value) - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/MainActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/MainActivity.kt deleted file mode 100644 index 94061fbc..00000000 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/MainActivity.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.navinfo.omqs.ui.activity - -import android.os.Bundle -import androidx.activity.viewModels -import androidx.core.view.WindowCompat -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.LifecycleOwner -import com.navinfo.omqs.R -import com.navinfo.omqs.databinding.ActivityMainBinding - -/** - * 地图主页面 - */ -class MainActivity : BaseActivity() { - - private lateinit var binding: ActivityMainBinding - private val viewModel by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { - WindowCompat.setDecorFitsSystemWindows(window, false) - super.onCreate(savedInstanceState) - - binding = DataBindingUtil.setContentView(this, R.layout.activity_main) - //关联生命周期 - binding.lifecycleOwner = this - //给xml转递对象 - binding.mainActivity = this - //给xml传递viewModel对象 - binding.viewModel = viewModel - //初始化地图 - viewModel.initMap(this, binding.mapView.mainActivityMap) - //让viewModel监听activity生命周期 - lifecycle.addObserver(viewModel) - } - - /** - * 打开个人中菜单 - */ - fun openMenu() { - binding.mainActivityDrawer.open() - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/MainViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/MainViewModel.kt deleted file mode 100644 index 242c9673..00000000 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/MainViewModel.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.navinfo.omqs.ui.activity - -import android.app.Application -import android.content.Context -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModel -import com.navinfo.collect.library.map.NIMapController -import com.navinfo.collect.library.map.NIMapView - -class MainViewModel(val app: Application) : AndroidViewModel(app), DefaultLifecycleObserver { - /** - * 地图控制器 - */ - private lateinit var mapController: NIMapController - - /** - * 初始化地图 - */ - fun initMap(context: Context, mapView: NIMapView) { - mapController = NIMapController(context = app, mapView = mapView) - - } - - override fun onStart(owner: LifecycleOwner) { - super.onStart(owner) - //开启定位 - mapController.locationLayerHandler.startLocation() - } - - override fun onPause(owner: LifecycleOwner) { - mapController.mMapView.onPause() - } - - override fun onDestroy(owner: LifecycleOwner) { - mapController.mMapView.onDestroy() - //结束定位 - mapController.locationLayerHandler.stopLocation() - } - - override fun onResume(owner: LifecycleOwner) { - mapController.mMapView.onResume() - } - - /** - * 点击我的位置,回到我的位置 - */ - fun onClickLocationButton() { - mapController.locationLayerHandler.animateToCurrentPosition() - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/MapTestActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/MapTestActivity.kt deleted file mode 100644 index 44918797..00000000 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/MapTestActivity.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.navinfo.omqs.ui.activity - -import android.os.Bundle -import com.navinfo.collect.library.map.NIMapController -import com.navinfo.collect.library.map.NIMapView -import com.navinfo.omqs.R - -class MapTestActivity: BaseActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_map_test) - - val mapController = NIMapController(context = this, mapView = findViewById(R.id.main_activity_map1)) - mapController.locationLayerHandler.startLocation() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/LoginActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt similarity index 80% rename from app/src/main/java/com/navinfo/omqs/ui/activity/LoginActivity.kt rename to app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt index 4ba3dd65..3ef45e23 100644 --- a/app/src/main/java/com/navinfo/omqs/ui/activity/LoginActivity.kt +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginActivity.kt @@ -1,4 +1,4 @@ -package com.navinfo.omqs.ui.activity +package com.navinfo.omqs.ui.activity.login import android.content.Intent import android.os.Bundle @@ -6,6 +6,8 @@ import androidx.activity.viewModels import androidx.databinding.DataBindingUtil 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 /** * 登陆页面 @@ -24,6 +26,8 @@ class LoginActivity : PermissionsActivity() { } override fun onPermissionsGranted() { + // 获取权限后初始化Realm设置 + viewModel } override fun onPermissionsDenied() { @@ -35,6 +39,6 @@ class LoginActivity : PermissionsActivity() { fun onClickLoginButton() { val intent = Intent(this@LoginActivity, MainActivity::class.java) startActivity(intent) - finish() +// finish() } } \ 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 new file mode 100644 index 00000000..ab5cf59b --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt @@ -0,0 +1,30 @@ +package com.navinfo.omqs.ui.activity.login + +import android.view.View +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.navinfo.omqs.bean.LoginUserBean +import com.navinfo.omqs.bean.OfflineMapCityBean +import io.realm.kotlin.RealmConfiguration + +class LoginViewModel : ViewModel() { + val loginUser: MutableLiveData = MutableLiveData() + + init { + loginUser.value = LoginUserBean(username = "admin", password = "123456") + } + + fun initRealm() { + val config = RealmConfiguration.Builder(schema = setOf(OfflineMapCityBean::class)).directory( + + ) + } + + /** + * 处理注册按钮 + */ + fun onClick(view: View) { + loginUser.value!!.username = "admin2" + loginUser.postValue(loginUser.value) + } +} \ 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 new file mode 100644 index 00000000..8eb6f6cb --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainActivity.kt @@ -0,0 +1,77 @@ +package com.navinfo.omqs.ui.activity.map + +import android.os.Bundle +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.R +import com.navinfo.omqs.databinding.ActivityMainBinding +import com.navinfo.omqs.ui.activity.BaseActivity +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +/** + * 地图主页面 + */ +@AndroidEntryPoint +class MainActivity : BaseActivity() { + + private lateinit var binding: ActivityMainBinding + private val viewModel by viewModels() + + //注入地图控制器 + @Inject + lateinit var mapController: NIMapController + + override fun onCreate(savedInstanceState: Bundle?) { + WindowCompat.setDecorFitsSystemWindows(window, false) + super.onCreate(savedInstanceState) + + binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + //初始化地图 + mapController.init(this, binding.mapView.mainActivityMap) + //关联生命周期 + binding.lifecycleOwner = this + //给xml转递对象 + binding.mainActivity = this + //给xml传递viewModel对象 + binding.viewModel = viewModel +// lifecycle.addObserver(viewModel) + + } + + override fun onStart() { + super.onStart() + + //开启定位 + mapController.locationLayerHandler.startLocation() + } + + override fun onPause() { + super.onPause() + mapController.mMapView.onPause() + } + + override fun onDestroy() { + super.onDestroy() + mapController.mMapView.onDestroy() + mapController.locationLayerHandler.stopLocation() + Log.e("jingo","MainActivity 销毁") + } + + override fun onResume() { + super.onResume() + mapController.mMapView.onResume() + } + + /** + * 打开个人中菜单 + */ + fun openMenu() { + binding.mainActivityDrawer.open() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt new file mode 100644 index 00000000..722baf7a --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/activity/map/MainViewModel.kt @@ -0,0 +1,31 @@ +package com.navinfo.omqs.ui.activity.map + +import android.util.Log +import androidx.lifecycle.ViewModel +import com.navinfo.collect.library.map.NIMapController +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.scopes.ActivityRetainedScoped +import javax.inject.Inject +import javax.inject.Singleton + +/** + * 创建Activity全局viewmode + */ +@HiltViewModel +class MainViewModel @Inject constructor( + private val mapController: NIMapController, +) : ViewModel() { + + + /** + * 点击我的位置,回到我的位置 + */ + fun onClickLocationButton() { + mapController.locationLayerHandler.animateToCurrentPosition() + } + + override fun onCleared() { + Log.e("jingo","MainViewModel 被释放了") + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/FirstFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/FirstFragment.kt deleted file mode 100644 index ab8943a7..00000000 --- a/app/src/main/java/com/navinfo/omqs/ui/fragment/FirstFragment.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.navinfo.omqs.ui.fragment - -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.navigation.fragment.findNavController -import com.navinfo.omqs.R -import com.navinfo.omqs.databinding.FragmentFirstBinding - -/** - * A simple [Fragment] subclass as the default destination in the navigation. - */ -class FirstFragment : Fragment() { - - private var _binding: FragmentFirstBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - - _binding = FragmentFirstBinding.inflate(inflater, container, false) - return binding.root - - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.buttonFirst.setOnClickListener { - findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/SecondFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/SecondFragment.kt deleted file mode 100644 index b9a3bffb..00000000 --- a/app/src/main/java/com/navinfo/omqs/ui/fragment/SecondFragment.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.navinfo.omqs.ui.fragment - -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.navigation.fragment.findNavController -import com.navinfo.omqs.R -import com.navinfo.omqs.databinding.FragmentSecondBinding - -/** - * A simple [Fragment] subclass as the second destination in the navigation. - */ -class SecondFragment : Fragment() { - - private var _binding: FragmentSecondBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - - _binding = FragmentSecondBinding.inflate(inflater, container, false) - return binding.root - - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.buttonSecond.setOnClickListener { - findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment) - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt new file mode 100644 index 00000000..4c1e6786 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapAdapter.kt @@ -0,0 +1,25 @@ +package com.navinfo.omqs.ui.fragment.offlinemap + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter + +/** + * 离线地图主页面,viewpage适配器 + */ +class OfflineMapAdapter(activity: FragmentActivity) : + FragmentStateAdapter(activity) { + private val stateFragment = OfflineMapStateListFragment() + private val cityListFragment = OfflineMapCityListFragment() + override fun getItemCount(): Int { + return 2 + } + + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> stateFragment + else -> + cityListFragment + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt new file mode 100644 index 00000000..8f94464c --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListAdapter.kt @@ -0,0 +1,33 @@ +package com.navinfo.omqs.ui.fragment.offlinemap + +import androidx.databinding.ViewDataBinding +import com.navinfo.omqs.R +import com.navinfo.omqs.BR +import com.navinfo.omqs.bean.OfflineMapCityBean +import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding +import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter +import com.navinfo.omqs.ui.other.BaseViewHolder +import javax.inject.Inject + +/** + * 离线地图城市列表 RecyclerView 适配器 + */ + +class OfflineMapCityListAdapter @Inject constructor() : + BaseRecyclerViewAdapter() { + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + var binding: ViewDataBinding = holder.dataBinding + //立刻刷新UI,解决闪烁 +// binding.executePendingBindings() + binding.setVariable(BR.cityBean, data[position]) + (binding as AdapterOfflineMapCityBinding).offlineMapDownloadBtn.setOnClickListener { + + } + + } + + override fun getItemViewType(position: Int): Int { + return R.layout.adapter_offline_map_city + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt new file mode 100644 index 00000000..23b74da5 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListFragment.kt @@ -0,0 +1,49 @@ +package com.navinfo.omqs.ui.fragment.offlinemap + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import com.navinfo.omqs.databinding.FragmentOfflineMapCityListBinding +import dagger.hilt.android.AndroidEntryPoint + +/** + * 离线地图城市列表 + */ +@AndroidEntryPoint +class OfflineMapCityListFragment : Fragment() { + private var _binding: FragmentOfflineMapCityListBinding? = null + private val viewModel by viewModels() + private val binding get() = _binding!! + private val adapter: OfflineMapCityListAdapter by lazy { OfflineMapCityListAdapter() } + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentOfflineMapCityListBinding.inflate(inflater, container, false) + return binding.root + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val layoutManager = LinearLayoutManager(context) + _binding!!.offlineMapCityListRecyclerview.layoutManager = layoutManager + _binding!!.offlineMapCityListRecyclerview.adapter = adapter + viewModel.cityListLiveData.observe(viewLifecycleOwner) { + adapter.refreshData(it) + } + viewModel.getCityList() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + Log.e("jingo","OfflineMapCityListFragment onDestroyView") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt new file mode 100644 index 00000000..4eba17ae --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapCityListViewModel.kt @@ -0,0 +1,49 @@ +package com.navinfo.omqs.ui.fragment.offlinemap + +import android.app.Application +import android.content.Context +import android.widget.Toast +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.navinfo.omqs.http.NetResult +import com.navinfo.omqs.http.NetworkService +import com.navinfo.omqs.bean.OfflineMapCityBean +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.launch +import javax.inject.Inject + +/** + * 离线地图城市列表viewModel + */ +@HiltViewModel +class OfflineMapCityListViewModel @Inject constructor( + private val networkService: NetworkService, + @ApplicationContext val context: Context +) : ViewModel() { + + val cityListLiveData = MutableLiveData>() + + /** + * 去获取离线地图列表 + */ + fun getCityList() { + viewModelScope.launch { + when (val result = networkService.getOfflineMapCityList()) { + is NetResult.Success -> { + cityListLiveData.postValue(result.data!!) + } + is NetResult.Error -> { + Toast.makeText(context, "${result.exception.message}", Toast.LENGTH_SHORT) + .show() + } + is NetResult.Failure -> { + Toast.makeText(context, "${result.code}:${result.msg}", Toast.LENGTH_SHORT) + .show() + } + NetResult.Loading -> {} + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapFragment.kt new file mode 100644 index 00000000..ee60b969 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapFragment.kt @@ -0,0 +1,66 @@ +package com.navinfo.omqs.ui.fragment.offlinemap + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayout.OnTabSelectedListener +import com.google.android.material.tabs.TabLayoutMediator +import com.navinfo.omqs.databinding.FragmentOfflineMapBinding +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * 离线地图 + */ +class OfflineMapFragment : Fragment() { + + private var _binding: FragmentOfflineMapBinding? = null + + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentOfflineMapBinding.inflate(inflater, container, false) + return binding.root + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + //禁止滑动,因为页面在抽屉里,和抽屉的滑动有冲突 + binding.offlineMapViewpager.isUserInputEnabled = false + //创建viewpager2的适配器 + binding.offlineMapViewpager.adapter = activity?.let { OfflineMapAdapter(it) } + //绑定viewpager2与tabLayout + TabLayoutMediator( + binding.offlineMapTabLayout, + binding.offlineMapViewpager + ) { tab, position -> + when (position) { + 0 -> tab.text = "下载管理" + 1 -> tab.text = "城市列表" + } + }.attach() + + //处理返回按钮 + binding.offlineMapBack.setOnClickListener { + findNavController().popBackStack() + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + Log.e("jingo","OfflineMapFragment onDestroyView") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapStateListFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapStateListFragment.kt new file mode 100644 index 00000000..59285dcf --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/offlinemap/OfflineMapStateListFragment.kt @@ -0,0 +1,38 @@ +package com.navinfo.omqs.ui.fragment.offlinemap + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator +import com.navinfo.omqs.databinding.FragmentOfflineMapBinding +import com.navinfo.omqs.databinding.FragmentOfflineMapStateListBinding + + +class OfflineMapStateListFragment : Fragment() { + private var _binding: FragmentOfflineMapStateListBinding? = null + + private val binding get() = _binding!! + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentOfflineMapStateListBinding.inflate(inflater, container, false) + return binding.root + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + Log.e("jingo","OfflineMapStateListFragment onDestroyView") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt new file mode 100644 index 00000000..37d7f1dd --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt @@ -0,0 +1,83 @@ +package com.navinfo.omqs.ui.fragment.personalcenter + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.viewModels +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.get +import androidx.navigation.fragment.findNavController +import com.blankj.utilcode.util.UriUtils +import com.github.k1rakishou.fsaf.FileChooser +import com.github.k1rakishou.fsaf.callback.FSAFActivityCallbacks +import com.github.k1rakishou.fsaf.callback.FileChooserCallback +import com.google.android.material.snackbar.Snackbar +import com.navinfo.omqs.R +import com.navinfo.omqs.databinding.FragmentPersonalCenterBinding + +/** + * 个人中心 + */ +class PersonalCenterFragment : Fragment(), FSAFActivityCallbacks { + + private var _binding: FragmentPersonalCenterBinding? = null + private val binding get() = _binding!! + private val fileChooser by lazy { FileChooser(requireContext()) } + private val viewModel by lazy { viewModels().value } + + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentPersonalCenterBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + Log.e("jingo", "NIMapController PersonalCenterFragment onViewCreated") + binding.root.setNavigationItemSelectedListener { + when (it.itemId) { + R.id.personal_center_menu_offline_map -> + findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) + R.id.personal_center_menu_import_data -> { + // 用户选中导入数据,打开文件选择器,用户选择导入的数据文件目录 + fileChooser.openChooseFileDialog(object: FileChooserCallback() { + override fun onCancel(reason: String) { + } + + override fun onResult(uri: Uri) { + val file = UriUtils.uri2File(uri) + // 开始导入数据 + viewModel.importOmdbData(file) + } + }) + } + } + true + } + + fileChooser.setCallbacks(this@PersonalCenterFragment) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun fsafStartActivityForResult(intent: Intent, requestCode: Int) { + startActivityForResult(intent, requestCode) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + fileChooser.onActivityResult(requestCode, resultCode, data) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt new file mode 100644 index 00000000..a0217e7d --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt @@ -0,0 +1,16 @@ +package com.navinfo.omqs.ui.fragment.personalcenter + +import androidx.lifecycle.ViewModel +import java.io.File + +class PersonalCenterViewModel: ViewModel() { + fun importOmdbData(omdbFile: File) { + // 检查File是否为sqlite数据库 + if (omdbFile == null || omdbFile.exists()) { + throw Exception("文件不存在") + } + if (!omdbFile.name.endsWith(".sqlite") and !omdbFile.name.endsWith("db")) { + throw Exception("文件不存在") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt b/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt new file mode 100644 index 00000000..33747c57 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/other/BaseRecyclerViewAdapter.kt @@ -0,0 +1,32 @@ +package com.navinfo.omqs.ui.other + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import androidx.recyclerview.widget.RecyclerView + +/** + * RecyclerView 适配器基础类 + */ +abstract class BaseRecyclerViewAdapter(var data: List = listOf()) : + RecyclerView.Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { + return BaseViewHolder( + DataBindingUtil.inflate( + LayoutInflater.from(parent.context), + viewType, + parent, + false + ) + ) + } + + override fun getItemCount(): Int { + return data.size + } + + fun refreshData(newData:List){ + this.data = newData + this.notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt b/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt new file mode 100644 index 00000000..a06b61ca --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/other/BaseViewHolder.kt @@ -0,0 +1,11 @@ +package com.navinfo.omqs.ui.other + +import androidx.databinding.ViewDataBinding +import androidx.recyclerview.widget.RecyclerView + +/** + * dataBinding viewHolder 基类 + */ +open class BaseViewHolder(var dataBinding: ViewDataBinding) : + RecyclerView.ViewHolder(dataBinding.root) { +} \ No newline at end of file diff --git a/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt b/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt new file mode 100644 index 00000000..0f1180a6 --- /dev/null +++ b/app/src/main/java/com/navinfo/omqs/ui/widget/MyProgressBar.kt @@ -0,0 +1,64 @@ +package com.navinfo.omqs.ui.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.opengl.ETC1.getHeight +import android.opengl.ETC1.getWidth +import android.util.AttributeSet +import android.widget.ProgressBar + + +/** + * 带文字提示的进度条 + */ +class MyProgressBar : ProgressBar { + private lateinit var mPaint: Paint + private var text: String = "" + private var rate = 0f + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + initView() + } + + constructor(context: Context?) : super(context) { + initView() + } + + private fun initView() { + mPaint = Paint() + mPaint.isAntiAlias = true + mPaint.color = Color.BLUE + } + + @Synchronized + override fun setProgress(progress: Int) { + setText(progress) + super.setProgress(progress) + } + + private fun setText(progress: Int) { + rate = progress * 1.0f / this.getMax() + val i = (rate * 100).toInt() + text = "$i%" + } + + @Synchronized + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + val rect = Rect() + mPaint.getTextBounds(text, 0, text.length, rect) + // int x = (getWidth()/2) - rect.centerX(); + // int y = (getHeight()/2) - rect.centerY(); + var x = (width * rate).toInt() + if (x == width) { + // 如果为百分之百则在左边绘制。 + x = width - rect.right + } + val y: Int = 0 - rect.top + mPaint.textSize = 22f + canvas.drawText(text, x.toFloat(), y.toFloat(), mPaint) + } +} \ No newline at end of file diff --git a/app/src/main/res/color/btn_blue_white.xml b/app/src/main/res/color/btn_blue_white.xml new file mode 100644 index 00000000..b8464420 --- /dev/null +++ b/app/src/main/res/color/btn_blue_white.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/baseline_map_24.xml b/app/src/main/res/drawable/baseline_map_24.xml new file mode 100644 index 00000000..04011a73 --- /dev/null +++ b/app/src/main/res/drawable/baseline_map_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/btn_back_xml.xml b/app/src/main/res/drawable/btn_back_xml.xml new file mode 100644 index 00000000..7368f389 --- /dev/null +++ b/app/src/main/res/drawable/btn_back_xml.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/fm_card_map_btn_bg_line.xml b/app/src/main/res/drawable/fm_card_map_btn_bg_line.xml new file mode 100644 index 00000000..518567e4 --- /dev/null +++ b/app/src/main/res/drawable/fm_card_map_btn_bg_line.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/fm_card_map_down_status_bg.xml b/app/src/main/res/drawable/fm_card_map_down_status_bg.xml new file mode 100644 index 00000000..397d7c2b --- /dev/null +++ b/app/src/main/res/drawable/fm_card_map_down_status_bg.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_import_export_24.xml b/app/src/main/res/drawable/ic_baseline_import_export_24.xml new file mode 100644 index 00000000..0c6ecd72 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_import_export_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index f42901d3..d1405c56 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -2,17 +2,17 @@ + tools:context=".ui.activity.login.LoginActivity"> + type="com.navinfo.omqs.ui.activity.login.LoginActivity" /> + type="com.navinfo.omqs.ui.activity.login.LoginViewModel" /> + tools:context=".ui.activity.map.MainActivity"> + type="com.navinfo.omqs.ui.activity.map.MainActivity" /> + type="com.navinfo.omqs.ui.activity.map.MainViewModel" /> + app:viewModel="@{viewModel}" /> - + app:navGraph="@navigation/nav_graph" /> \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_offline_map_city.xml b/app/src/main/res/layout/adapter_offline_map_city.xml new file mode 100644 index 00000000..3bad8ddd --- /dev/null +++ b/app/src/main/res/layout/adapter_offline_map_city.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml deleted file mode 100644 index e416e1c1..00000000 --- a/app/src/main/res/layout/content_main.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_first.xml b/app/src/main/res/layout/fragment_first.xml deleted file mode 100644 index d7283fad..00000000 --- a/app/src/main/res/layout/fragment_first.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - -