feat: 增加图片查看和音频播放

This commit is contained in:
xiaoyan 2023-01-06 09:45:47 +08:00
commit 8e5f216b42
27 changed files with 846 additions and 506 deletions

View File

@ -136,9 +136,13 @@ dependencies {
// https://github.com/elvishew/xLog/blob/master/README_ZH.md
implementation 'com.elvishew:xlog:1.10.1'
//
implementation ("com.github.bumptech.glide:glide:4.11.0") {
implementation("com.github.bumptech.glide:glide:4.11.0") {
exclude group: "com.android.support"
}
// https://github.com/JagarYousef/ChatVoicePlayer
implementation 'com.github.JagarYousef:ChatVoicePlayer:1.1.0'
// https://github.com/XiaoGe-1996/ImageViewer
implementation 'com.github.XiaoGe-1996:ImageViewer:v1.0.0'
}
kapt {

View File

@ -2,19 +2,19 @@ package com.navinfo.volvo.database
import androidx.room.Database
import androidx.room.RoomDatabase
import com.navinfo.volvo.database.dao.MessageDao
import com.navinfo.volvo.database.dao.GreetingMessageDao
import com.navinfo.volvo.database.dao.UserDao
import com.navinfo.volvo.model.Attachment
import com.navinfo.volvo.model.Message
import com.navinfo.volvo.model.User
import com.navinfo.volvo.database.entity.Attachment
import com.navinfo.volvo.database.entity.GreetingMessage
import com.navinfo.volvo.database.entity.User
@Database(
entities = [Message::class, Attachment::class, User::class],
entities = [GreetingMessage::class, Attachment::class, User::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun getMessageDao(): MessageDao
abstract fun getMessageDao(): GreetingMessageDao
abstract fun getUserDao(): UserDao
}

View File

@ -1,191 +1,191 @@
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.model.Message;
import com.navinfo.volvo.model.Attachment;
import com.navinfo.volvo.model.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;
}
}
//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

@ -0,0 +1,19 @@
package com.navinfo.volvo.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.navinfo.volvo.database.entity.GreetingMessage
@Dao
interface GreetingMessageDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg check: GreetingMessage)
@Query("SELECT * FROM GreetingMessage where id =:id")
fun findCheckManagerById(id: Long): GreetingMessage?
@Query("SELECT * FROM GreetingMessage")
fun findList(): List<GreetingMessage>
}

View File

@ -1,19 +0,0 @@
package com.navinfo.volvo.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.navinfo.volvo.model.Message
@Dao
interface MessageDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg check: Message)
@Query("SELECT * FROM Message where id =:id")
fun findCheckManagerById(id: Long): Message?
@Query("SELECT * FROM Message")
fun findList(): List<Message>
}

View File

@ -3,7 +3,7 @@ package com.navinfo.volvo.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import com.navinfo.volvo.model.User
import com.navinfo.volvo.database.entity.User
@Dao
interface UserDao {

View File

@ -1,4 +1,4 @@
package com.navinfo.volvo.model
package com.navinfo.volvo.database.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@ -8,7 +8,7 @@ import com.navinfo.volvo.tools.GsonUtil
@Entity(tableName = "Attachment")
data class Attachment(
@PrimaryKey()
@PrimaryKey
var id: String,
var pathUrl: String,
var attachmentType: AttachmentType

View File

@ -0,0 +1,44 @@
package com.navinfo.volvo.database.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import org.jetbrains.annotations.NotNull
@Entity(tableName = "GreetingMessage")
@TypeConverters(AttachmentConverters::class)
data class GreetingMessage @JvmOverloads constructor(
@PrimaryKey(autoGenerate = true)
var uuid:Long = 0,
var id: Long = 0,
var searchValue: String? = "",
var createBy: String? = "",
var createTime: String? = "",
var updateBy: String? = "",
var updateTime: String? = "",
var remark: String? = "",
var name: String? = "",
var imageUrl: String? = "",
var mediaUrl: String? = "",
var who: String? = "",
var toWho: String? = "",
var sendDate: String? = "",
var status: String? = "",
var isSkip: String? = "",
var skipUrl: String? = "",
var startTime: String? = "",
var endTime: String? = "",
var sendVehicle: String? = "",
var sendSex: String? = "",
var sendAge: String? = "",
var sendNum: String? = "",
var sendVins: String? = "",
var sendType: String? = "",
var del: String? = "",
var version: String? = "",
// /**
// * 附件列表
// */
// var attachment: MutableList<Attachment> = mutableListOf()
) {
}

View File

@ -1,4 +1,4 @@
package com.navinfo.volvo.model
package com.navinfo.volvo.database.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@ -7,8 +7,6 @@ import androidx.room.TypeConverters
import com.google.gson.reflect.TypeToken
import com.navinfo.volvo.tools.GsonUtil
import org.jetbrains.annotations.NotNull
import java.time.LocalDateTime
import java.time.LocalTime
import javax.inject.Inject
@Entity(tableName = "User")

View File

@ -3,7 +3,7 @@ package com.navinfo.volvo.di.module
import android.content.Context
import androidx.room.Room
import com.navinfo.volvo.database.AppDatabase
import com.navinfo.volvo.database.dao.MessageDao
import com.navinfo.volvo.database.dao.GreetingMessageDao
import com.navinfo.volvo.database.dao.UserDao
import com.tencent.wcdb.database.SQLiteCipherSpec
import com.tencent.wcdb.room.db.WCDBOpenHelperFactory
@ -45,7 +45,7 @@ class DatabaseModule {
@Singleton
@Provides
fun provideMessageDao(database: AppDatabase): MessageDao {
fun provideMessageDao(database: AppDatabase): GreetingMessageDao {
return database.getMessageDao()
}

View File

@ -0,0 +1,86 @@
package com.navinfo.volvo.http
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import okhttp3.ResponseBody
import retrofit2.Retrofit
import java.io.File
import java.io.IOException
object DownloadManager {
suspend fun download(url: String, file: File): Flow<DownloadState> {
return flow {
val retrofit = Retrofit.Builder()
.baseUrl(UrlUtils.getBaseUrl(url))
.build()
val response = retrofit.create(NavinfoVolvoService::class.java).downloadFile(url).execute()
if (response.isSuccessful) {
saveToFile(response.body()!!, file) {
emit(DownloadState.InProgress(it))
}
emit(DownloadState.Success(file))
} else {
emit(DownloadState.Error(IOException(response.toString())))
}
}.catch {
emit(DownloadState.Error(it))
}.flowOn(Dispatchers.IO)
}
private inline fun saveToFile(responseBody: ResponseBody, file: File, progressListener: (Int) -> Unit) {
val total = responseBody.contentLength()
var bytesCopied = 0
var emittedProgress = 0
file.outputStream().use { output ->
val input = responseBody.byteStream()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var bytes = input.read(buffer)
while (bytes >= 0) {
output.write(buffer, 0, bytes)
bytesCopied += bytes
bytes = input.read(buffer)
val progress = (bytesCopied * 100 / total).toInt()
if (progress - emittedProgress > 0) {
progressListener(progress)
emittedProgress = progress
}
}
}
}
}
sealed class DownloadState {
data class InProgress(val progress: Int) : DownloadState()
data class Success(val file: File) : DownloadState()
data class Error(val throwable: Throwable) : DownloadState()
}
interface DownloadCallback {
fun progress(progress: Int)
fun error(throwable: Throwable)
fun success(file: File)
}
object UrlUtils {
/**
* 从url分割出BaseUrl
*/
fun getBaseUrl(url: String): String {
var mutableUrl = url
var head = ""
var index = mutableUrl.indexOf("://")
if (index != -1) {
head = mutableUrl.substring(0, index + 3)
mutableUrl = mutableUrl.substring(index + 3)
}
index = mutableUrl.indexOf("/")
if (index != -1) {
mutableUrl = mutableUrl.substring(0, index + 1)
}
return head + mutableUrl
}
}

View File

@ -2,11 +2,9 @@ package com.navinfo.volvo.http
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.*
import java.io.File
interface NavinfoVolvoService {
@ -23,4 +21,7 @@ interface NavinfoVolvoService {
suspend fun uploadAttachment(@Part attachmentFile: MultipartBody.Part):DefaultResponse<MutableMap<String, String>>
@POST("/img/download")
suspend fun downLoadAttachment(@Body downloadData: Map<String, String>):DefaultResponse<String>
@Streaming
@GET
fun downloadFile(@Url url: String): Call<ResponseBody>
}

View File

@ -1,46 +0,0 @@
package com.navinfo.volvo.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
@Entity(tableName = "message")
@TypeConverters(AttachmentConverters::class)
data class Message @JvmOverloads constructor(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
var netId: String = "",
/**
*标题
*/
var title: String = "",
/**
* 信息内容
*/
var message: String = "",
/**
* 操作时间
*/
var optionDate: String = "",
/**
* 发送时间
*/
var sendDate: String = "",
/**
* 信息状态
*/
var status: Int = 1,
/**
* 发送者ID
*/
var fromId: String = "",
/**
* 接收者ID
*/
var toId: String = "",
/**
* 附件列表
*/
var attachment: MutableList<Attachment> = mutableListOf()
)

View File

@ -1,6 +1,6 @@
package com.navinfo.volvo.model.network
package com.navinfo.volvo.model.messagelist
data class NetworkPostMessage(
data class NetworkMessageListPost(
val name: String,//问候名称,非必填项
val who: String, //我是谁
val toWho: String, //发送给谁

View File

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

View File

@ -1,9 +1,10 @@
package com.navinfo.volvo.repository
import com.navinfo.volvo.model.Message
import com.navinfo.volvo.model.network.NetworkPostMessage
import com.navinfo.volvo.http.DefaultResponse
import com.navinfo.volvo.model.messagelist.NetworkMessageListPost
import com.navinfo.volvo.model.messagelist.NetworkMessageListResponse
import com.navinfo.volvo.util.NetResult
interface NetworkDataSource {
suspend fun getCardList(message: NetworkPostMessage): NetResult<List<Message>>
suspend fun getCardList(message: NetworkMessageListPost): NetResult<DefaultResponse<NetworkMessageListResponse>>
}

View File

@ -1,15 +1,17 @@
package com.navinfo.volvo.repository
import com.navinfo.volvo.database.AppDatabase
import com.navinfo.volvo.database.dao.GreetingMessageDao
import com.navinfo.volvo.di.scope.IoDispatcher
import com.navinfo.volvo.model.Message
import com.navinfo.volvo.model.network.NetworkPostMessage
import com.navinfo.volvo.database.entity.GreetingMessage
import com.navinfo.volvo.http.DefaultResponse
import com.navinfo.volvo.model.messagelist.NetworkMessageListPost
import com.navinfo.volvo.model.messagelist.NetworkMessageListResponse
import com.navinfo.volvo.repository.service.NetworkService
import com.navinfo.volvo.tools.GsonUtil
import com.navinfo.volvo.util.NetResult
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import okhttp3.FormBody
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import javax.inject.Inject
@ -17,17 +19,22 @@ import javax.inject.Inject
class NetworkDataSourceImp @Inject constructor(
private val netWorkService: NetworkService,
private val messageDao: GreetingMessageDao,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
) : NetworkDataSource {
override suspend fun getCardList(message: NetworkPostMessage): NetResult<List<Message>> =
override suspend fun getCardList(message: NetworkMessageListPost): NetResult<DefaultResponse<NetworkMessageListResponse>> =
withContext(ioDispatcher) {
return@withContext try {
val stringBody = GsonUtil.getInstance().toJson(message).toRequestBody("application/json;charset=utf-8".toMediaType())
val stringBody = GsonUtil.getInstance().toJson(message)
.toRequestBody("application/json;charset=utf-8".toMediaType())
val result = netWorkService.queryCardListByApp(stringBody)
if (result.isSuccessful) {
val list = result.body()
NetResult.Success(list)
val body = result.body()
val list: MutableList<GreetingMessage> =
listOf(body!!.data!!.rows) as MutableList<GreetingMessage>
messageDao.insert(*list.map { it }.toTypedArray())
NetResult.Success(body)
} else {
NetResult.Success(null)
}

View File

@ -1,6 +1,7 @@
package com.navinfo.volvo.repository.service
import com.navinfo.volvo.model.Message
import com.navinfo.volvo.http.DefaultResponse
import com.navinfo.volvo.model.messagelist.NetworkMessageListResponse
import okhttp3.RequestBody
import retrofit2.Response
import retrofit2.http.Body
@ -8,5 +9,5 @@ import retrofit2.http.POST
interface NetworkService {
@POST("/navi/cardDelivery/queryCardListByApp")
suspend fun queryCardListByApp(@Body body: RequestBody): Response<List<Message>>
suspend fun queryCardListByApp(@Body body: RequestBody): Response<DefaultResponse<NetworkMessageListResponse>>
}

View File

@ -10,6 +10,7 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.easytools.tools.FileUtils
import com.elvishew.xlog.BuildConfig
import com.elvishew.xlog.LogConfiguration
import com.elvishew.xlog.LogLevel
import com.elvishew.xlog.XLog
@ -25,7 +26,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.navinfo.volvo.BuildConfig
import com.navinfo.volvo.R
import com.navinfo.volvo.databinding.ActivityMainBinding
import com.navinfo.volvo.utils.SystemConstant
@ -55,7 +55,8 @@ class MainActivity : AppCompatActivity() {
override fun onGranted(permissions: MutableList<String>, all: Boolean) {
if (!all) {
Toast.makeText(this@MainActivity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
Toast.makeText(this@MainActivity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT)
.show()
return
}
// 在SD卡创建项目目录
@ -64,7 +65,8 @@ class MainActivity : AppCompatActivity() {
override fun onDenied(permissions: MutableList<String>, never: Boolean) {
if (never) {
Toast.makeText(this@MainActivity, "永久拒绝授权,请手动授权文件读写权限", Toast.LENGTH_SHORT).show()
Toast.makeText(this@MainActivity, "永久拒绝授权,请手动授权文件读写权限", Toast.LENGTH_SHORT)
.show()
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(this@MainActivity, permissions)
} else {
@ -146,10 +148,11 @@ class MainActivity : AppCompatActivity() {
val consolePrinter: Printer = ConsolePrinter() // 通过 System.out 打印日志到控制台的打印器
val filePrinter: Printer = FilePrinter.Builder("${SystemConstant.ROOT_PATH}/Logs") // 指定保存日志文件的路径
.fileNameGenerator(DateFileNameGenerator()) // 指定日志文件名生成器,默认为 ChangelessFileNameGenerator("log")
.backupStrategy(NeverBackupStrategy()) // 指定日志文件备份策略,默认为 FileSizeBackupStrategy(1024 * 1024)
.build()
val filePrinter: Printer =
FilePrinter.Builder("${SystemConstant.ROOT_PATH}/Logs") // 指定保存日志文件的路径
.fileNameGenerator(DateFileNameGenerator()) // 指定日志文件名生成器,默认为 ChangelessFileNameGenerator("log")
.backupStrategy(NeverBackupStrategy()) // 指定日志文件备份策略,默认为 FileSizeBackupStrategy(1024 * 1024)
.build()
XLog.init( // 初始化 XLog
config, // 指定日志配置,如果不指定,会默认使用 new LogConfiguration.Builder().build()

View File

@ -7,18 +7,18 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.navinfo.volvo.R
import com.navinfo.volvo.model.Message
import com.navinfo.volvo.database.entity.GreetingMessage
class MessageAdapter : RecyclerView.Adapter<MessageAdapter.MyViewHolder>() {
var itemList: MutableList<Message> = mutableListOf()
var itemList: MutableList<GreetingMessage> = mutableListOf()
fun addItem(message: Message) {
fun addItem(message: GreetingMessage) {
itemList.add(message)
notifyItemInserted(itemList.size - 1)
}
fun setItem(messageList: MutableList<Message>){
fun setItem(messageList: MutableList<GreetingMessage>){
itemList = messageList
notifyDataSetChanged()
}
@ -35,8 +35,8 @@ class MessageAdapter : RecyclerView.Adapter<MessageAdapter.MyViewHolder>() {
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val message = itemList[position]
holder.toName.text = message.fromId
holder.messageText.text = message.message
holder.toName.text = message.toWho
holder.messageText.text = message.name
holder.sendTime.text = message.sendDate
}

View File

@ -3,8 +3,9 @@ package com.navinfo.volvo.ui.fragments.home
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.navinfo.volvo.model.Message
import com.navinfo.volvo.model.network.NetworkPostMessage
import com.navinfo.volvo.database.entity.GreetingMessage
import com.navinfo.volvo.model.messagelist.NetworkMessageListPost
import com.navinfo.volvo.model.messagelist.NetworkMessageListResponse
import com.navinfo.volvo.repository.NetworkDataSource
import com.navinfo.volvo.util.NetResult
import com.navinfo.volvo.util.asLiveData
@ -18,18 +19,18 @@ class MessageViewModel @Inject constructor(
private val _isLoading = MutableLiveData<Boolean>()
val isLoading = _isLoading.asLiveData()
private val _messageList = MutableLiveData<List<Message>>()
private val _messageList = MutableLiveData<List<GreetingMessage>>()
val messageList = _messageList.asLiveData()
fun getMessageList() {
_isLoading.postValue(true)
viewModelScope.launch {
val messagePost = NetworkPostMessage(who = "北京测试", toWho = "volvo测试")
val messagePost = NetworkMessageListPost(who = "北京测试", toWho = "volvo测试")
when (val result = repository.getCardList(messagePost)) {
is NetResult.Success -> {
_isLoading.value = false
if (result.data != null) {
val list = result.data
val list = (result.data.data as NetworkMessageListResponse).rows
_messageList.value = list
}
}

View File

@ -5,7 +5,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.navinfo.volvo.database.AppDatabase
import com.navinfo.volvo.model.User
import com.navinfo.volvo.database.entity.User
import javax.inject.Inject
class LoginViewModel @Inject constructor(private val dataBase: AppDatabase) : ViewModel() {

View File

@ -1,9 +1,11 @@
package com.navinfo.volvo.ui.fragments.message
import android.content.DialogInterface
import android.graphics.Paint
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.View.*
import android.view.ViewGroup
@ -13,12 +15,17 @@ import android.widget.ArrayAdapter
import android.widget.Toast
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 com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.easytools.tools.DateUtils
import com.easytools.tools.DeviceUtils
import com.easytools.tools.DisplayUtils
import com.easytools.tools.FileUtils
import com.easytools.tools.ResourceUtils
import com.easytools.tools.ToastUtils
import com.elvishew.xlog.XLog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -28,15 +35,18 @@ import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.navinfo.volvo.R
import com.navinfo.volvo.RecorderLifecycleObserver
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.databinding.FragmentObtainMessageBinding
import com.navinfo.volvo.http.NavinfoVolvoCall
import com.navinfo.volvo.model.Attachment
import com.navinfo.volvo.model.AttachmentType
import com.navinfo.volvo.model.Message
import com.navinfo.volvo.http.DownloadCallback
import com.navinfo.volvo.ui.markRequiredInRed
import com.navinfo.volvo.util.PhotoLoader
import com.navinfo.volvo.utils.EasyMediaFile
import com.navinfo.volvo.utils.SystemConstant
import com.nhaarman.supertooltips.ToolTip
import indi.liyi.viewer.Utils
import indi.liyi.viewer.ViewData
import top.zibin.luban.Luban
import top.zibin.luban.OnCompressListener
import java.io.File
@ -56,6 +66,9 @@ class ObtainMessageFragment: Fragment() {
RecorderLifecycleObserver()
}
private val dateSendFormat = "yyyy-MM-dd HH:mm:ss"
private val dateShowFormat = "yyyy-MM-dd HH:mm"
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
@ -68,67 +81,71 @@ class ObtainMessageFragment: Fragment() {
_binding = FragmentObtainMessageBinding.inflate(inflater, container, false)
val root: View = binding.root
obtainMessageViewModel.setCurrentMessage(Message())
obtainMessageViewModel.setCurrentMessage(GreetingMessage())
obtainMessageViewModel?.getMessageLiveData()?.observe(
viewLifecycleOwner, Observer {
// 初始化界面显示内容
if(it.title?.isNotEmpty() == true)
binding.tvMessageTitle?.setText(it.title)
if(it.name?.isNotEmpty() == true)
binding.tvMessageTitle?.setText(it.name)
if (it.sendDate?.isNotEmpty() == true) {
// 获取当前发送时间,如果早于当前时间,则显示现在
val sendDate = DateUtils.str2Date(it.sendDate, "yyyy-MM-dd HH:mm:ss")
val sendDate = DateUtils.str2Date(it.sendDate, dateSendFormat)
if (sendDate<=Date()) {
binding.btnSendTime.text = "现在"
} else {
binding.btnSendTime.text = it.sendDate
}
} else { // 如果发送时间此时为空,自动设置发送时间为当前时间
it.sendDate = DateUtils.date2Str(Date(), dateSendFormat)
}
var hasPhoto = false
var hasAudio = false
if (it.attachment.isNotEmpty()) {
// 展示照片文件或录音文件
for (attachment in it.attachment) {
if (attachment.attachmentType == AttachmentType.PIC) {
Glide.with(context!!)
.asBitmap().fitCenter()
.load(attachment.pathUrl)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(binding.imgMessageAttachment)
// 显示名称
hasPhoto = true
// 如果当前attachment文件是本地文件开始尝试网络上传
val str = attachment.pathUrl.replace("\\", "/")
if (!str.startsWith("http")) {
obtainMessageViewModel.uploadAttachment(File(attachment.pathUrl), attachment.attachmentType)
binding.tvPhotoName.text = str.substringAfterLast("/", "picture.jpg")
} else {
binding.tvPhotoName.text = str.substring(str.lastIndexOf("/"), str.indexOf("?"))
}
}
if (attachment.attachmentType == AttachmentType.AUDIO) {
hasAudio = true
// 如果当前attachment文件是本地文件开始尝试网络上传
val str = attachment.pathUrl.replace("\\", "/")
if (!str.startsWith("http")) {
obtainMessageViewModel.uploadAttachment(File(attachment.pathUrl), attachment.attachmentType)
binding.tvAudioName.text = str.substringAfterLast("/", "audio.m4a")
} else {
val str = attachment.pathUrl.replace("\\", "/")
binding.tvAudioName.text = str.substring(str.lastIndexOf("/"), str.indexOf("?"))
}
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)
// 如果当前attachment文件是本地文件开始尝试网络上传
val str = it.imageUrl?.replace("\\", "/")
binding.tvPhotoName.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG )
if (!str!!.startsWith("http")) {
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("?"))
} else {
binding.tvPhotoName.text = str.substringAfterLast("/")
}
}
}
// 如果当前attachment不为空可以显示预览按钮
if (it.mediaUrl!=null&&it.mediaUrl?.isNotEmpty() == true) {
hasAudio = true
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)
binding.tvAudioName.text = str.substringAfterLast("/", "audio.m4a")
} else {
if (str.contains("?")) {
binding.tvAudioName.text = str.substring(str.lastIndexOf("/")+1, str.indexOf("?"))
} else {
binding.tvAudioName.text = str.substringAfterLast("/")
}
}
}
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
binding.llAudioPlay.visibility = if (hasAudio) VISIBLE else GONE
}
)
lifecycle.addObserver(recorderLifecycleObserver)
@ -138,27 +155,21 @@ class ObtainMessageFragment: Fragment() {
fun initView() {
// 设置问候信息提示的红色星号
// binding.tiLayoutTitle.markRequiredInRed()
// binding.tvMessageTitle.addTextChangedListener(afterTextChanged = {
// obtainMessageViewModel.updateMessageTitle(it.toString())
// })
binding.tvMessageTitle.setOnFocusChangeListener { view, b ->
if (!b) {
obtainMessageViewModel.updateMessageTitle(binding.tvMessageTitle.text.toString())
}
}
binding.tiLayoutTitle.markRequiredInRed()
binding.tvMessageTitle.addTextChangedListener(afterTextChanged = {
obtainMessageViewModel.getMessageLiveData().value?.name = it.toString()
})
binding.edtSendFrom.addTextChangedListener (afterTextChanged = {
obtainMessageViewModel.updateMessageSendFrom(it.toString())
obtainMessageViewModel.getMessageLiveData().value?.who = it.toString()
})
binding.imgPhotoDelete.setOnClickListener {
obtainMessageViewModel.updateMessagePic(null)
obtainMessageViewModel.updateMessagePic("")
}
binding.imgAudioDelete.setOnClickListener {
obtainMessageViewModel.updateMessageAudio(null)
obtainMessageViewModel.updateMessageAudio("")
}
val sendToArray = mutableListOf<String>("绑定车辆1(LYVXFEFEXNL754427)")
@ -166,7 +177,7 @@ class ObtainMessageFragment: Fragment() {
android.R.layout.simple_dropdown_item_1line, android.R.id.text1, sendToArray)
binding.edtSendTo.onItemSelectedListener = object: OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
obtainMessageViewModel.getMessageLiveData().value?.toId = sendToArray[p2]
obtainMessageViewModel.getMessageLiveData().value?.toWho = sendToArray[p2]
}
override fun onNothingSelected(p0: AdapterView<*>?) {
@ -179,11 +190,11 @@ class ObtainMessageFragment: Fragment() {
val dialog = DateTimePickerFragment.newInstance().mode(0)
dialog.listener = object : DateTimePickerFragment.OnClickListener {
override fun onClickListener(selectTime: String) {
val sendDate = DateUtils.str2Date(selectTime, "yyyy-MM-dd HH:mm")
val sendDate = DateUtils.str2Date(selectTime, dateShowFormat)
if (sendDate <= Date()) {
obtainMessageViewModel.updateMessageSendTime(DateUtils.date2Str(Date(), "yyyy-MM-dd HH:mm:ss"))
obtainMessageViewModel.updateMessageSendTime(DateUtils.date2Str(Date(), dateSendFormat))
} else {
obtainMessageViewModel.updateMessageSendTime(DateUtils.date2Str(sendDate, "yyyy-MM-dd HH:mm:ss"))
obtainMessageViewModel.updateMessageSendTime(DateUtils.date2Str(sendDate, dateSendFormat))
}
}
@ -231,10 +242,9 @@ class ObtainMessageFragment: Fragment() {
photoHelper.setCrop(false).selectAudio(activity!!)
}
// 开始录音
binding.btnStartRecord.setOnClickListener {
binding.btnStartRecord.setOnTouchListener { view, motionEvent ->
// 申请权限
XXPermissions.with(this)
XXPermissions.with(this@ObtainMessageFragment)
// 申请单个权限
.permission(Permission.RECORD_AUDIO)
.request(object : OnPermissionCallback {
@ -243,17 +253,23 @@ class ObtainMessageFragment: Fragment() {
Toast.makeText(activity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
return
}
if (it.isSelected) {
it.isSelected = false
val recorderAudioPath = recorderLifecycleObserver.stopAndReleaseRecorder()
if (File(recorderAudioPath).exists()) {
obtainMessageViewModel.updateMessageAudio(recorderAudioPath)
when(motionEvent.action) {
MotionEvent.ACTION_DOWN-> {
// 申请权限
recorderLifecycleObserver.initAndStartRecorder()
ToastUtils.showToast("开始录音!")
false
}
MotionEvent.ACTION_UP -> {
val recorderAudioPath = recorderLifecycleObserver.stopAndReleaseRecorder()
if (File(recorderAudioPath).exists()) {
obtainMessageViewModel.updateMessageAudio(recorderAudioPath)
}
false
}
else -> {
false
}
} else{
it.isSelected = true
recorderLifecycleObserver.initAndStartRecorder()
}
}
@ -268,6 +284,7 @@ class ObtainMessageFragment: Fragment() {
}
}
})
false
}
// 获取照片文件和音频文件
@ -296,8 +313,15 @@ class ObtainMessageFragment: Fragment() {
if (!it.absolutePath.equals(file?.absolutePath)) {
it?.delete()
}
// 跳转回原Fragment展示拍摄的照片
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(file!!.absolutePath)
// 如果当前文件不在camera缓存文件夹下则移动该文件
if (!file!!.parentFile.absolutePath.equals(SystemConstant.CameraFolder)) {
FileUtils.renameFile(file.absolutePath, File(SystemConstant.CameraFolder, fileName).absolutePath)
// 跳转回原Fragment展示拍摄的照片
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(File(SystemConstant.CameraFolder, fileName).absolutePath)
} else {
// 跳转回原Fragment展示拍摄的照片
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(file!!.absolutePath)
}
}
override fun onError(e: Throwable) {
@ -306,7 +330,12 @@ class ObtainMessageFragment: Fragment() {
}).launch()
} else if (fileName.endsWith(".mp3")||fileName.endsWith(".wav")||fileName.endsWith(".amr")||fileName.endsWith(".m4a")) {
ToastUtils.showToast(it.absolutePath)
obtainMessageViewModel.updateMessageAudio(it.absolutePath)
if (!it.parentFile.parentFile.absolutePath.equals(SystemConstant.SoundFolder)) {
FileUtils.renameFile(it.absolutePath, File(SystemConstant.SoundFolder, fileName).absolutePath)
obtainMessageViewModel.updateMessageAudio(File(SystemConstant.SoundFolder, fileName).absolutePath)
} else {
obtainMessageViewModel.updateMessageAudio(it.absolutePath)
}
}
}
@ -315,76 +344,108 @@ class ObtainMessageFragment: Fragment() {
ToastUtils.showToast(it.message)
}
binding.voicePlayerView.setOnClickListener {
// 判断当前播放的文件是否在缓存文件夹内,如果不在首先下载该文件
val fileUrl = obtainMessageViewModel.getMessageLiveData().value!!.mediaUrl!!
val localFile = obtainMessageViewModel.getLocalFileFromNetUrl(fileUrl, AttachmentType.AUDIO)
if (!localFile.exists()) {
obtainMessageViewModel.downLoadFile(fileUrl, localFile, object: DownloadCallback {
override fun progress(progress: Int) {
}
override fun error(throwable: Throwable) {
}
override fun success(file: File) {
binding.voicePlayerView.setAudio(localFile.absolutePath)
}
})
} else {
binding.voicePlayerView.setAudio(localFile.absolutePath)
}
}
binding.btnObtainMessageBack.setOnClickListener {
Navigation.findNavController(it).popBackStack()
}
binding.btnObtainMessageConfirm.setOnClickListener {
var checkResult = true
val toolTipBackColor = ResourceUtils.getColor(R.color.teal_200)
val toolTipTextColor = ResourceUtils.getColor(R.color.black)
// 检查当前输入数据
val messageData = obtainMessageViewModel.getMessageLiveData().value
if (messageData?.title?.isEmpty() == true) {
if (messageData?.name?.isEmpty() == true) {
val toolTipRelativeLayout =
binding.ttTitle
val toolTip = ToolTip()
.withText("请输入问候信息")
.withColor(R.color.white)
.withTextColor(R.color.black)
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tiLayoutTitle)
checkResult = false
}
var hasPic = false
var hasAudio = false
for (attachment in messageData?.attachment!!) {
if (attachment.attachmentType == AttachmentType.PIC) {
hasPic = true
}
if (attachment.attachmentType == AttachmentType.AUDIO) {
hasAudio = true
} else {
if (messageData?.name!!.length>10) {
val toolTipRelativeLayout =
binding.ttTitle
val toolTip = ToolTip()
.withText("问候信息长度不能超过10")
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tiLayoutTitle)
checkResult = false
}
}
if (!hasPic) {
if (messageData?.imageUrl?.isEmpty() == true) {
val toolTipRelativeLayout =
binding.ttPic
val toolTip = ToolTip()
.withText("需要提供照片文件")
.withColor(R.color.white)
.withTextColor(R.color.black)
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tvUploadPic)
checkResult = false
}
if (!hasAudio) {
if (messageData?.mediaUrl?.isEmpty() == true) {
val toolTipRelativeLayout =
binding.ttAudio
val toolTip = ToolTip()
.withText("需要提供音频文件")
.withColor(R.color.white)
.withTextColor(R.color.black)
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tvUploadPic)
checkResult = false
}
if (messageData?.fromId?.isEmpty()==true) {
if (messageData?.who?.isEmpty()==true) {
val toolTipRelativeLayout =
binding.ttSendFrom
val toolTip = ToolTip()
.withText("请输入您的名称")
.withColor(R.color.white)
.withTextColor(R.color.black)
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.edtSendFrom)
checkResult = false
}
if (messageData?.toId?.isEmpty()==true) {
if (messageData?.toWho?.isEmpty()==true) {
val toolTipRelativeLayout =
binding.ttSendTo
val toolTip = ToolTip()
.withText("请选择要发送的车辆")
.withColor(R.color.white)
.withTextColor(R.color.black)
.withColor(toolTipBackColor)
.withTextColor(toolTipTextColor)
.withoutShadow()
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
toolTipRelativeLayout.showToolTipForView(toolTip, binding.edtSendTo)
checkResult = false
@ -393,10 +454,13 @@ class ObtainMessageFragment: Fragment() {
if (checkResult) { // 检查通过
// 检查attachment是否为本地数据如果是本地则弹出对话框尝试上传
val localAttachmentList = mutableListOf<Attachment>()
for (attachment in messageData?.attachment!!) {
if (!attachment.pathUrl.startsWith("http")) {
localAttachmentList.add(attachment)
}
if (messageData?.imageUrl?.startsWith("http") == false) {
val imageAttachment = Attachment("", messageData.imageUrl!!, AttachmentType.PIC)
localAttachmentList.add(imageAttachment)
}
if (messageData?.mediaUrl?.startsWith("http") == false) {
val audioAttachment = Attachment("", messageData.mediaUrl!!, AttachmentType.AUDIO)
localAttachmentList.add(audioAttachment)
}
if (localAttachmentList.isNotEmpty()) {
MaterialAlertDialogBuilder(context!!)
@ -408,21 +472,50 @@ class ObtainMessageFragment: Fragment() {
obtainMessageViewModel.uploadAttachment(File(attachment.pathUrl), attachment.attachmentType)
}
})
.setNegativeButton("取消", DialogInterface.OnClickListener {
dialogInterface, i -> dialogInterface.dismiss()
})
.show()
return@setOnClickListener
}
// 检查发送时间
val sendDate = DateUtils.str2Date(messageData?.sendDate, dateSendFormat)
val cal = Calendar.getInstance()
cal.time = Date()
cal.set(Calendar.MINUTE, cal.get(Calendar.MINUTE)+1)
if (cal.time.time < sendDate.time) { // 发送时间设置小于当前时间1分钟前Toast提示用户并自动设置发送时间
messageData?.sendDate = DateUtils.date2Str(cal.time, dateSendFormat)
ToastUtils.showToast("自动调整发送时间为1分钟后发送")
}
// 开始网络提交数据
if (obtainMessageViewModel.getMessageLiveData().value?.netId!!.isEmpty()) { // 如果网络id为空则调用更新操作
if (obtainMessageViewModel.getMessageLiveData().value?.id==0L) { // 如果网络id为空则调用更新操作
obtainMessageViewModel.insertCardByApp()
} else {
obtainMessageViewModel.updateCardByApp()
}
}
}
// 点击照片名称
binding.tvPhotoName.setOnClickListener {
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.targetHeight = Utils.dp2px(context, 200F)
val viewDataList = listOf(viewData)
binding.imageViewer.overlayStatusBar(true) // ImageViewer 是否会占据 StatusBar 的空间
.viewData(viewDataList) // 图片数据
.imageLoader(PhotoLoader()) // 设置图片加载方式
.showIndex(true) // 是否显示图片索引默认为true
.watch(0) // 开启浏览
}
// 点击音频名称
binding.tvAudioName.setOnClickListener {
}
}
override fun onDestroyView() {

View File

@ -1,12 +1,18 @@
package com.navinfo.volvo.ui.fragments.message
import androidx.lifecycle.*
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.model.Attachment
import com.navinfo.volvo.model.AttachmentType
import com.navinfo.volvo.model.Message
import com.navinfo.volvo.utils.SystemConstant
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -17,72 +23,74 @@ import java.util.*
class ObtainMessageViewModel: ViewModel() {
private val msgLiveData: MutableLiveData<Message> by lazy {
MutableLiveData<Message>()
private val msgLiveData: MutableLiveData<GreetingMessage> by lazy {
MutableLiveData<GreetingMessage>()
}
fun setCurrentMessage(msg: Message) {
fun setCurrentMessage(msg: GreetingMessage) {
msgLiveData.postValue(msg)
}
fun getMessageLiveData(): MutableLiveData<Message> {
fun getMessageLiveData(): MutableLiveData<GreetingMessage> {
return msgLiveData
}
// 更新消息标题
fun updateMessageTitle(title: String) {
this.msgLiveData.value?.title = title
this.msgLiveData.value?.name = title
this.msgLiveData.postValue(this.msgLiveData.value)
}
// 更新消息附件中的照片文件
fun updateMessagePic(picUrl: String?) {
var hasPic = false
// var hasPic = false
for (attachment in this.msgLiveData.value!!.attachment) {
if (attachment.attachmentType == AttachmentType.PIC) {
if (picUrl==null||picUrl.isEmpty()) {
this.msgLiveData.value!!.attachment.remove(attachment)
} else {
attachment.pathUrl = picUrl
}
hasPic = true
}
}
if (!hasPic&&picUrl!=null) {
this.msgLiveData.value!!.attachment.add(Attachment(UUID.randomUUID().toString(), picUrl, AttachmentType.PIC))
}
this.msgLiveData.value?.imageUrl = picUrl
// for (attachment in this.msgLiveData.value!!.attachment) {
// if (attachment.attachmentType == AttachmentType.PIC) {
// if (picUrl==null||picUrl.isEmpty()) {
// this.msgLiveData.value!!.attachment.remove(attachment)
// } else {
// attachment.pathUrl = picUrl
// }
// hasPic = true
// }
// }
// if (!hasPic&&picUrl!=null) {
// this.msgLiveData.value!!.attachment.add(Attachment(UUID.randomUUID().toString(), picUrl, AttachmentType.PIC))
// }
this.msgLiveData.postValue(this.msgLiveData.value)
}
// 更新消息附件中的录音文件
fun updateMessageAudio(audioUrl: String?) {
var hasAudio = false
for (attachment in this.msgLiveData.value!!.attachment) {
if (attachment.attachmentType == AttachmentType.AUDIO) {
if (audioUrl==null||audioUrl.isEmpty()) {
this.msgLiveData.value!!.attachment.remove(attachment)
} else {
attachment.pathUrl = audioUrl
}
hasAudio = true
}
}
if (!hasAudio&&audioUrl!=null) {
this.msgLiveData.value!!.attachment.add(Attachment(UUID.randomUUID().toString(), audioUrl, AttachmentType.AUDIO))
}
// var hasAudio = false
// for (attachment in this.msgLiveData.value!!.attachment) {
// if (attachment.attachmentType == AttachmentType.AUDIO) {
// if (audioUrl==null||audioUrl.isEmpty()) {
// this.msgLiveData.value!!.attachment.remove(attachment)
// } else {
// attachment.pathUrl = audioUrl
// }
// hasAudio = true
// }
// }
// if (!hasAudio&&audioUrl!=null) {
// this.msgLiveData.value!!.attachment.add(Attachment(UUID.randomUUID().toString(), audioUrl, AttachmentType.AUDIO))
// }
this.msgLiveData.value?.mediaUrl = audioUrl
this.msgLiveData.postValue(this.msgLiveData.value)
}
// 更新发送人
fun updateMessageSendFrom(sendFrom: String) {
this.msgLiveData.value?.fromId = sendFrom
this.msgLiveData.value?.who = sendFrom
this.msgLiveData.postValue(this.msgLiveData.value)
}
// 更新接收人
fun updateMessageSendTo(sendTo: String) {
this.msgLiveData.value?.toId = sendTo
this.msgLiveData.value?.toWho = sendTo
this.msgLiveData.postValue(this.msgLiveData.value)
}
@ -92,30 +100,25 @@ class ObtainMessageViewModel: ViewModel() {
this.msgLiveData.postValue(this.msgLiveData.value)
}
// 获取照片url
fun getImageAttachment(attachementList: List<Attachment>): Attachment? {
for (attachment in attachementList) {
if (attachment.attachmentType == AttachmentType.PIC) {
return attachment
}
}
return null
}
// 获取音频url
fun getAudioAttachment(attachementList: List<Attachment>): Attachment? {
for (attachment in attachementList) {
if (attachment.attachmentType == AttachmentType.AUDIO) {
return attachment
}
}
return null
}
// 获取发送时间
fun getSendDate(){
}
// // 获取照片url
// fun getImageAttachment(attachementList: List<Attachment>): Attachment? {
// for (attachment in attachementList) {
// if (attachment.attachmentType == AttachmentType.PIC) {
// return attachment
// }
// }
// return null
// }
//
// // 获取音频url
// fun getAudioAttachment(attachementList: List<Attachment>): Attachment? {
// for (attachment in attachementList) {
// if (attachment.attachmentType == AttachmentType.AUDIO) {
// return attachment
// }
// }
// return null
// }
// 上传附件文件
fun uploadAttachment(attachmentFile: File, attachmentType: AttachmentType) {
@ -130,6 +133,21 @@ class ObtainMessageViewModel: ViewModel() {
if (result.code == 200) { // 请求成功
// 获取上传后的结果
val fileKey = result.data?.get("fileKey")
val newFileName = fileKey!!.substringAfterLast("/")
// 修改缓存文件名
if (attachmentType == AttachmentType.PIC) { // 修改当前文件在缓存文件夹的名称
val destFile = File(SystemConstant.CameraFolder, newFileName)
if (destFile.exists()) {
FileUtils.deleteFile(destFile)
}
FileUtils.renameFile(attachmentFile.absolutePath, destFile.absolutePath)
} else {
val destFile = File(SystemConstant.SoundFolder, newFileName)
if (destFile.exists()) {
FileUtils.deleteFile(destFile)
}
FileUtils.renameFile(attachmentFile.absolutePath, destFile.absolutePath)
}
if (fileKey!=null) {
downloadAttachment(fileKey, attachmentType)
}
@ -173,16 +191,41 @@ class ObtainMessageViewModel: ViewModel() {
}
}
fun downLoadFile(url: String, destFile: File, downloadCallback: DownloadCallback){
viewModelScope.launch {
DownloadManager.download(
url,
destFile
).collect {
when (it) {
is DownloadState.InProgress -> {
XLog.d("~~~", "download in progress: ${it.progress}.")
downloadCallback.progress(it.progress)
}
is DownloadState.Success -> {
XLog.d("~~~", "download finished.")
downloadCallback.success(it.file)
}
is DownloadState.Error -> {
XLog.d("~~~", "download error: ${it.throwable}.")
downloadCallback.error(it.throwable)
}
}
}
}
}
fun insertCardByApp() {
viewModelScope.launch {
try {
// TODO 首先保存数据到本地
val message = msgLiveData.value
val insertData = mapOf(
"name" to message?.title,
"imageUrl" to getImageAttachment(message?.attachment!!)?.pathUrl,
"mediaUrl" to getAudioAttachment(message?.attachment!!)?.pathUrl,
"who" to message?.fromId,
"toWho" to message?.toId,
"name" to message?.name,
"imageUrl" to message?.imageUrl,
"mediaUrl" to message?.mediaUrl,
"who" to message?.who,
"toWho" to message?.toWho,
"sendDate" to message?.sendDate
)
val result = NavinfoVolvoCall.getApi().insertCardByApp(insertData as Map<String, String>)
@ -190,8 +233,9 @@ class ObtainMessageViewModel: ViewModel() {
if (result.code == 200) { // 请求成功
// 获取上传后的结果
val netId = result.data
message.netId = netId!!
// 尝试保存数据到本地
message?.id = netId!!.toLong()
// TODO 尝试更新本地数据
} else {
ToastUtils.showToast(result.msg)
}
@ -206,21 +250,21 @@ class ObtainMessageViewModel: ViewModel() {
viewModelScope.launch {
try {
val message = msgLiveData.value
val insertData = mapOf(
"id" to message?.netId,
"name" to message?.title,
"imageUrl" to getImageAttachment(message?.attachment!!)?.pathUrl,
"mediaUrl" to getAudioAttachment(message?.attachment!!)?.pathUrl,
"who" to message?.fromId,
"toWho" to message?.toId,
val updateData = mapOf(
"id" to message?.id,
"name" to message?.name,
"imageUrl" to message?.imageUrl,
"mediaUrl" to message?.mediaUrl,
"who" to message?.who,
"toWho" to message?.toWho,
"sendDate" to message?.sendDate
)
val result = NavinfoVolvoCall.getApi().updateCardByApp(insertData as Map<String, String>)
val result = NavinfoVolvoCall.getApi().updateCardByApp(updateData as Map<String, String>)
XLog.d("updateCardByApp:${result.code}")
if (result.code == 200) { // 请求成功
// 获取上传后的结果
val netId = result.data
message.netId = netId!!
message?.id = netId!!.toLong()
// 尝试保存数据到本地
} else {
ToastUtils.showToast(result.msg)
@ -231,4 +275,20 @@ class ObtainMessageViewModel: ViewModel() {
}
}
}
/**
* 根据网络地址获取本地的缓存文件路径
* */
fun getLocalFileFromNetUrl(url: String, attachmentType: AttachmentType):File {
val folder = when(attachmentType) {
AttachmentType.PIC-> SystemConstant.CameraFolder
else -> SystemConstant.SoundFolder
}
var name = if (url.contains("?")) {
url.substring(url.lastIndexOf("/")+1, url.indexOf("?"))
} else {
url.substringAfterLast("/")
}
return File(folder, name)
}
}

View File

@ -0,0 +1,50 @@
package com.navinfo.volvo.util;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomViewTarget;
import com.bumptech.glide.request.transition.Transition;
import indi.liyi.viewer.ImageLoader;
public class PhotoLoader extends ImageLoader {
@Override
public void displayImage(final Object src, ImageView imageView, final LoadCallback callback) {
Glide.with(imageView.getContext())
.load(src)
.into(new CustomViewTarget<ImageView, Drawable>(imageView) {
@Override
protected void onResourceLoading(@Nullable Drawable placeholder) {
super.onResourceLoading(placeholder);
if(callback!=null){
callback.onLoadStarted(placeholder);
}
}
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
if(callback!=null) {
callback.onLoadSucceed(resource);
}
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
if(callback!=null) {
callback.onLoadFailed(errorDrawable);
}
}
@Override
protected void onResourceCleared(@Nullable Drawable placeholder) {
}
});
}
}

View File

@ -131,7 +131,7 @@ class EasyMediaFile {
* 选择文件
*/
private fun selectFileInternal(intent: Intent, activity: Activity, type: Int) {
val resolveInfoList = activity.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
var resolveInfoList = activity.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
if (resolveInfoList.isEmpty()) {
error?.invoke(IllegalStateException("No Activity found to handle Intent "))
} else {

View File

@ -121,9 +121,10 @@
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv_photo_name"
android:layout_width="wrap_content"
android:textColor="@android:color/holo_blue_dark"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""></com.google.android.material.textview.MaterialTextView>
android:layout_weight="1"></com.google.android.material.textview.MaterialTextView>
<Space
android:layout_width="@dimen/default_widget_padding"
android:layout_height="wrap_content"></Space>
@ -147,7 +148,6 @@
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_pic"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
@ -156,6 +156,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginVertical="@dimen/default_widget_padding"
android:orientation="horizontal">
<TextView
@ -180,7 +181,7 @@
android:layout_height="wrap_content"
style="@style/Widget.Material3.Button.ElevatedButton"
app:icon="@drawable/ic_baseline_fiber_manual_record_24"
android:text="录"
android:text="长按录音"
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
<Space
android:layout_width="@dimen/default_widget_padding"
@ -204,8 +205,10 @@
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv_audio_name"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="@android:color/holo_blue_dark"
android:layout_weight="1"
android:text=""></com.google.android.material.textview.MaterialTextView>
<Space
android:layout_width="@dimen/default_widget_padding"
@ -219,16 +222,36 @@
</LinearLayout>
</LinearLayout>
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/img_sound_play"
android:layout_width="wrap_content"
<!--增加音频播放按钮-->
<LinearLayout
android:id="@+id/ll_audio_play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/ic_baseline_volume_up_24"
android:layout_gravity="center"
android:visibility="gone"
android:layout_gravity="center"></com.google.android.material.imageview.ShapeableImageView>
android:orientation="horizontal">
<me.jagar.chatvoiceplayerlibrary.VoicePlayerView
android:id="@+id/voicePlayerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:enableVisualizer="true"
app:visualizationPlayedColor="@color/teal_200"
app:visualizationNotPlayedColor="@color/teal_700"
app:playPauseBackgroundColor="@color/teal_700"
app:timingBackgroundColor="@color/purple_200"
app:seekBarProgressColor="@color/purple_500"
app:showShareButton="false"
app:shareCornerRadius="100"
app:playPauseCornerRadius="100"
app:showTiming="true"
app:viewCornerRadius="100"
app:viewBackground="@android:color/transparent"
app:progressTimeColor="@color/purple_500"
app:seekBarThumbColor="@color/teal_200"/>
</LinearLayout>
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_audio"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
@ -283,7 +306,6 @@
<com.nhaarman.supertooltips.ToolTipRelativeLayout
android:id="@+id/tt_send_from"
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
@ -379,4 +401,11 @@
android:layout_weight="1"
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>