fix: 增加Message禁用功能

This commit is contained in:
xiaoyan 2023-01-10 17:41:42 +08:00
commit cf72615d96
45 changed files with 992 additions and 768 deletions

View File

@ -2,9 +2,10 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-android'
id 'kotlin-parcelize'
id 'kotlin-parcelize' //
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
id 'dagger.hilt.android.plugin' //hilt
id "com.google.protobuf" version "0.8.17" //Proto DataStore
// id 'com.google.dagger.hilt.android'
}
@ -82,7 +83,7 @@ dependencies {
//room
implementation 'com.tencent.wcdb:room:1.1-19' // room-runtime wcdb-android
api 'androidx.sqlite:sqlite:2.2.0'
implementation 'androidx.sqlite:sqlite-ktx:2.2.0'
implementation 'androidx.room:room-runtime:2.4.3'
implementation 'androidx.room:room-ktx:2.4.3'
annotationProcessor 'androidx.room:room-compiler:2.4.3'
@ -90,13 +91,12 @@ dependencies {
kapt 'android.arch.persistence.room:compiler:1.1.1'// compiler room
kapt 'androidx.room:room-compiler:2.4.3'
kapt 'androidx.room:room-ktx:2.4.3'
//
implementation "androidx.room:room-paging:2.4.3"
implementation "androidx.paging:paging-runtime-ktx:3.1.1"
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
// implementation "android.arch.lifecycle:extensions:1.1.1"
// annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
implementation 'com.tencent.wcdb:wcdb-android:1.1-19'
@ -108,29 +108,20 @@ dependencies {
//
implementation 'com.yanzhenjie.recyclerview:x:1.3.2'
// implementation 'androidx.appcompat:appcompat:1.5.1'
// // Koin
// implementation("io.insert-koin:koin-android:3.3.2")
// implementation("io.insert-koin:koin-core:3.3.2")
//
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
// const val chuck = "com.readystatesoftware.chuck:library:${Versions.chuck}"
// const val chuckNoOp = "com.readystatesoftware.chuck:library-no-op:${Versions.chuck}"
implementation("com.squareup.okhttp3:okhttp:4.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")
implementation("com.google.code.gson:gson:2.8.6")
//hilt
implementation "com.google.dagger:hilt-android:2.41"
kapt "com.google.dagger:hilt-compiler:2.41"
// implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
// androidTestImplementation "com.google.dagger:hilt-android-testing:2.41"
// kaptAndroidTest "com.google.dagger:hilt-android-compiler:2.41"
// https://github.com/nhaarman/supertooltips
implementation 'com.nhaarman.supertooltips:library:3.0.0'
@ -155,6 +146,30 @@ dependencies {
// https://github.com/XiaoGe-1996/ImageViewer
implementation 'com.github.XiaoGe-1996:ImageViewer:v1.0.0'
implementation 'com.github.majidarabi:AndroidFilePicker:0.2.1'
// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0"
// Proto DataStore
implementation "androidx.datastore:datastore-core:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
}
//Proto DataStore
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.14.0"
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
kapt {

View File

@ -24,14 +24,15 @@
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/volvo_logo_small"
android:requestLegacyExternalStorage="true"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.NavinfoVolvo"
android:usesCleartextTraffic="true">
<activity
android:name=".ui.message.MessageActivity"
android:configChanges="orientation"
android:exported="false"
android:label="@string/title_activity_second"
android:screenOrientation="portrait"
@ -54,6 +55,8 @@
<meta-data
android:name="android.app.lib_name"
android:value="" />
<meta-data android:name="ScopedStorage" android:value="true" />
<meta-data
android:name="ScopedStorage"
android:value="true" />
</application>
</manifest>

View File

@ -9,6 +9,8 @@ class Constant {
*/
const val SERVER_ADDRESS = "http://ec2-52-81-73-5.cn-north-1.compute.amazonaws.com.cn:8088/"
val DEBUG = Boolean.parseBoolean("true")
const val message_status_late = "预约,待发送"
}
}

View File

@ -1,8 +1,39 @@
package com.navinfo.volvo
import android.app.Activity
import android.app.Application
import android.content.pm.ActivityInfo
import android.os.Bundle
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
open class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
}
override fun onActivityDestroyed(activity: Activity) {
}
})
}
}

View File

@ -1,191 +0,0 @@
//package com.navinfo.volvo.database;
//
//
//import android.content.Context;
//
//import androidx.annotation.NonNull;
//import androidx.room.Database;
//import androidx.room.Room;
//import androidx.room.RoomDatabase;
//import androidx.sqlite.db.SupportSQLiteDatabase;
//import androidx.sqlite.db.SupportSQLiteOpenHelper;
//
//import com.navinfo.volvo.database.dao.MessageDao;
//import com.navinfo.volvo.database.dao.UserDao;
//import com.navinfo.volvo.database.entity.Message;
//import com.navinfo.volvo.database.entity.Attachment;
//import com.navinfo.volvo.database.entity.User;
//import com.tencent.wcdb.database.SQLiteCipherSpec;
//import com.tencent.wcdb.database.SQLiteDatabase;
//
//import com.tencent.wcdb.room.db.WCDBOpenHelperFactory;
//
//import android.os.AsyncTask;
//import android.util.Log;
//
//import com.tencent.wcdb.repair.BackupKit;
//import com.tencent.wcdb.repair.RecoverKit;
//import com.tencent.wcdb.room.db.WCDBDatabase;
//
//@Database(entities = {Message.class, Attachment.class, User.class}, version = 1, exportSchema = false)
//public abstract class MapLifeDataBase extends RoomDatabase {
// // marking the instance as volatile to ensure atomic access to the variable
// /**
// * 数据库单例对象
// */
// private static volatile MapLifeDataBase INSTANCE;
//
// /**
// * 要素数据库类
// */
// public abstract MessageDao getMessageDao();
//
// public abstract UserDao getUserDao();
//
// /**
// * 数据库秘钥
// */
// private final static String DB_PASSWORD = "123456";
//
// public static MapLifeDataBase getDatabase(final Context context, final String name) {
// if (INSTANCE == null) {
// synchronized (MapLifeDataBase.class) {
// if (INSTANCE == null) {
// // [WCDB] To use Room library with WCDB, pass a WCDBOpenHelper factory object
// // to the database builder with .openHelperFactory(...). In the factory object,
// // you can specify passphrase and cipher options to open or create encrypted
// // database, as well as optimization options like asynchronous checkpoint.
// SQLiteCipherSpec cipherSpec = new SQLiteCipherSpec()
// .setPageSize(1024)
// .setSQLCipherVersion(3);
// WCDBOpenHelperFactory factory = new WCDBOpenHelperFactory()
// .passphrase(DB_PASSWORD.getBytes()) // passphrase to the database, remove this line for plain-text
// .cipherSpec(cipherSpec) // cipher to use, remove for default settings
// .writeAheadLoggingEnabled(true) // enable WAL mode, remove if not needed
// .asyncCheckpointEnabled(true); // enable asynchronous checkpoint, remove if not needed
//
// INSTANCE = Room.databaseBuilder(context.getApplicationContext(), MapLifeDataBase.class, name)
//
// // [WCDB] Specify open helper to use WCDB database implementation instead
// // of the Android framework.
// .openHelperFactory((SupportSQLiteOpenHelper.Factory) factory)
//
// // Wipes and rebuilds instead of migrating if no Migration object.
// // Migration is not part of this codelab.
// .fallbackToDestructiveMigration().addCallback(sRoomDatabaseCallback).build();
// }
// }
// }
// return INSTANCE;
// }
//
// /**
// * Override the onOpen method to populate the database.
// * For this sample, we clear the database every time it is created or opened.
// * <p>
// * If you want to populate the database only when the database is created for the 1st time,
// * override RoomDatabase.Callback()#onCreate
// */
// private static Callback sRoomDatabaseCallback = new Callback() {
//
// @Override
// public void onOpen(@NonNull SupportSQLiteDatabase db) {
// super.onOpen(db);
// // If you want to keep the data through app restarts,
// // comment out the following line.
// new PopulateDbAsync(INSTANCE).execute();
// }
// };
//
// /**
// * Populate the database in the background.
// * If you want to start with more words, just add them.
// */
// private static class PopulateDbAsync extends AsyncTask<Void, Void, Void> {
//
// private final MessageDao messageDao;
//
// PopulateDbAsync(MapLifeDataBase db) {
// messageDao = db.getMessageDao();
// }
//
// @Override
// protected Void doInBackground(final Void... params) {
// // Start the app with a clean database every time.
// // Not needed if you only populate on creation.
// //mDao.deleteAll();
// Log.e("qj", "doInBackground");
// return null;
// }
// }
//
// /**
// * 数据恢复
// */
// protected boolean recoverData() {
// if (INSTANCE != null) {
// SQLiteDatabase sqlite = ((WCDBDatabase) INSTANCE.getOpenHelper().getWritableDatabase()).getInnerDatabase();
// RecoverKit recover = new RecoverKit(sqlite, // 要恢复到的目标 DB
// sqlite.getPath() + "-backup", // 备份文件
// DB_PASSWORD.getBytes() // 加密备份文件的密钥 DB 密钥
// );
// int result = recover.run(false); // fatal 参数传 false 表示遇到错误忽略并继续
// // 若传 true 遇到错误则中止并返回 FAILED
// switch (result) {
// case RecoverKit.RESULT_OK:
// /* 成功 */
// Log.e("qj", "sRoomDatabaseCallback==RecoverKit成功");
// return true;
// case RecoverKit.RESULT_CANCELED: /* 取消操作 */
// Log.e("qj", "sRoomDatabaseCallback==RecoverKit取消操作");
// break;
// case RecoverKit.RESULT_FAILED: /* 失败 */
// Log.e("qj", "sRoomDatabaseCallback==RecoverKit失败");
// break;
//
// }
//
// recover.release();
// }
//
// return false;
// }
//
// /**
// * 备份数据
// */
// protected boolean backup() {
// Log.e("qj", "sRoomDatabaseCallback===backup==start");
// if (INSTANCE != null) {
// //备份文件
// SQLiteDatabase sqlite = ((WCDBDatabase) INSTANCE.getOpenHelper().getWritableDatabase()).getInnerDatabase();
// BackupKit backup = new BackupKit(sqlite, // 要备份的 DB
// sqlite.getPath() + "-backup", // 备份文件
// "123456".getBytes(), // 加密备份文件的密钥 DB 密钥
// 0, null);
// int result = backup.run();
// switch (result) {
// case BackupKit.RESULT_OK:
// /* 成功 */
// Log.e("qj", "sRoomDatabaseCallback==成功");
// return true;
// case BackupKit.RESULT_CANCELED:
// /* 取消操作 */
// Log.e("qj", "sRoomDatabaseCallback==取消操作");
// break;
// case BackupKit.RESULT_FAILED:
// /* 失败 */
// Log.e("qj", "sRoomDatabaseCallback==失败");
// break;
// }
//
// backup.release();
// }
// Log.e("qj", "sRoomDatabaseCallback===backup==end");
// return false;
// }
//
// protected void release() {
// INSTANCE = null;
// }
//}

View File

@ -1,21 +1,28 @@
package com.navinfo.volvo.database.dao
import android.util.Log
import androidx.paging.PagingSource
import androidx.room.*
import com.navinfo.volvo.Constant
import com.navinfo.volvo.database.entity.GreetingMessage
import kotlinx.coroutines.flow.Flow
@Dao
interface GreetingMessageDao {
@Query("DELETE from GreetingMessage WHERE id=:id")
suspend fun deleteById(id: Long)
@Insert
fun insert(message: GreetingMessage): Long
suspend fun insert(message: GreetingMessage): Long
@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(message: GreetingMessage)
suspend fun update(message: GreetingMessage)
@Query("SELECT count(id) FROM GreetingMessage WHERE read = 0")
/**
* 未读消息统计
*/
@Query("SELECT count(id) FROM GreetingMessage WHERE status = '${Constant.message_status_late}'")
fun countUnreadByFlow(): Flow<Long>
/**
@ -27,18 +34,29 @@ interface GreetingMessageDao {
/**
* 检查某条数据是否存在
*/
@Query("SELECT id From GreetingMessage WHERE id = :id LIMIT 1")
fun getMessageId(id: Long): Long
@Query("SELECT uuid From GreetingMessage WHERE id = :id LIMIT 1")
suspend fun getMessageId(id: Long): Long?
/**
*
*/
@Transaction
suspend fun insertOrUpdate(list: List<GreetingMessage>) {
for (message in list) {
val id = getMessageId(message.id)
if (id == 0L) {
insert(message)
}else{
Log.e("jingo", "insertOrUpdate ${message.id}")
val uuid = getMessageId(message.id)
Log.e("jingo", "insertOrUpdate $uuid")
if (uuid == null || uuid == 0L) {
Log.e("jingo", "insertOrUpdate start ")
val l = insert(message)
Log.e("jingo", "insertOrUpdate $l ")
} else {
message.uuid = uuid
update(message)
}
Log.e("jingo", "insertOrUpdate end")
}
}
}

View File

@ -22,6 +22,7 @@ import javax.inject.Singleton
@Module
class NetworkUtilModule {
@Provides
@Singleton
fun provideContext(application: Application): Context {

View File

@ -1,7 +1,13 @@
package com.navinfo.volvo.di.module
import android.content.Context
import com.navinfo.volvo.util.SharedPreferenceHelper
import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.core.Serializer
import androidx.datastore.dataStoreFile
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import com.navinfo.volvo.model.proto.LoginUser
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -14,7 +20,12 @@ class UtilModule {
@Provides
@Singleton
fun provideSharedPreferencesHelper(context: Context): SharedPreferenceHelper {
return SharedPreferenceHelper.getInstance(context)
}
fun provideLoginUserDataStore(
context: Context,
serializer: Serializer<LoginUser>
): DataStore<LoginUser> = DataStoreFactory.create(
serializer = serializer,
produceFile = { context.dataStoreFile("login_user") }
)
}

View File

@ -0,0 +1,22 @@
package com.navinfo.volvo.di.module
import androidx.datastore.core.Serializer
import com.navinfo.volvo.model.proto.LoginUser
import com.navinfo.volvo.model.proto.LoginUserSerializer
import com.navinfo.volvo.repository.preferences.PreferencesRepository
import com.navinfo.volvo.repository.preferences.PreferencesRepositoryImp
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
abstract class UtilRepositoryModule {
@Binds
abstract fun providePreferencesRepository(preferencesRepositoryImp: PreferencesRepositoryImp): PreferencesRepository
@Binds
abstract fun userLocalSerializer(impl: LoginUserSerializer): Serializer<LoginUser>
}

View File

@ -1,35 +1,35 @@
package com.navinfo.volvo.di.module
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
/**
* Factory for all ViewModels.
* reference : https://github.com/googlesamples/android-architecture-components
*/
@Singleton
class ViewModelFactory @Inject constructor(
private val viewModelMap: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var viewModel = viewModelMap[modelClass]
if (viewModel == null) {
for (entry in viewModelMap) {
if (modelClass.isAssignableFrom(entry.key)) {
viewModel = entry.value
break
}
}
}
if (viewModel == null) throw IllegalArgumentException("Unknown model class $modelClass")
return viewModel.get() as T
}
}
//package com.navinfo.volvo.di.module
//
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.ViewModelProvider
//import javax.inject.Inject
//import javax.inject.Provider
//import javax.inject.Singleton
//
//
///**
// * Factory for all ViewModels.
// * reference : https://github.com/googlesamples/android-architecture-components
// */
//@Singleton
//class ViewModelFactory @Inject constructor(
// private val viewModelMap: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
//) : ViewModelProvider.Factory {
//
// @Suppress("UNCHECKED_CAST")
// override fun <T : ViewModel> create(modelClass: Class<T>): T {
// var viewModel = viewModelMap[modelClass]
//
// if (viewModel == null) {
// for (entry in viewModelMap) {
// if (modelClass.isAssignableFrom(entry.key)) {
// viewModel = entry.value
// break
// }
// }
// }
//
// if (viewModel == null) throw IllegalArgumentException("Unknown model class $modelClass")
// return viewModel.get() as T
// }
//}

View File

@ -1,44 +1,44 @@
package com.navinfo.volvo.di.module
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.navinfo.volvo.di.key.ViewModelKey
import com.navinfo.volvo.ui.MainActivityViewModel
import com.navinfo.volvo.ui.fragments.home.HomeViewModel
import com.navinfo.volvo.ui.fragments.login.LoginViewModel
import com.navinfo.volvo.ui.fragments.message.ObtainMessageViewModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoMap
@InstallIn(SingletonComponent::class)
@Module
abstract class ViewModelModule {
@Binds
abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
@IntoMap
@Binds
@ViewModelKey(MainActivityViewModel::class)
abstract fun bindMainViewModel(viewModel: MainActivityViewModel): ViewModel
@IntoMap
@Binds
@ViewModelKey(LoginViewModel::class)
abstract fun bindLoginFragmentViewModel(viewModel: LoginViewModel): ViewModel
@IntoMap
@Binds
@ViewModelKey(HomeViewModel::class)
abstract fun bindMessageFragmentViewModel(viewModel: HomeViewModel): ViewModel
@IntoMap
@Binds
@ViewModelKey(ObtainMessageViewModel::class)
abstract fun bindObtainMessageFragmentViewModel(viewModel: ObtainMessageViewModel): ViewModel
}
//package com.navinfo.volvo.di.module
//
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.ViewModelProvider
//import com.navinfo.volvo.di.key.ViewModelKey
//import com.navinfo.volvo.ui.MainActivityViewModel
//import com.navinfo.volvo.ui.fragments.home.HomeViewModel
//import com.navinfo.volvo.ui.fragments.login.LoginViewModel
//import com.navinfo.volvo.ui.fragments.message.ObtainMessageViewModel
//import dagger.Binds
//import dagger.Module
//import dagger.hilt.InstallIn
//import dagger.hilt.components.SingletonComponent
//import dagger.multibindings.IntoMap
//
//@InstallIn(SingletonComponent::class)
//@Module
//abstract class ViewModelModule {
//
// @Binds
// abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
//
// @IntoMap
// @Binds
// @ViewModelKey(MainActivityViewModel::class)
// abstract fun bindMainViewModel(viewModel: MainActivityViewModel): ViewModel
//
// @IntoMap
// @Binds
// @ViewModelKey(LoginViewModel::class)
// abstract fun bindLoginFragmentViewModel(viewModel: LoginViewModel): ViewModel
//
// @IntoMap
// @Binds
// @ViewModelKey(HomeViewModel::class)
// abstract fun bindMessageFragmentViewModel(viewModel: HomeViewModel): ViewModel
//
// @IntoMap
// @Binds
// @ViewModelKey(ObtainMessageViewModel::class)
// abstract fun bindObtainMessageFragmentViewModel(viewModel: ObtainMessageViewModel): ViewModel
//
//
//}

View File

@ -1,9 +0,0 @@
package com.navinfo.volvo.model
/**
* 登录用户信息
*/
data class LoginUser(
var name: String,
var password: String
)

View File

@ -1,8 +1,8 @@
package com.navinfo.volvo.model.messagelist
package com.navinfo.volvo.model.network
import com.navinfo.volvo.database.entity.GreetingMessage
data class NetworkMessageListResponse(
val total: Int,
val rows: List<GreetingMessage>
val rows: List<GreetingMessage>?
)

View File

@ -1,4 +1,4 @@
package com.navinfo.volvo.model.messagelist
package com.navinfo.volvo.model.network
data class NetworkMessageListPost(
val name: String,//问候名称,非必填项
@ -12,4 +12,8 @@ data class NetworkMessageListPost(
constructor(who: String, toWho: String) : this("", who, toWho, "", "", "10", "1") {
}
}
}
data class NetworkDeleteMessagePost(
val id: Long
)

View File

@ -0,0 +1,22 @@
package com.navinfo.volvo.model.proto
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
import androidx.datastore.preferences.protobuf.InvalidProtocolBufferException
import java.io.InputStream
import java.io.OutputStream
import javax.inject.Inject
class LoginUserSerializer @Inject constructor(): Serializer<LoginUser> {
override val defaultValue: LoginUser = LoginUser.getDefaultInstance()
override suspend fun readFrom(input: InputStream): LoginUser {
try {
return LoginUser.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(t: LoginUser, output: OutputStream) = t.writeTo(output)
}

View File

@ -11,12 +11,14 @@ import javax.inject.Inject
class DatabaseRepositoryImp @Inject constructor(
private val messageDao: GreetingMessageDao,
private val database: AppDatabase
) : DatabaseRepository {
companion object {
const val PAGE_SIZE = 20
}
/**
* 分页加载消息
*/
override fun getMessageByPaging(): Flow<PagingData<GreetingMessage>> {
return Pager(PagingConfig(PAGE_SIZE)) {
messageDao.findAllByDataSource()

View File

@ -1,13 +1,22 @@
package com.navinfo.volvo.repository.network
import com.navinfo.volvo.http.DefaultResponse
import com.navinfo.volvo.model.messagelist.NetworkMessageListPost
import com.navinfo.volvo.model.messagelist.NetworkMessageListResponse
import com.navinfo.volvo.model.network.NetworkDeleteMessagePost
import com.navinfo.volvo.model.network.NetworkMessageListPost
import com.navinfo.volvo.model.network.NetworkMessageListResponse
import com.navinfo.volvo.util.NetResult
/**
* 网络访问接口
*/
interface NetworkRepository {
suspend fun getCardList(message: NetworkMessageListPost): NetResult<DefaultResponse<NetworkMessageListResponse>>
interface NetworkRepository {
/**
* 获取问候列表
*/
suspend fun getMessageList(message: NetworkMessageListPost): NetResult<DefaultResponse<NetworkMessageListResponse>>
/**
*删除问候
*/
suspend fun deleteMessage(message: NetworkDeleteMessagePost): NetResult<DefaultResponse<*>>
}

View File

@ -1,11 +1,12 @@
package com.navinfo.volvo.repository.network
import com.google.gson.Gson
import com.navinfo.volvo.database.dao.GreetingMessageDao
import com.navinfo.volvo.di.scope.IoDispatcher
import com.navinfo.volvo.http.DefaultResponse
import com.navinfo.volvo.model.messagelist.NetworkMessageListPost
import com.navinfo.volvo.model.messagelist.NetworkMessageListResponse
import com.navinfo.volvo.tools.GsonUtil
import com.navinfo.volvo.model.network.NetworkDeleteMessagePost
import com.navinfo.volvo.model.network.NetworkMessageListPost
import com.navinfo.volvo.model.network.NetworkMessageListResponse
import com.navinfo.volvo.util.NetResult
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@ -16,22 +17,47 @@ import javax.inject.Inject
class NetworkRepositoryImp @Inject constructor(
private val netWorkService: NetworkService,
private val messageDao: GreetingMessageDao,
private val gson: Gson,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
) : NetworkRepository {
override suspend fun getCardList(message: NetworkMessageListPost): NetResult<DefaultResponse<NetworkMessageListResponse>> =
/**
* 获取问候列表
*/
override suspend fun getMessageList(message: NetworkMessageListPost): NetResult<DefaultResponse<NetworkMessageListResponse>> =
withContext(ioDispatcher) {
return@withContext try {
val stringBody = GsonUtil.getInstance().toJson(message)
val stringBody = gson.toJson(message)
.toRequestBody("application/json;charset=utf-8".toMediaType())
val result = netWorkService.queryCardListByApp(stringBody)
val result = netWorkService.queryMessageListByApp(stringBody)
if (result.isSuccessful) {
val body = result.body()
if(body!!.data != null && body.data!!.rows != null){
messageDao.insertOrUpdate(body.data!!.rows)
if (result.body()!!.code == 200) {
NetResult.Success(result.body())
} else {
NetResult.Failure(result.body()!!.code, result.body()!!.msg)
}
} else {
NetResult.Success(null)
}
} catch (e: Exception) {
NetResult.Error(e)
}
}
/**
* 删除问候
*/
override suspend fun deleteMessage(message: NetworkDeleteMessagePost): NetResult<DefaultResponse<*>> =
withContext(ioDispatcher) {
return@withContext try {
val stringBody = gson.toJson(message)
.toRequestBody("application/json;charset=utf-8".toMediaType())
val result = netWorkService.deleteMessage(stringBody)
if (result.isSuccessful) {
if (result.body()!!.code == 200) {
NetResult.Success(result.body())
} else {
NetResult.Failure(result.body()!!.code, result.body()!!.msg)
}
NetResult.Success(body)
} else {
NetResult.Success(null)
}

View File

@ -1,13 +1,23 @@
package com.navinfo.volvo.repository.network
import com.navinfo.volvo.http.DefaultResponse
import com.navinfo.volvo.model.messagelist.NetworkMessageListResponse
import com.navinfo.volvo.model.network.NetworkMessageListResponse
import okhttp3.RequestBody
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST
interface NetworkService {
/**
* 获取问候列表
*/
@POST("/navi/cardDelivery/queryCardListByApp")
suspend fun queryCardListByApp(@Body body: RequestBody): Response<DefaultResponse<NetworkMessageListResponse>>
suspend fun queryMessageListByApp(@Body body: RequestBody): Response<DefaultResponse<NetworkMessageListResponse>>
/**
* 删除问候
*/
@POST("/navi/cardDelivery/deleteCardByApp")
suspend fun deleteMessage(@Body body: RequestBody): Response<DefaultResponse<*>>
}

View File

@ -0,0 +1,17 @@
package com.navinfo.volvo.repository.preferences
import com.navinfo.volvo.model.proto.LoginUser
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
/**
* 数据库操作接口
*/
interface PreferencesRepository {
suspend fun saveLoginUser(id: String, name: String, password: String)
fun loginUser(): Flow<LoginUser?>
suspend fun saveString(key: String, content: String)
suspend fun getString(key: String): Flow<String?>
suspend fun saveInt(key: String, content: Int)
suspend fun getInt(key: String): Flow<Int?>
}

View File

@ -0,0 +1,61 @@
package com.navinfo.volvo.repository.preferences
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.lifecycle.viewModelScope
import com.navinfo.volvo.model.proto.LoginUser
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject
const val DataStore_NAME = "ShardPreferences"
val Context.datastore: DataStore<Preferences> by preferencesDataStore(name = DataStore_NAME)
class PreferencesRepositoryImp @Inject constructor(
private val context: Context,
private val loginUser: DataStore<LoginUser>
) : PreferencesRepository {
companion object {
val NAME = stringPreferencesKey("NAME")
val PHONE_NUMBER = stringPreferencesKey("PHONE")
val address = stringPreferencesKey("ADDRESS")
}
override suspend fun saveLoginUser(id: String, name: String, password: String) {
loginUser.updateData { preference ->
preference.toBuilder().setUsername(name).setPassword(password).build()
}
}
override suspend fun saveString(key: String, content: String) {
context.datastore.edit {
it[stringPreferencesKey(key)] = content
}
}
override suspend fun getString(key: String): Flow<String?> = context.datastore.data.map {
it[stringPreferencesKey(key)]
}
override suspend fun saveInt(key: String, content: Int) {
context.datastore.edit {
it[intPreferencesKey(key)] = content
}
}
override suspend fun getInt(key: String): Flow<Int?> = context.datastore.data.map {
it[intPreferencesKey(key)]
}
override fun loginUser(): Flow<LoginUser?> = loginUser.data
}

View File

@ -6,6 +6,6 @@ import androidx.lifecycle.ViewModelProvider
import javax.inject.Inject
abstract class BaseActivity : AppCompatActivity() {
@Inject
lateinit var viewModelFactoryProvider: ViewModelProvider.Factory
// @Inject
// lateinit var viewModelFactoryProvider: ViewModelProvider.Factory
}

View File

@ -1,8 +1,8 @@
package com.navinfo.volvo.ui
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.widget.Toast
import androidx.activity.viewModels
@ -11,6 +11,9 @@ import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import androidx.transition.Slide
import androidx.transition.Transition
import androidx.transition.TransitionManager
import com.easytools.tools.FileUtils
import com.elvishew.xlog.BuildConfig
import com.elvishew.xlog.LogConfiguration
@ -30,16 +33,17 @@ import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.navinfo.volvo.R
import com.navinfo.volvo.databinding.ActivityMainBinding
import com.navinfo.volvo.ui.message.MessageActivity
import com.navinfo.volvo.utils.SystemConstant
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint
class MainActivity : BaseActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel by viewModels<MainActivityViewModel> { viewModelFactoryProvider }
private val viewModel by viewModels<MainActivityViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -47,7 +51,6 @@ class MainActivity : BaseActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
XXPermissions.with(this)
// 申请单个权限
.permission(Permission.WRITE_EXTERNAL_STORAGE)
@ -98,28 +101,29 @@ class MainActivity : BaseActivity() {
lifecycleScope.launch {
viewModel.getUnreadCount().collect {
runOnUiThread {
if (it == 0L) {
navView.removeBadge(R.id.navigation_home)
} else {
var badge = navView.getOrCreateBadge(R.id.navigation_home);
badge.number = it.toInt()
}
if (it == 0L) {
navView.removeBadge(R.id.navigation_home)
} else {
var badge = navView.getOrCreateBadge(R.id.navigation_home);
badge.number = it.toInt()
}
}
}
navController.addOnDestinationChangedListener { controller, destination, arguments ->
if (destination.id == R.id.navigation_home
|| destination.id == R.id.navigation_dashboard
|| destination.id == R.id.navigation_notifications
) {
if (destination.id == R.id.navigation_home || destination.id == R.id.navigation_dashboard || destination.id == R.id.navigation_notifications) {
runOnUiThread {
val transition: Transition = Slide(Gravity.BOTTOM)
transition.duration = 300;
TransitionManager.beginDelayedTransition(binding.root, transition);
navView.visibility = View.VISIBLE
newMessageView.visibility = View.VISIBLE
}
} else {
runOnUiThread {
val transition: Transition = Slide(Gravity.BOTTOM)
transition.duration = 300;
TransitionManager.beginDelayedTransition(binding.root, transition);
navView.visibility = View.GONE
newMessageView.visibility = View.GONE
}
@ -132,6 +136,7 @@ class MainActivity : BaseActivity() {
}
}
override fun onSupportNavigateUp() =
findNavController(R.id.nav_host_fragment_activity_main).navigateUp()
@ -151,13 +156,10 @@ class MainActivity : BaseActivity() {
}
fun xLogInit(logFolder: String) {
val config = LogConfiguration.Builder()
.logLevel(
if (BuildConfig.DEBUG)
LogLevel.ALL // 指定日志级别,低于该级别的日志将不会被打印,默认为 LogLevel.ALL
else LogLevel.NONE
)
.tag("Volvo") // 指定 TAG默认为 "X-LOG"
val config = LogConfiguration.Builder().logLevel(
if (BuildConfig.DEBUG) LogLevel.ALL // 指定日志级别,低于该级别的日志将不会被打印,默认为 LogLevel.ALL
else LogLevel.NONE
).tag("Volvo") // 指定 TAG默认为 "X-LOG"
.enableThreadInfo() // 允许打印线程信息,默认禁止
.enableStackTrace(2) // 允许打印深度为 2 的调用栈信息,默认禁止
.enableBorder() // 允许打印日志边框,默认禁止
@ -165,8 +167,7 @@ class MainActivity : BaseActivity() {
BlacklistTagsFilterInterceptor( // 添加黑名单 TAG 过滤器
"blacklist1", "blacklist2", "blacklist3"
)
)
.build()
).build()
val androidPrinter: Printer = AndroidPrinter(true) // 通过 android.util.Log 打印日志的打印器
@ -181,8 +182,7 @@ class MainActivity : BaseActivity() {
XLog.init( // 初始化 XLog
config, // 指定日志配置,如果不指定,会默认使用 new LogConfiguration.Builder().build()
androidPrinter, // 添加任意多的打印器。如果没有添加任何打印器,会默认使用 AndroidPrinter(Android)/ConsolePrinter(java)
consolePrinter,
filePrinter
consolePrinter, filePrinter
)
}
@ -190,14 +190,11 @@ class MainActivity : BaseActivity() {
fun showRationaleForSDCard(permissions: MutableList<String>) {
// showRationaleDialog(R.string.permission_camera_rationale, request)
// Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show()
MaterialAlertDialogBuilder(this)
.setTitle("提示")
.setMessage("当前操作需要您授权读写SD卡权限")
MaterialAlertDialogBuilder(this).setTitle("提示").setMessage("当前操作需要您授权读写SD卡权限")
.setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
dialogInterface.dismiss()
XXPermissions.startPermissionActivity(this@MainActivity, permissions)
})
.show()
}).show()
}
// @OnPermissionDenied(Manifest.permission.MANAGE_EXTERNAL_STORAGE)

View File

@ -4,9 +4,12 @@ import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import com.navinfo.volvo.database.dao.GreetingMessageDao
import com.navinfo.volvo.database.entity.GreetingMessage
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton
@HiltViewModel
class MainActivityViewModel @Inject constructor(
private val messageDao: GreetingMessageDao,
) : ViewModel() {

View File

@ -5,6 +5,6 @@ import androidx.lifecycle.ViewModelProvider
import javax.inject.Inject
abstract class BaseFragment : Fragment() {
@Inject
lateinit var viewModelFactoryProvider: ViewModelProvider.Factory
// @Inject
// lateinit var viewModelFactoryProvider: ViewModelProvider.Factory
}

View File

@ -19,23 +19,11 @@ class HomeAdapter(fragment: Fragment) :
PagingDataAdapter<GreetingMessage, HomeAdapter.MyViewHolder>(DiffCallback()) {
val fragment = fragment
// var itemList = ArrayList<GreetingMessage>()
//
// fun addItem(message: GreetingMessage) {
// itemList.add(message)
// notifyItemInserted(itemList.size - 1)
// }
//
// fun setItems(messageList: List<GreetingMessage>) {
// itemList.clear()
// itemList.addAll(messageList)
// notifyDataSetChanged()
// }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val mDataBinding: AdapterHomeBinding =
DataBindingUtil.inflate(
LayoutInflater.from(fragment.context),
LayoutInflater.from(parent.context),
R.layout.adapter_home,
parent,
false
@ -47,9 +35,11 @@ class HomeAdapter(fragment: Fragment) :
holder.onBind(position)
}
// override fun getItemCount(): Int {
// return itemList.size
// }
fun getItemData(position: Int): GreetingMessage {
return getItem(position)!!
}
inner class MyViewHolder(private val mDataBinding: AdapterHomeBinding) :
RecyclerView.ViewHolder(mDataBinding.root) {
@ -63,12 +53,11 @@ class HomeAdapter(fragment: Fragment) :
.error(R.mipmap.volvo_logo_small)
.into(mDataBinding.messageHeadIcon)
}
}
class DiffCallback : DiffUtil.ItemCallback<GreetingMessage>() {
override fun areItemsTheSame(oldItem: GreetingMessage, newItem: GreetingMessage): Boolean {
return oldItem.uuid == newItem.uuid
return oldItem.uuid == newItem.uuid && oldItem.status == newItem.status
}
override fun areContentsTheSame(

View File

@ -1,96 +1,172 @@
package com.navinfo.volvo.ui.fragments.home
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import android.widget.Toast
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.easytools.tools.ThreadPoolUtils.runOnUiThread
import com.navinfo.volvo.R
import com.navinfo.volvo.databinding.FragmentHomeBinding
import com.navinfo.volvo.databinding.HomeAdapterNotingBinding
import com.navinfo.volvo.databinding.LoadStateViewBinding
import com.navinfo.volvo.tools.DisplayUtil
import com.navinfo.volvo.ui.fragments.BaseFragment
import com.yanzhenjie.recyclerview.*
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
@AndroidEntryPoint
class HomeFragment : BaseFragment(), OnItemClickListener, OnItemMenuClickListener {
private var _binding: FragmentHomeBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private val viewModel by viewModels<HomeViewModel> { viewModelFactoryProvider }
private lateinit var messageAdapter: HomeAdapter
private val viewModel by viewModels<HomeViewModel>()
private val messageAdapter by lazy { HomeAdapter(this) }
private var headBinding: HomeAdapterNotingBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val root: View = binding.root
headBinding = HomeAdapterNotingBinding.inflate(inflater, container, false)
initView()
return root
}
private fun initView() {
// mDataBinding.homeViewModel = viewModel
messageAdapter = HomeAdapter(this)
val recyclerview: SwipeRecyclerView = binding.homeRecyclerview
recyclerview.adapter = null //先设置null否则会报错
//创建菜单选项
//注意:使用滑动菜单不能开启滑动删除,否则只有滑动删除没有滑动菜单
var mSwipeMenuCreator =
SwipeMenuCreator { _, rightMenu, position ->
//添加菜单自动添加至尾部
var deleteItem = SwipeMenuItem(context)
deleteItem.height = DisplayUtil.dip2px(context!!, 60f)
deleteItem.width = DisplayUtil.dip2px(context!!, 80f)
deleteItem.background = context!!.getDrawable(R.color.red)
deleteItem.text = context!!.getString(R.string.delete)
rightMenu.addMenuItem(deleteItem)
var mSwipeMenuCreator = SwipeMenuCreator { _, rightMenu, _ ->
//添加菜单自动添加至尾部
var deleteItem = SwipeMenuItem(context)
deleteItem.height = DisplayUtil.dip2px(requireContext(), 60f)
deleteItem.width = DisplayUtil.dip2px(requireContext(), 80f)
deleteItem.background = requireContext().getDrawable(R.color.red)
deleteItem.text = requireContext().getString(R.string.delete)
rightMenu.addMenuItem(deleteItem)
//分享
var shareItem = SwipeMenuItem(context)
shareItem.height = DisplayUtil.dip2px(requireContext(), 60f)
shareItem.width = DisplayUtil.dip2px(requireContext(), 80f)
shareItem.background = requireContext().getDrawable(R.color.gray)
shareItem.text = requireContext().getString(R.string.share)
shareItem.setTextColor(requireContext().getColor(R.color.white))
rightMenu.addMenuItem(shareItem)
}
//侧滑按钮
binding.homeRecyclerview.setOnItemMenuClickListener { menuBridge, position ->
menuBridge.closeMenu()
// val direction: Int = menuBridge.getDirection() // 左侧还是右侧菜单。
when (menuBridge.position) {// 菜单在RecyclerView的Item中的Position。
0 -> {//删除按钮
viewModel.deleteMessage(messageAdapter.getItemData(position).id)
}
1 -> {//分享按钮
}
//分享
var shareItem = SwipeMenuItem(context)
shareItem.height = DisplayUtil.dip2px(context!!, 60f)
shareItem.width = DisplayUtil.dip2px(context!!, 80f)
shareItem.background = context!!.getDrawable(R.color.gray)
shareItem.text = context!!.getString(R.string.share)
shareItem.setTextColor(R.color.white)
rightMenu.addMenuItem(shareItem)
}
val layoutManager = LinearLayoutManager(context)
}
recyclerview.layoutManager = layoutManager
recyclerview.addItemDecoration(DividerItemDecoration(context, layoutManager.orientation))
recyclerview.setSwipeMenuCreator(mSwipeMenuCreator)
recyclerview.setOnItemClickListener(this)
// recyclerview.useDefaultLoadMore()
// recyclerview.setLoadMoreListener {
//
// }
val layoutManager = LinearLayoutManager(context)
binding.homeRecyclerview.layoutManager = layoutManager
//自动增加分割线
binding.homeRecyclerview.addItemDecoration(
DividerItemDecoration(
context, layoutManager.orientation
)
)
//增加侧滑按钮
binding.homeRecyclerview.setSwipeMenuCreator(mSwipeMenuCreator)
//单项点击
binding.homeRecyclerview.setOnItemClickListener(this)
//使用下拉加载
// binding.homeRecyclerview.useDefaultLoadMore() // 使用默认的加载更多的View。
binding.homeRecyclerview.setLoadMoreListener {
Log.e("jingo", "下拉加载开始")
} // 加载更多的监听。
//开始下拉刷新
binding.homeSwipeRefreshLayout.setOnRefreshListener {
Log.e("jingo", "开始刷新")
viewModel.getNetMessageList()
}
//列表自动分页
lifecycleScope.launch {
viewModel.messageList.collectLatest {
viewModel.messageList.collect {
messageAdapter.submitData(it)
}
}
// messageAdapter.withLoadStateFooter(
// footer = RecLoadStateAdapter { messageAdapter.retry() }
// )
binding.homeRecyclerview.adapter = messageAdapter
//初始状态添加监听
messageAdapter.addLoadStateListener {
when (it.refresh) {
is LoadState.NotLoading -> {
if (messageAdapter.itemCount == 0)
binding.homeRecyclerview.addHeaderView(headBinding!!.root)
else{
binding.homeRecyclerview.removeHeaderView(headBinding!!.root)
}
Log.d("jingo", "is NotLoading")
}
is LoadState.Loading -> {
Log.d("jingo", "is Loading")
}
is LoadState.Error -> {
Log.d("jingo", "is Error:")
when ((it.refresh as LoadState.Error).error) {
is IOException -> {
Log.d("jingo", "IOException")
}
else -> {
Log.d("jingo", "others exception")
}
}
}
}
}
loadMoreFinish()
//监听数据请求是否结束
viewModel.isLoading.observe(viewLifecycleOwner, Observer {
if (!it) loadMoreFinish()
})
// messageAdapter.withLoadStateHeader()
recyclerview.adapter = messageAdapter
}
private fun loadMoreFinish() {
binding.homeSwipeRefreshLayout.isRefreshing = false
// 第一次加载数据:一定要掉用这个方法。
// 第一个参数表示此次数据是否为空假如你请求到的list为空(== null || list.size == 0)那么这里就要true。
// 第二个参数表示是否还有更多数据根据服务器返回给你的page等信息判断是否还有更多这样可以提供性能如果不能判断则传true。
binding.homeRecyclerview.loadMoreFinish(false, false)
}
override fun onStart() {
super.onStart()
viewModel.getNetMessageList()
@ -100,6 +176,7 @@ class HomeFragment : BaseFragment(), OnItemClickListener, OnItemMenuClickListene
override fun onDestroyView() {
super.onDestroyView()
_binding = null
headBinding = null
}
override fun onItemClick(view: View?, adapterPosition: Int) {

View File

@ -1,49 +1,100 @@
package com.navinfo.volvo.ui.fragments.home
import android.app.Application
import android.widget.Toast
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import com.navinfo.volvo.database.dao.GreetingMessageDao
import com.navinfo.volvo.database.entity.GreetingMessage
import com.navinfo.volvo.model.messagelist.NetworkMessageListPost
import com.navinfo.volvo.model.network.NetworkDeleteMessagePost
import com.navinfo.volvo.model.network.NetworkMessageListPost
import com.navinfo.volvo.repository.database.DatabaseRepository
import com.navinfo.volvo.repository.network.NetworkRepository
import com.navinfo.volvo.repository.preferences.PreferencesRepository
import com.navinfo.volvo.util.NetResult
import com.navinfo.volvo.util.asLiveData
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor(
private val application: Application,
private val netRepository: NetworkRepository,
private val dataRepository: DatabaseRepository
private val dataRepository: DatabaseRepository,
private val messageDao: GreetingMessageDao,
private val shard: PreferencesRepository
) : ViewModel() {
private val _isLoading = MutableLiveData<Boolean>()
val isLoading = _isLoading.asLiveData()
// private val _messageList = MutableLiveData<List<GreetingMessage>>()
// val messageList = _messageList.asLiveData()
val messageList: Flow<PagingData<GreetingMessage>>
get() = dataRepository.getMessageByPaging()
lateinit var userName: String
init {
viewModelScope.launch {
shard.loginUser().collect {
userName = it!!.username
}
}
}
fun getNetMessageList() {
if (_isLoading.value == true)
return
_isLoading.postValue(true)
viewModelScope.launch {
val messagePost = NetworkMessageListPost(who = "", toWho = "")
when (val result = netRepository.getCardList(messagePost)) {
val messagePost = NetworkMessageListPost(who = userName, toWho = "")
when (val result = netRepository.getMessageList(messagePost)) {
is NetResult.Success -> {
_isLoading.value = false
// if (result.data != null) {
// val list = (result.data.data as NetworkMessageListResponse).rows
// _messageList.value = list
// }
if ((result.data!!.data != null) && (result.data.data!!.rows != null)) {
messageDao.insertOrUpdate(result.data.data!!.rows!!)
}
}
is NetResult.Failure -> {
_isLoading.value = false
Toast.makeText(application, "${result.code}:${result.msg}", Toast.LENGTH_SHORT)
.show()
}
is NetResult.Error -> {
_isLoading.value = false
}
is NetResult.Loading -> {
_isLoading.postValue(true)
}
}
}
}
fun deleteMessage(id: Long) {
viewModelScope.launch {
val post = NetworkDeleteMessagePost(id)
netRepository.deleteMessage(post)
messageDao.deleteById(id)
when (val result = netRepository.deleteMessage(post)) {
is NetResult.Success -> {
_isLoading.value = false
}
is NetResult.Failure -> {
_isLoading.value = false
Toast.makeText(
application,
"服务返回信息:${result.code}:${result.msg}",
Toast.LENGTH_SHORT
)
.show()
}
is NetResult.Error -> {
_isLoading.value = false
}

View File

@ -0,0 +1,18 @@
package com.navinfo.volvo.ui.fragments.home
import android.view.ViewGroup
import androidx.paging.LoadState
import androidx.paging.LoadStateAdapter
class LoadStateAdapter(private val retry: () -> Unit) :
LoadStateAdapter<LoadStateViewHolder>() {
override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
holder.bindState(loadState)
}
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
return LoadStateViewHolder(parent, retry)
}
}

View File

@ -0,0 +1,39 @@
package com.navinfo.volvo.ui.fragments.home
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.paging.LoadState
import androidx.recyclerview.widget.RecyclerView
import com.navinfo.volvo.R
import com.navinfo.volvo.databinding.LoadStateViewBinding
class LoadStateViewHolder(parent: ViewGroup, var retry: () -> Unit) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.load_state_view, parent, false)
) {
var itemLoadStateBindingUtil: LoadStateViewBinding = LoadStateViewBinding.bind(itemView)
fun bindState(loadState: LoadState) {
when (loadState) {
is LoadState.Error -> {
itemLoadStateBindingUtil.loadStateLayout.visibility = View.VISIBLE
// itemLoadStateBindingUtil.btnRetry.setOnClickListener {
// retry()
// }
Log.d("jingo", "error了吧")
}
is LoadState.Loading -> {
itemLoadStateBindingUtil.loadStateLayout.visibility = View.VISIBLE
Log.d("jingo", "该显示了")
}
else -> {
Log.d("jingo", "--其他的错误")
}
}
}
}

View File

@ -1,51 +0,0 @@
//package com.navinfo.volvo.ui.fragments.home
//
//import android.view.LayoutInflater
//import android.view.View
//import android.view.ViewGroup
//import androidx.core.view.isVisible
//import androidx.paging.LoadState
//import androidx.paging.LoadStateAdapter
//import androidx.recyclerview.widget.RecyclerView
//import com.example.picsapp.R
//import com.example.picsapp.databinding.LoadStateViewBinding
//
//class RecLoadStateAdapter(
// private val retry: () -> Unit
//) : LoadStateAdapter<RecLoadStateAdapter.LoadStateViewHolder>() {
//
// override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
//
// val binding = LoadStateViewBinding
// .inflate(LayoutInflater.from(parent.context), parent, false)
//
// return LoadStateViewHolder(binding)
// }
//
// override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
// holder.onBind(loadState)
//
// }
//
//
//
// inner class LoadStateViewHolder(private val binding: LoadStateViewBinding) : RecyclerView.ViewHolder(binding.root){
// fun onBind(loadState: LoadState) {
// val progress = binding.loadStateProgress
// val btnRetry = binding.loadStateRetry
// val txtErrorMessage = binding.loadStateErrorMessage
//
// btnRetry.isVisible = loadState !is LoadState.Loading
// txtErrorMessage.isVisible = loadState !is LoadState.Loading
// progress.isVisible = loadState is LoadState.Loading
//
// if (loadState is LoadState.Error){
// txtErrorMessage.text = loadState.error.localizedMessage
// }
//
// btnRetry.setOnClickListener {
// retry.invoke()
// }
// }
// }
//}

View File

@ -4,13 +4,18 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.navinfo.volvo.R
import com.navinfo.volvo.databinding.FragmentLoginBinding
import com.navinfo.volvo.ui.fragments.BaseFragment
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
@AndroidEntryPoint
@ -20,7 +25,7 @@ class LoginFragment : BaseFragment() {
private lateinit var viewBinding: FragmentLoginBinding
private val viewModel by viewModels<LoginViewModel> { viewModelFactoryProvider }
private val viewModel by viewModels<LoginViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
@ -34,11 +39,30 @@ class LoginFragment : BaseFragment() {
}
private fun initView() {
lifecycleScope.launch {
viewModel.user.collect {
if (it != null){
viewBinding.loginUser = it
}
}
}
viewBinding.loginFragmentRegisterButton.setOnClickListener {
}
viewBinding.loginFragmentLoginButton.setOnClickListener {
// viewModel.login(viewBinding.loginFragmentUserLayout)
if (viewBinding.loginUsername.text!!.isEmpty()) {
Toast.makeText(context, "请输入用户名", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (viewBinding.loginPassword.text!!.isEmpty()) {
Toast.makeText(context, "请输入密码", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
viewModel.onClickLogin(
viewBinding.loginUsername.text.toString(),
viewBinding.loginPassword.text.toString()
)
findNavController().navigate(R.id.action_login_to_home)
}
}

View File

@ -1,23 +1,29 @@
package com.navinfo.volvo.ui.fragments.login
import android.view.View
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.navinfo.volvo.database.AppDatabase
import com.navinfo.volvo.database.entity.User
import com.navinfo.volvo.util.SharedPreferenceHelper
import androidx.lifecycle.viewModelScope
import com.navinfo.volvo.repository.preferences.PreferencesRepository
import com.navinfo.volvo.util.asLiveData
//import com.navinfo.volvo.repository.preferences.PreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import javax.inject.Inject
class LoginViewModel @Inject constructor(private val sharedPreferenceHelper: SharedPreferenceHelper) : ViewModel() {
@HiltViewModel
class LoginViewModel @Inject constructor(private val repository: PreferencesRepository) :
ViewModel() {
// val user: LiveData<User> = _user
fun liveDataOnclick(view: View) {
val user = repository.loginUser()
fun onClickLogin(name: String, password: String) {
viewModelScope.launch {
repository.saveLoginUser(id = "", name = name, password = password)
}
}
fun userRegister(username: String, password: String) {
fun onClickLoginRegister(username: String, password: String) {
}
}

View File

@ -7,7 +7,8 @@ import android.text.TextUtils
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.View.*
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.AdapterView.OnItemSelectedListener
@ -17,15 +18,11 @@ import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.Navigation
import androidx.navigation.fragment.findNavController
import com.easytools.tools.DateUtils
import com.easytools.tools.DisplayUtils
import com.easytools.tools.FileIOUtils
import com.easytools.tools.ResourceUtils
import com.easytools.tools.ToastUtils
import com.easytools.tools.*
import com.elvishew.xlog.XLog
import com.github.file_picker.FileType
import com.github.file_picker.ListDirection
@ -52,6 +49,7 @@ import com.navinfo.volvo.util.PhotoLoader
import com.navinfo.volvo.utils.EasyMediaFile
import com.navinfo.volvo.utils.SystemConstant
import com.nhaarman.supertooltips.ToolTip
import dagger.hilt.android.AndroidEntryPoint
import indi.liyi.viewer.Utils
import indi.liyi.viewer.ViewData
import top.zibin.luban.Luban
@ -62,12 +60,10 @@ import java.util.*
import kotlin.streams.toList
//@RuntimePermissions
class ObtainMessageFragment: Fragment() {
@AndroidEntryPoint
class ObtainMessageFragment : Fragment() {
private var _binding: FragmentObtainMessageBinding? = null
private val obtainMessageViewModel by lazy {
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java)
}
private val obtainMessageViewModel by viewModels<ObtainMessageViewModel>()
private val photoHelper by lazy {
EasyMediaFile().setCrop(true)
}
@ -91,18 +87,17 @@ class ObtainMessageFragment: Fragment() {
_binding = FragmentObtainMessageBinding.inflate(inflater, container, false)
val root: View = binding.root
val greetingMessage = GreetingMessage()
obtainMessageViewModel.setCurrentMessage(greetingMessage)
obtainMessageViewModel.setCurrentMessage(GreetingMessage(who = obtainMessageViewModel.username))
obtainMessageViewModel?.getMessageLiveData()?.observe(
obtainMessageViewModel.getMessageLiveData().observe(
viewLifecycleOwner, Observer {
// 初始化界面显示内容
if(it.name?.isNotEmpty() == true)
if (it.name?.isNotEmpty() == true)
binding.tvMessageTitle?.setText(it.name)
if (it.sendDate?.isNotEmpty() == true) {
// 获取当前发送时间,如果早于当前时间,则显示现在
val sendDate = DateUtils.str2Date(it.sendDate, dateSendFormat)
if (sendDate<=Date()) {
if (sendDate <= Date()) {
binding.btnSendTime.text = "现在"
} else {
binding.btnSendTime.text = it.sendDate
@ -112,39 +107,47 @@ class ObtainMessageFragment: Fragment() {
}
var hasPhoto = false
var hasAudio = false
if (it.imageUrl!=null&&it.imageUrl?.isNotEmpty() == true) {
if (it.imageUrl != null && it.imageUrl?.isNotEmpty() == true) {
hasPhoto = true
// Glide.with(this@ObtainMessageFragment)
// .asBitmap().fitCenter()
// .load(it.imageUrl)
// .diskCacheStrategy(DiskCacheStrategy.ALL)
// .into(binding.imgMessageAttachment)
// Glide.with(this@ObtainMessageFragment)
// .asBitmap().fitCenter()
// .load(it.imageUrl)
// .diskCacheStrategy(DiskCacheStrategy.ALL)
// .into(binding.imgMessageAttachment)
// 如果当前attachment文件是本地文件开始尝试网络上传
val str = it.imageUrl?.replace("\\", "/")
binding.tvPhotoName.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG )
binding.tvPhotoName.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG)
if (!str!!.startsWith("http")) {
obtainMessageViewModel.uploadAttachment(File(it.imageUrl), AttachmentType.PIC)
obtainMessageViewModel.uploadAttachment(
File(it.imageUrl),
AttachmentType.PIC
)
binding.tvPhotoName.text = str.substringAfterLast("/", "picture.jpg")
} else {
if (str.contains("?")) {
binding.tvPhotoName.text = str.substring(str.lastIndexOf("/")+1, str.indexOf("?"))
binding.tvPhotoName.text =
str.substring(str.lastIndexOf("/") + 1, str.indexOf("?"))
} else {
binding.tvPhotoName.text = str.substringAfterLast("/")
}
}
}
if (it.mediaUrl!=null&&it.mediaUrl?.isNotEmpty() == true) {
if (it.mediaUrl != null && it.mediaUrl?.isNotEmpty() == true) {
hasAudio = true
binding.tvAudioName.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG )
binding.tvAudioName.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG)
// 如果当前attachment文件是本地文件开始尝试网络上传
val str = it.mediaUrl?.replace("\\", "/")
if (!str!!.startsWith("http")) {
obtainMessageViewModel.uploadAttachment(File(it.mediaUrl),AttachmentType.AUDIO)
obtainMessageViewModel.uploadAttachment(
File(it.mediaUrl),
AttachmentType.AUDIO
)
binding.tvAudioName.text = str.substringAfterLast("/", "audio.m4a")
} else {
if (str.contains("?")) {
binding.tvAudioName.text = str.substring(str.lastIndexOf("/")+1, str.indexOf("?"))
binding.tvAudioName.text =
str.substring(str.lastIndexOf("/") + 1, str.indexOf("?"))
} else {
binding.tvAudioName.text = str.substringAfterLast("/")
}
@ -152,7 +155,7 @@ class ObtainMessageFragment: Fragment() {
}
binding.layerPhotoResult.visibility = if (hasPhoto) VISIBLE else GONE
binding.layerGetPhoto.visibility = if (hasPhoto) GONE else VISIBLE
// binding.imgMessageAttachment.visibility = if (hasPhoto) VISIBLE else GONE
// binding.imgMessageAttachment.visibility = if (hasPhoto) VISIBLE else GONE
binding.layerAudioResult.visibility = if (hasAudio) VISIBLE else GONE
binding.layerGetAudio.visibility = if (hasAudio) GONE else VISIBLE
@ -188,7 +191,7 @@ class ObtainMessageFragment: Fragment() {
obtainMessageViewModel.getMessageLiveData().value?.name = it.toString()
})
binding.edtSendFrom.addTextChangedListener (afterTextChanged = {
binding.edtSendFrom.addTextChangedListener(afterTextChanged = {
obtainMessageViewModel.getMessageLiveData().value?.who = it.toString()
})
@ -201,8 +204,11 @@ class ObtainMessageFragment: Fragment() {
}
val sendToArray = mutableListOf<VolvoModel>(VolvoModel("XC60", "智雅", "LYVXFEFEXNL754427"))
binding.edtSendTo.adapter = ArrayAdapter<String>(requireContext(),
android.R.layout.simple_dropdown_item_1line, android.R.id.text1, sendToArray.stream().map { it -> "${it.version} ${it.model} ${it.num}" }.toList())
binding.edtSendTo.onItemSelectedListener = object: OnItemSelectedListener {
android.R.layout.simple_dropdown_item_1line,
android.R.id.text1,
sendToArray.stream().map { it -> "${it.version} ${it.model} ${it.num}" }.toList()
)
binding.edtSendTo.onItemSelectedListener = object : OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
obtainMessageViewModel.getMessageLiveData().value?.toWho = sendToArray[p2].num
}
@ -219,9 +225,19 @@ class ObtainMessageFragment: Fragment() {
override fun onClickListener(selectTime: String) {
val sendDate = DateUtils.str2Date(selectTime, dateShowFormat)
if (sendDate <= Date()) {
obtainMessageViewModel.updateMessageSendTime(DateUtils.date2Str(Date(), dateSendFormat))
obtainMessageViewModel.updateMessageSendTime(
DateUtils.date2Str(
Date(),
dateSendFormat
)
)
} else {
obtainMessageViewModel.updateMessageSendTime(DateUtils.date2Str(sendDate, dateSendFormat))
obtainMessageViewModel.updateMessageSendTime(
DateUtils.date2Str(
sendDate,
dateSendFormat
)
)
}
}
}
@ -238,7 +254,8 @@ class ObtainMessageFragment: Fragment() {
override fun onGranted(permissions: MutableList<String>, all: Boolean) {
if (!all) {
Toast.makeText(activity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
Toast.makeText(activity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT)
.show()
return
}
// 开始启动拍照界面
@ -280,13 +297,23 @@ class ObtainMessageFragment: Fragment() {
// Do something here with selected files
val audioFile = files.get(0).file
if (!audioFile.parentFile.parentFile.absolutePath.equals(SystemConstant.SoundFolder)) {
val copyResult = FileIOUtils.writeFileFromIS(File(SystemConstant.SoundFolder, audioFile.name), FileInputStream(audioFile))
XLog.e("拷贝结果:"+copyResult)
val copyResult = FileIOUtils.writeFileFromIS(
File(
SystemConstant.SoundFolder,
audioFile.name
), FileInputStream(audioFile)
)
XLog.e("拷贝结果:" + copyResult)
if (!copyResult) {
ToastUtils.showToast("无法访问该文件,请重新选择其他文件")
return
}
obtainMessageViewModel.updateMessageAudio(File(SystemConstant.SoundFolder, audioFile.name).absolutePath)
obtainMessageViewModel.updateMessageAudio(
File(
SystemConstant.SoundFolder,
audioFile.name
).absolutePath
)
} else {
obtainMessageViewModel.updateMessageAudio(audioFile.absolutePath)
}
@ -299,7 +326,7 @@ class ObtainMessageFragment: Fragment() {
ToastUtils.showToast("只能选择.m4a文件")
return
}
if (media.file.length()>2*1000*1000) {
if (media.file.length() > 2 * 1000 * 1000) {
ToastUtils.showToast("文件不能超过2M")
return
}
@ -330,23 +357,25 @@ class ObtainMessageFragment: Fragment() {
.request(object : OnPermissionCallback {
override fun onGranted(permissions: MutableList<String>, all: Boolean) {
if (!all) {
Toast.makeText(activity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
Toast.makeText(activity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT)
.show()
return
}
when(motionEvent.action) {
MotionEvent.ACTION_DOWN-> {
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
// 申请权限
recorderLifecycleObserver.initAndStartRecorder()
startRecordTime = System.currentTimeMillis()
false
}
MotionEvent.ACTION_UP -> {
if (System.currentTimeMillis() - startRecordTime<2000) {
if (System.currentTimeMillis() - startRecordTime < 2000) {
ToastUtils.showToast("录音时间太短!")
recorderLifecycleObserver.stopAndReleaseRecorder()
return
}
val recorderAudioPath = recorderLifecycleObserver.stopAndReleaseRecorder()
val recorderAudioPath =
recorderLifecycleObserver.stopAndReleaseRecorder()
if (File(recorderAudioPath).exists()) {
obtainMessageViewModel.updateMessageAudio(recorderAudioPath)
}
@ -376,7 +405,7 @@ class ObtainMessageFragment: Fragment() {
photoHelper.setCallback {
if (it.exists()) {
val fileName = it.name.lowercase()
if (fileName.endsWith(".jpg")||fileName.endsWith(".jpeg")||fileName.endsWith(".png")) {
if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") || fileName.endsWith(".png")) {
// 获取选中的图片,自动压缩图片质量
// 压缩图片文件
Luban.with(context)
@ -394,19 +423,35 @@ class ObtainMessageFragment: Fragment() {
override fun onSuccess(file: File?) {
XLog.d("压缩图片成功:${file?.absolutePath}")
// 删除源文件
if (!it.absolutePath.equals(file?.absolutePath)) {
it?.delete()
}
// 删除源文件
if (!it.absolutePath.equals(file?.absolutePath)) {
it?.delete()
}
// 如果当前文件不在camera缓存文件夹下则移动该文件
if (!file!!.parentFile.absolutePath.equals(SystemConstant.CameraFolder)) {
val copyResult = FileIOUtils.writeFileFromIS(File(SystemConstant.CameraFolder, fileName), FileInputStream(file))
XLog.e("拷贝结果:"+copyResult)
// 跳转回原Fragment展示拍摄的照片
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(File(SystemConstant.CameraFolder, fileName).absolutePath)
try {
val copyResult = FileIOUtils.writeFileFromIS(
File(
SystemConstant.CameraFolder,
fileName
), FileInputStream(file)
)
XLog.e("拷贝结果:$copyResult")
// 跳转回原Fragment展示拍摄的照片
obtainMessageViewModel
.updateMessagePic(
File(
SystemConstant.CameraFolder,
fileName
).absolutePath
)
} catch (e: Exception) {
XLog.e("崩溃:${e.message}")
}
} else {
// 跳转回原Fragment展示拍摄的照片
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(file!!.absolutePath)
obtainMessageViewModel.updateMessagePic(file!!.absolutePath)
}
}
@ -414,12 +459,23 @@ class ObtainMessageFragment: Fragment() {
XLog.d("压缩图片失败:${e.message}")
}
}).launch()
} else if (fileName.endsWith(".mp3")||fileName.endsWith(".wav")||fileName.endsWith(".amr")||fileName.endsWith(".m4a")) {
} else if (fileName.endsWith(".mp3") || fileName.endsWith(".wav") || fileName.endsWith(
".amr"
) || fileName.endsWith(".m4a")
) {
ToastUtils.showToast(it.absolutePath)
if (!it.parentFile.parentFile.absolutePath.equals(SystemConstant.SoundFolder)) {
val copyResult = FileIOUtils.writeFileFromIS(File(SystemConstant.SoundFolder, fileName), FileInputStream(it))
XLog.e("拷贝结果:"+copyResult)
obtainMessageViewModel.updateMessageAudio(File(SystemConstant.SoundFolder, fileName).absolutePath)
val copyResult = FileIOUtils.writeFileFromIS(
File(SystemConstant.SoundFolder, fileName),
FileInputStream(it)
)
XLog.e("拷贝结果:" + copyResult)
obtainMessageViewModel.updateMessageAudio(
File(
SystemConstant.SoundFolder,
fileName
).absolutePath
)
} else {
obtainMessageViewModel.updateMessageAudio(it.absolutePath)
}
@ -432,12 +488,14 @@ class ObtainMessageFragment: Fragment() {
}
binding.tvAudioName.setOnClickListener {
binding.llAudioPlay.visibility = if (binding.llAudioPlay.visibility == VISIBLE) GONE else VISIBLE
binding.llAudioPlay.visibility =
if (binding.llAudioPlay.visibility == VISIBLE) GONE else VISIBLE
// 判断当前播放的文件是否在缓存文件夹内,如果不在首先下载该文件
val fileUrl = obtainMessageViewModel.getMessageLiveData().value!!.mediaUrl!!
val localFile = obtainMessageViewModel.getLocalFileFromNetUrl(fileUrl, AttachmentType.AUDIO)
val localFile =
obtainMessageViewModel.getLocalFileFromNetUrl(fileUrl, AttachmentType.AUDIO)
if (!localFile.exists()) {
obtainMessageViewModel.downLoadFile(fileUrl, localFile, object: DownloadCallback {
obtainMessageViewModel.downLoadFile(fileUrl, localFile, object : DownloadCallback {
override fun progress(progress: Int) {
}
@ -455,7 +513,7 @@ class ObtainMessageFragment: Fragment() {
}
binding.btnObtainMessageBack.setOnClickListener {
Navigation.findNavController(it).popBackStack()
findNavController().popBackStack()
}
binding.btnObtainMessageConfirm.setOnClickListener {
@ -476,7 +534,7 @@ class ObtainMessageFragment: Fragment() {
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tiLayoutTitle)
checkResult = false
} else {
if (messageData?.name!!.length>10) {
if (messageData?.name!!.length > 10) {
val toolTipRelativeLayout =
binding.ttTitle
val toolTip = ToolTip()
@ -514,7 +572,7 @@ class ObtainMessageFragment: Fragment() {
checkResult = false
}
if (messageData?.who?.isEmpty()==true) {
if (messageData?.who?.isEmpty() == true) {
val toolTipRelativeLayout =
binding.ttSendFrom
val toolTip = ToolTip()
@ -526,7 +584,7 @@ class ObtainMessageFragment: Fragment() {
toolTipRelativeLayout.showToolTipForView(toolTip, binding.edtSendFrom)
checkResult = false
}
if (messageData?.toWho?.isEmpty()==true) {
if (messageData?.toWho?.isEmpty() == true) {
val toolTipRelativeLayout =
binding.ttSendTo
val toolTip = ToolTip()
@ -547,22 +605,30 @@ class ObtainMessageFragment: Fragment() {
localAttachmentList.add(imageAttachment)
}
if (messageData?.mediaUrl?.startsWith("http") == false) {
val audioAttachment = Attachment("", messageData.mediaUrl!!, AttachmentType.AUDIO)
val audioAttachment =
Attachment("", messageData.mediaUrl!!, AttachmentType.AUDIO)
localAttachmentList.add(audioAttachment)
}
if (localAttachmentList.isNotEmpty()) {
MaterialAlertDialogBuilder(requireContext())
.setTitle("提示")
.setMessage("当前照片及音频内容需首先上传,是否尝试上传?")
.setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
dialogInterface.dismiss()
for (attachment in localAttachmentList) {
obtainMessageViewModel.uploadAttachment(File(attachment.pathUrl), attachment.attachmentType)
}
})
.setNegativeButton("取消", DialogInterface.OnClickListener {
dialogInterface, i -> dialogInterface.dismiss()
})
.setPositiveButton(
"确定",
DialogInterface.OnClickListener { dialogInterface, i ->
dialogInterface.dismiss()
for (attachment in localAttachmentList) {
obtainMessageViewModel.uploadAttachment(
File(attachment.pathUrl),
attachment.attachmentType
)
}
})
.setNegativeButton(
"取消",
DialogInterface.OnClickListener { dialogInterface, i ->
dialogInterface.dismiss()
})
.show()
return@setOnClickListener
}
@ -571,7 +637,7 @@ class ObtainMessageFragment: Fragment() {
val sendDate = DateUtils.str2Date(messageData?.sendDate, dateSendFormat)
val cal = Calendar.getInstance()
cal.time = Date()
cal.set(Calendar.MINUTE, cal.get(Calendar.MINUTE)+1)
cal.set(Calendar.MINUTE, cal.get(Calendar.MINUTE) + 1)
if (sendDate.time < cal.time.time) { // 发送时间设置小于当前时间1分钟后Toast提示用户并自动设置发送时间
messageData?.sendDate = DateUtils.date2Str(cal.time, dateSendFormat)
ToastUtils.showToast("自动调整发送时间为1分钟后发送")
@ -581,7 +647,7 @@ class ObtainMessageFragment: Fragment() {
}
// 开始网络提交数据
if (obtainMessageViewModel.getMessageLiveData().value?.id==0L) { // 如果网络id为空则调用更新操作
if (obtainMessageViewModel.getMessageLiveData().value?.id == 0L) { // 如果网络id为空则调用更新操作
obtainMessageViewModel.insertCardByApp(confirmCallback)
} else {
obtainMessageViewModel.updateCardByApp(confirmCallback)
@ -593,7 +659,8 @@ class ObtainMessageFragment: Fragment() {
val viewData = ViewData()
viewData.imageSrc = obtainMessageViewModel.getMessageLiveData().value!!.imageUrl
viewData.targetX = Utils.dp2px(context, 10F).toFloat()
viewData.targetWidth = DisplayUtils.getScreenWidthPixels(activity) - Utils.dp2px(context, 20F)
viewData.targetWidth =
DisplayUtils.getScreenWidthPixels(activity) - Utils.dp2px(context, 20F)
viewData.targetHeight = Utils.dp2px(context, 200F)
val viewDataList = listOf(viewData)
binding.imageViewer.overlayStatusBar(true) // ImageViewer 是否会占据 StatusBar 的空间
@ -603,11 +670,13 @@ class ObtainMessageFragment: Fragment() {
.watch(0) // 开启浏览
}
binding.edtSendFrom.setText(obtainMessageViewModel.username)
}
val confirmCallback = object: ObtainMessageViewModel.MyConfirmCallback {
val confirmCallback = object : ObtainMessageViewModel.MyConfirmCallback {
override fun onSucess() {
findNavController().navigate(R.id.navigation_home)
findNavController().popBackStack()
}
}
@ -648,6 +717,7 @@ class ObtainMessageFragment: Fragment() {
})
.show()
}
fun onRecorderDenied() {
ToastUtils.showToast("当前操作需要您授权录音权限!")
}

View File

@ -1,36 +1,47 @@
package com.navinfo.volvo.ui.fragments.message
import android.security.ConfirmationCallback
import androidx.lifecycle.*
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.easytools.tools.FileIOUtils
import com.easytools.tools.FileUtils
import com.easytools.tools.ToastUtils
import com.elvishew.xlog.XLog
import com.navinfo.volvo.database.entity.Attachment
import com.navinfo.volvo.database.entity.AttachmentType
import com.navinfo.volvo.database.entity.GreetingMessage
import com.navinfo.volvo.http.DownloadCallback
import com.navinfo.volvo.http.DownloadManager
import com.navinfo.volvo.http.DownloadState
import com.navinfo.volvo.http.NavinfoVolvoCall
import com.navinfo.volvo.repository.preferences.PreferencesRepository
import com.navinfo.volvo.utils.SystemConstant
import kotlinx.coroutines.flow.Flow
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import java.io.File
import java.io.FileInputStream
import java.util.*
import javax.inject.Inject
class ObtainMessageViewModel @Inject constructor(): ViewModel() {
@HiltViewModel
class ObtainMessageViewModel @Inject constructor(
private val pre: PreferencesRepository,
) : ViewModel() {
private val msgLiveData: MutableLiveData<GreetingMessage> by lazy {
MutableLiveData<GreetingMessage>()
}
var username: String = ""
init {
viewModelScope.launch {
pre.loginUser().collect {
username = it!!.username
}
}
}
fun setCurrentMessage(msg: GreetingMessage) {
msgLiveData.postValue(msg)
}
@ -131,7 +142,11 @@ class ObtainMessageViewModel @Inject constructor(): ViewModel() {
try {
val requestFile: RequestBody =
RequestBody.create("multipart/form-data".toMediaTypeOrNull(), attachmentFile)
val body = MultipartBody.Part.createFormData("picture", attachmentFile.getName(), requestFile)
val body = MultipartBody.Part.createFormData(
"picture",
attachmentFile.getName(),
requestFile
)
val result = NavinfoVolvoCall.getApi().uploadAttachment(body)
XLog.d(result.code)
if (result.code == 200) { // 请求成功
@ -144,17 +159,19 @@ class ObtainMessageViewModel @Inject constructor(): ViewModel() {
if (destFile.exists()) {
FileUtils.deleteFile(destFile)
}
val copyResult = FileIOUtils.writeFileFromIS(destFile, FileInputStream(attachmentFile))
XLog.e("拷贝结果:"+copyResult)
val copyResult =
FileIOUtils.writeFileFromIS(destFile, FileInputStream(attachmentFile))
XLog.e("拷贝结果:" + copyResult)
} else {
val destFile = File(SystemConstant.SoundFolder, newFileName)
if (destFile.exists()) {
FileUtils.deleteFile(destFile)
}
val copyResult = FileIOUtils.writeFileFromIS(destFile, FileInputStream(attachmentFile))
XLog.e("拷贝结果:"+copyResult)
val copyResult =
FileIOUtils.writeFileFromIS(destFile, FileInputStream(attachmentFile))
XLog.e("拷贝结果:" + copyResult)
}
if (fileKey!=null) {
if (fileKey != null) {
downloadAttachment(fileKey, attachmentType)
}
} else {
@ -179,7 +196,7 @@ class ObtainMessageViewModel @Inject constructor(): ViewModel() {
if (result.code == 200) { // 请求成功
// 获取上传后的结果
val imageUrl = result.data
if (imageUrl!=null) {
if (imageUrl != null) {
XLog.d("downloadAttachment-imageUrl:${imageUrl}")
// 获取到图片的网络地址
if (attachmentType == AttachmentType.PIC) {
@ -198,7 +215,7 @@ class ObtainMessageViewModel @Inject constructor(): ViewModel() {
}
}
fun downLoadFile(url: String, destFile: File, downloadCallback: DownloadCallback){
fun downLoadFile(url: String, destFile: File, downloadCallback: DownloadCallback) {
viewModelScope.launch {
DownloadManager.download(
url,
@ -236,7 +253,8 @@ class ObtainMessageViewModel @Inject constructor(): ViewModel() {
"sendDate" to message?.sendDate,
"version" to message?.version
)
val result = NavinfoVolvoCall.getApi().insertCardByApp(insertData as Map<String, String>)
val result =
NavinfoVolvoCall.getApi().insertCardByApp(insertData as Map<String, String>)
XLog.d("insertCardByApp:${result.code}")
if (result.code == 200) { // 请求成功
// 获取上传后的结果
@ -269,7 +287,8 @@ class ObtainMessageViewModel @Inject constructor(): ViewModel() {
"sendDate" to message?.sendDate,
"version" to message?.version
)
val result = NavinfoVolvoCall.getApi().updateCardByApp(updateData as Map<String, String>)
val result =
NavinfoVolvoCall.getApi().updateCardByApp(updateData as Map<String, String>)
XLog.d("updateCardByApp:${result.code}")
if (result.code == 200) { // 请求成功
// 数据更新成功
@ -289,14 +308,14 @@ class ObtainMessageViewModel @Inject constructor(): ViewModel() {
/**
* 根据网络地址获取本地的缓存文件路径
* */
fun getLocalFileFromNetUrl(url: String, attachmentType: AttachmentType):File {
fun getLocalFileFromNetUrl(url: String, attachmentType: AttachmentType): File {
if (url.startsWith("http")) {
val folder = when(attachmentType) {
AttachmentType.PIC-> SystemConstant.CameraFolder
val folder = when (attachmentType) {
AttachmentType.PIC -> SystemConstant.CameraFolder
else -> SystemConstant.SoundFolder
}
var name = if (url.contains("?")) {
url.substring(url.lastIndexOf("/")+1, url.indexOf("?"))
url.substring(url.lastIndexOf("/") + 1, url.indexOf("?"))
} else {
url.substringAfterLast("/")
}

View File

@ -11,14 +11,16 @@ package com.navinfo.volvo.util
sealed class NetResult<out R> {
data class Success<out T>(val data: T?) : NetResult<T>()
data class Failure(val code: Int, val msg: String) : NetResult<Nothing>()
data class Error(val exception: Exception) : NetResult<Nothing>()
object Loading : NetResult<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
is Loading -> "Loading"
is Success<*> -> "网络访问成功返回正确结果Success[data=$data]"
is Failure -> "网络访问成功返回错误结果Failure[$msg]"
is Error -> "网络访问出错 Error[exception=$exception]"
is Loading -> "网络访问中 Loading"
}
}
}

View File

@ -1,131 +0,0 @@
package com.navinfo.volvo.util
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
import androidx.core.content.edit
import com.google.gson.Gson
class SharedPreferenceHelper {
companion object {
private const val WEATHER_PREF_TIME = "Weather pref time"
private const val WEATHER_FORECAST_PREF_TIME = "Forecast pref time"
private const val CITY_ID = "City ID"
private var prefs: SharedPreferences? = null
private const val LOCATION = "LOCATION"
@Volatile
private var instance: SharedPreferenceHelper? = null
/**
* This checks if there is an existing instance of the [SharedPreferences] in the
* specified [context] and creates one if there isn't or else, it returns the
* already existing instance. This function ensures that the [SharedPreferences] is
* accessed at any instance by a single thread.
*/
fun getInstance(context: Context): SharedPreferenceHelper {
synchronized(this) {
val _instance = instance
if (_instance == null) {
prefs = PreferenceManager.getDefaultSharedPreferences(context)
instance = _instance
}
return SharedPreferenceHelper()
}
}
}
/**
* This function saves the initial time [System.nanoTime] at which the weather information
* at the user's location is accessed.
* @param time the value of [System.nanoTime] when the weather information is received.
*/
fun saveTimeOfInitialWeatherFetch(time: Long) {
prefs?.edit(commit = true) {
putLong(WEATHER_PREF_TIME, time)
}
}
/**
* This function returns the saved value of [System.nanoTime] when the weather information
* at the user's location was accessed.
* @see saveTimeOfInitialWeatherFetch
*/
fun getTimeOfInitialWeatherFetch() = prefs?.getLong(WEATHER_PREF_TIME, 0L)
/**
* This function saves the initial time [System.nanoTime] at which the weather forecast
* at the user's location is accessed.
* @param time the value of [System.nanoTime] when the weather forecast is received.
*/
fun saveTimeOfInitialWeatherForecastFetch(time: Long) {
prefs?.edit(commit = true) {
putLong(WEATHER_FORECAST_PREF_TIME, time)
}
}
/**
* This function returns the saved value of [System.nanoTime] when the weather forecast
* at the user's location was accessed.
* @see saveTimeOfInitialWeatherForecastFetch
*/
fun getTimeOfInitialWeatherForecastFetch() = prefs?.getLong(WEATHER_FORECAST_PREF_TIME, 0L)
/**
* This function saves the [cityId] of the location whose weather information has been
* received.
* @param cityId the id of the location whose weather has been received
*/
fun saveCityId(cityId: Int) {
prefs?.edit(commit = true) {
putInt(CITY_ID, cityId)
}
}
/**
* This function returns the id of the location whose weather information has been received.
* @see saveCityId
*/
fun getCityId() = prefs?.getInt(CITY_ID, 0)
/**
* This function gets the value of the cache duration the user set in the
* Settings Fragment.
*/
fun getUserSetCacheDuration() = prefs?.getString("cache_key", "0")
/**
* This function gets the value of the app theme the user set in the
* Settings Fragment.
*/
fun getSelectedThemePref() = prefs?.getString("theme_key", "")
/**
* This function gets the value of the temperature unit the user set in the
* Settings Fragment.
*/
fun getSelectedTemperatureUnit() = prefs?.getString("unit_key", "")
// /**
// * This function saves a [LocationModel]
// */
// fun saveLocation(location: LocationModel) {
// prefs?.edit(commit = true) {
// val gson = Gson()
// val json = gson.toJson(location)
// putString(LOCATION, json)
// }
// }
// /**
// * This function gets the value of the saved [LocationModel]
// */
// fun getLocation(): LocationModel {
// val gson = Gson()
// val json = prefs?.getString(LOCATION, null)
// return gson.fromJson(json, LocationModel::class.java)
// }
}

View File

@ -0,0 +1,9 @@
syntax = "proto3";
option java_package = "com.navinfo.volvo.model.proto";
option java_multiple_files = true;
message LoginUser {
string username = 1;
string password = 2;
}

View File

@ -1,12 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<import type="com.navinfo.volvo.Constant" />
<variable
name="greetingMessage"
type="com.navinfo.volvo.database.entity.GreetingMessage" />
@ -17,15 +19,17 @@
android:layout_height="wrap_content">
<ImageView
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/message_head_icon"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginLeft="8dp"
android:src="@mipmap/volvo_logo_small"
android:scaleType="fitXY"
android:src="@mipmap/ic_launcher"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.4" />
<TextView
android:id="@+id/message_badge"
@ -34,6 +38,7 @@
android:background="@drawable/shape_circular"
android:gravity="center"
android:textColor="#000000"
android:visibility="@{Constant.message_status_late.equals(greetingMessage.status)?View.VISIBLE:View.INVISIBLE}"
app:layout_constraintCircle="@id/message_head_icon"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="40dp"

View File

@ -11,8 +11,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:layout_marginRight="20dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -23,8 +23,8 @@
android:hint="请输入查询内容" />
</com.google.android.material.textfield.TextInputLayout>
<com.yanzhenjie.recyclerview.SwipeRecyclerView
android:id="@+id/home_recyclerview"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/home_SwipeRefreshLayout"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="10dp"
@ -33,7 +33,14 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/home_search" />
app:layout_constraintTop_toBottomOf="@id/home_search">
<com.yanzhenjie.recyclerview.SwipeRecyclerView
android:id="@+id/home_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,7 +6,7 @@
<data>
<variable
name="loginUser"
type="com.navinfo.volvo.model.LoginUser" />
type="com.navinfo.volvo.model.proto.LoginUser" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -14,11 +14,11 @@
android:layout_height="match_parent"
tools:context="com.navinfo.volvo.ui.fragments.login.LoginFragment">
<androidx.constraintlayout.utils.widget.ImageFilterView
<ImageView
android:id="@+id/login_fragment_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="fitXY"
android:src="@mipmap/volvo_logo_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -40,9 +40,10 @@
app:layout_constraintVertical_bias="0.4">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/login_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{loginUser.name}"
android:text="@{loginUser.username}"
android:hint="请输入用户名" />
</com.google.android.material.textfield.TextInputLayout>
@ -54,6 +55,7 @@
app:layout_constraintTop_toBottomOf="@id/login_fragment_user_layout">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"

View File

@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_default_padding"
tools:context=".ui.message.ObtainMessageFragment">
tools:context="com.navinfo.volvo.ui.fragments.message.ObtainMessageFragment">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -300,6 +300,7 @@
android:text="我是谁:"></TextView>
<androidx.appcompat.widget.AppCompatEditText
android:enabled="false"
android:id="@+id/edt_send_from"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -333,6 +334,13 @@
android:id="@+id/edt_send_to"
android:layout_width="match_parent"
android:layout_height="wrap_content"></androidx.appcompat.widget.AppCompatSpinner>
<indi.liyi.viewer.ImageViewer
android:id="@+id/imageViewer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
app:ivr_dragMode="agile" />
</LinearLayout>
<com.nhaarman.supertooltips.ToolTipRelativeLayout
@ -404,10 +412,4 @@
android:text="确认提交"></com.google.android.material.button.MaterialButton>
</LinearLayout>
<indi.liyi.viewer.ImageViewer
android:id="@+id/imageViewer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
app:ivr_dragMode="agile" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="暂无数据,可下拉刷新"
android:textSize="16sp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/load_state_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -13,12 +13,9 @@
<action
android:id="@+id/action_login_to_home"
app:destination="@id/navigation_home"
app:enterAnim="@anim/from_left"
app:exitAnim="@anim/to_right"
app:popEnterAnim="@anim/from_right"
app:popExitAnim="@anim/to_left"
app:popUpTo="@id/navigation_login"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/navigation_home"