增加sharde数据存储功能,记录登录用户

优化页面跳转动画
增加message删除流程
This commit is contained in:
squallzhjch 2023-01-09 10:58:19 +08:00
parent 64972c3c88
commit 1e52eac92c
38 changed files with 681 additions and 639 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'
@ -107,29 +107,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'
@ -153,6 +144,31 @@ dependencies {
implementation 'com.github.JagarYousef:ChatVoicePlayer:1.1.0'
// https://github.com/XiaoGe-1996/ImageViewer
implementation 'com.github.XiaoGe-1996:ImageViewer:v1.0.0'
// 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

@ -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

@ -8,13 +8,18 @@ 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")
fun countUnreadByFlow(): Flow<Long>
@ -28,17 +33,22 @@ interface GreetingMessageDao {
* 检查某条数据是否存在
*/
@Query("SELECT id From GreetingMessage WHERE id = :id LIMIT 1")
fun getMessageId(id: Long): Long
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{
} else {
update(message)
}
}
}
}

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,16 @@
package com.navinfo.volvo.repository.preferences
import com.navinfo.volvo.model.proto.LoginUser
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,58 @@
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 com.navinfo.volvo.model.proto.LoginUser
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
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,10 @@ 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,7 +52,6 @@ class HomeAdapter(fragment: Fragment) :
.error(R.mipmap.volvo_logo_small)
.into(mDataBinding.messageHeadIcon)
}
}
class DiffCallback : DiffUtil.ItemCallback<GreetingMessage>() {

View File

@ -1,12 +1,15 @@
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.navinfo.volvo.R
@ -15,25 +18,24 @@ 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 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 val viewModel by viewModels<HomeViewModel>()
private lateinit var messageAdapter: HomeAdapter
private val messageAdapter by lazy { HomeAdapter(this) }
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
@ -42,55 +44,117 @@ class HomeFragment : BaseFragment(), OnItemClickListener, OnItemMenuClickListene
}
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(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 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)
}
//侧滑按钮
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 -> {
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()

View File

@ -1,23 +1,30 @@
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.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,
) : ViewModel() {
private val _isLoading = MutableLiveData<Boolean>()
@ -32,18 +39,46 @@ class HomeViewModel @Inject constructor(
fun getNetMessageList() {
if (_isLoading.value == true)
return
_isLoading.postValue(true)
viewModelScope.launch {
val messagePost = NetworkMessageListPost(who = "", toWho = "")
when (val result = netRepository.getCardList(messagePost)) {
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)
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,31 @@ class LoginFragment : BaseFragment() {
}
private fun initView() {
lifecycleScope.launch {
viewModel.user.collect {
if (it != null){
viewBinding.loginUser = it
}
cancel()
}
}
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

@ -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

@ -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,7 +14,6 @@
android:layout_height="match_parent"
tools:context="com.navinfo.volvo.ui.fragments.login.LoginFragment">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/login_fragment_logo"
android:layout_width="wrap_content"
@ -40,9 +39,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 +54,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

@ -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"