refactor: jetpack compose (3/n)
This commit is contained in:
@@ -57,6 +57,8 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation("androidx.navigation:navigation-compose:2.7.7")
|
||||||
|
|
||||||
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
|
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
|
||||||
implementation 'androidx.preference:preference:1.2.1'
|
implementation 'androidx.preference:preference:1.2.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
|
||||||
|
|||||||
@@ -2,42 +2,19 @@ package com.lsp.view
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.IBinder
|
|
||||||
import com.lsp.view.service.DownloadService.DownloadBinder
|
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import android.content.Intent
|
|
||||||
import android.util.Log
|
|
||||||
import com.lsp.view.service.DownloadService
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
|
|
||||||
class YandViewApplication : Application(), DefaultLifecycleObserver {
|
class YandViewApplication : Application(), DefaultLifecycleObserver {
|
||||||
lateinit var downloadBinder:DownloadBinder
|
|
||||||
|
|
||||||
private val connection: ServiceConnection = object : ServiceConnection {
|
|
||||||
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
|
|
||||||
downloadBinder = iBinder as DownloadBinder
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onServiceDisconnected(componentName: ComponentName) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super<Application>.onCreate()
|
super<Application>.onCreate()
|
||||||
context = applicationContext
|
context = applicationContext
|
||||||
val serviceIntent = Intent(this, DownloadService::class.java)
|
|
||||||
bindService(serviceIntent, connection, BIND_AUTO_CREATE)
|
|
||||||
|
|
||||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy(owner: LifecycleOwner) {
|
|
||||||
super.onDestroy(owner)
|
|
||||||
unbindService(connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var context: Context? = null
|
var context: Context? = null
|
||||||
private set
|
private set
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.lsp.view.bean
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
open class Post(
|
open class Post(
|
||||||
open val postId: String,
|
open val postId: String,
|
||||||
@@ -11,7 +12,7 @@ open class Post(
|
|||||||
open val sampleHeight: Int,
|
open val sampleHeight: Int,
|
||||||
open val sampleWidth: Int,
|
open val sampleWidth: Int,
|
||||||
open val tags:String?
|
open val tags:String?
|
||||||
): Parcelable {
|
): Parcelable,Serializable {
|
||||||
constructor(parcel: Parcel) : this(
|
constructor(parcel: Parcel) : this(
|
||||||
parcel.readString()!!,
|
parcel.readString()!!,
|
||||||
parcel.readString()!!,
|
parcel.readString()!!,
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import retrofit2.converter.gson.GsonConverterFactory
|
|||||||
|
|
||||||
object ServiceCreator {
|
object ServiceCreator {
|
||||||
|
|
||||||
|
val CLIENT = OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BODY
|
||||||
|
}).build()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param serviceClass:所属类的Service接口
|
* @param serviceClass:所属类的Service接口
|
||||||
@@ -14,16 +17,11 @@ object ServiceCreator {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fun <T> create(serviceClass: Class<T>, source: String): T {
|
fun <T> create(serviceClass: Class<T>, source: String): T {
|
||||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
|
||||||
level = HttpLoggingInterceptor.Level.BODY
|
|
||||||
}
|
|
||||||
|
|
||||||
val client = OkHttpClient.Builder().addInterceptor(loggingInterceptor).build()
|
|
||||||
|
|
||||||
|
|
||||||
val retrofit = Retrofit.Builder()
|
val retrofit = Retrofit.Builder()
|
||||||
.baseUrl(source)
|
.baseUrl(source)
|
||||||
.client(client)
|
.client(CLIENT)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.build()
|
.build()
|
||||||
return retrofit.create(serviceClass)
|
return retrofit.create(serviceClass)
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import okhttp3.OkHttpClient
|
import com.lsp.view.repository.network.retrofit.ServiceCreator
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@@ -17,48 +21,47 @@ class DownloadService : Service() {
|
|||||||
|
|
||||||
class DownloadBinder(val context: Context) : Binder() {
|
class DownloadBinder(val context: Context) : Binder() {
|
||||||
|
|
||||||
fun downloadImage(fileUrl: String):Result<Serializable> {
|
fun downloadImage(fileUrl: String): Deferred<Result<Serializable>> {
|
||||||
|
return CoroutineScope(Dispatchers.IO).async {
|
||||||
val fileDir =
|
val fileDir =
|
||||||
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/")
|
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/")
|
||||||
if (!fileDir.exists()) {
|
if (!fileDir.exists()) {
|
||||||
fileDir.mkdirs()
|
fileDir.mkdirs()
|
||||||
}
|
|
||||||
val split = fileUrl.split("/")
|
|
||||||
val fileName = split[split.size - 1]
|
|
||||||
val file =
|
|
||||||
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/$fileName")
|
|
||||||
if (file.exists()) {
|
|
||||||
return Result.failure<Exception>(Exception("File exists"))
|
|
||||||
}
|
|
||||||
val fos = FileOutputStream(file)
|
|
||||||
try {
|
|
||||||
val client = OkHttpClient()
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(fileUrl)
|
|
||||||
.build()
|
|
||||||
val response = client.newCall(request).execute()
|
|
||||||
val responseData = response.body?.bytes()
|
|
||||||
return if (response.code == 200) {
|
|
||||||
fos.write(responseData)
|
|
||||||
//通知媒体更新
|
|
||||||
MediaScannerConnection.scanFile(
|
|
||||||
context, arrayOf(file.path),
|
|
||||||
null, null
|
|
||||||
)
|
|
||||||
Result.success("File download successfully")
|
|
||||||
} else {
|
|
||||||
file.delete()
|
|
||||||
Result.failure<Exception>(NetworkErrorException("Connection timed out"))
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
val split = fileUrl.split("/")
|
||||||
Result.failure<Exception>(e)
|
val fileName = split[split.size - 1]
|
||||||
} finally {
|
val file =
|
||||||
fos.close()
|
File("${Environment.getExternalStorageDirectory()}/${Environment.DIRECTORY_PICTURES}/LspMake/$fileName")
|
||||||
|
if (file.exists()) {
|
||||||
|
return@async Result.failure<Exception>(Exception("File exists"))
|
||||||
|
}
|
||||||
|
val fos = FileOutputStream(file)
|
||||||
|
try {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(fileUrl)
|
||||||
|
.build()
|
||||||
|
val response = ServiceCreator.CLIENT.newCall(request).execute()
|
||||||
|
val responseData = response.body?.bytes()
|
||||||
|
return@async if (response.code == 200) {
|
||||||
|
fos.write(responseData)
|
||||||
|
//通知媒体更新
|
||||||
|
MediaScannerConnection.scanFile(
|
||||||
|
context, arrayOf(file.path),
|
||||||
|
null, null
|
||||||
|
)
|
||||||
|
Result.success("File download successfully")
|
||||||
|
} else {
|
||||||
|
file.delete()
|
||||||
|
Result.failure<Exception>(NetworkErrorException("Connection timed out"))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure<Exception>(e)
|
||||||
|
} finally {
|
||||||
|
fos.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return@async Result.failure(Exception("Unknown error"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.failure(Exception("Unknown error"))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
package com.lsp.view.ui.compose
|
package com.lsp.view.ui.compose
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||||
@@ -18,9 +26,19 @@ import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
|||||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Star
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -29,39 +47,117 @@ import androidx.compose.runtime.snapshotFlow
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.lsp.view.bean.Post
|
import com.lsp.view.bean.Post
|
||||||
import com.lsp.view.ui.compose.theme.LspViewTheme
|
import com.lsp.view.ui.compose.theme.LspViewTheme
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import coil.compose.AsyncImage
|
||||||
import coil.compose.SubcomposeAsyncImage
|
import coil.compose.SubcomposeAsyncImage
|
||||||
import coil.compose.SubcomposeAsyncImageContent
|
import coil.compose.SubcomposeAsyncImageContent
|
||||||
|
import com.lsp.view.R
|
||||||
|
import com.lsp.view.service.DownloadService
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
private const val TAG = "Compose_PostActivity"
|
private const val TAG = "Compose_PostActivity"
|
||||||
|
|
||||||
class PostActivity : ComponentActivity() {
|
class PostActivity : ComponentActivity() {
|
||||||
|
lateinit var downloadBinder: DownloadService.DownloadBinder
|
||||||
|
val viewModel: PostViewModel by viewModels<PostViewModel>()
|
||||||
|
|
||||||
|
private val connection: ServiceConnection = object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
|
||||||
|
downloadBinder = iBinder as DownloadService.DownloadBinder
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(componentName: ComponentName) {}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
LspViewTheme {
|
LspViewTheme {
|
||||||
// A surface container using the 'background' color from the theme
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colorScheme.background
|
color = MaterialTheme.colorScheme.background
|
||||||
) {
|
) {
|
||||||
PostList()
|
App()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.downloadAction.observe(this){
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val result =
|
||||||
|
downloadBinder.downloadImage(viewModel.uiState.value.selectPost?.fileUrl ?: "")
|
||||||
|
.await()
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
if (result.isSuccess) {
|
||||||
|
Toast.makeText(
|
||||||
|
this@PostActivity,
|
||||||
|
"Download successful",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
this@PostActivity,
|
||||||
|
"Download fail",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val serviceIntent = Intent(this, DownloadService::class.java)
|
||||||
|
bindService(serviceIntent, connection, BIND_AUTO_CREATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
unbindService(connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const val NAV_ROUTE_POSTLIST_SCREEN = "NAV_ROUTE_POSTLIST_SCREEN"
|
||||||
|
const val NAV_ROUTE_DETAIL_SCREEN = "NAV_ROUTE_DETAIL_SCREEN"
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun App(viewModel: PostViewModel = viewModel()) {
|
||||||
|
val navController = rememberNavController()
|
||||||
|
|
||||||
|
NavHost(navController, startDestination = NAV_ROUTE_POSTLIST_SCREEN) {
|
||||||
|
composable(NAV_ROUTE_POSTLIST_SCREEN) {
|
||||||
|
PostListScreen(onNavigateToDetail = {
|
||||||
|
viewModel.uiState.value.selectPost = it
|
||||||
|
navController.navigate(NAV_ROUTE_DETAIL_SCREEN)
|
||||||
|
}, navController = navController, viewModel = viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(NAV_ROUTE_DETAIL_SCREEN) {
|
||||||
|
DetailScreen(navController = navController, viewModel = viewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PostList(viewModel: PostViewModel = viewModel()) {
|
fun PostListScreen(
|
||||||
val uiState by viewModel.uiState.collectAsState()
|
navController: NavController,
|
||||||
|
onNavigateToDetail: (Post) -> Unit,
|
||||||
|
viewModel: PostViewModel
|
||||||
|
) {
|
||||||
val postList by viewModel.postData.collectAsState()
|
val postList by viewModel.postData.collectAsState()
|
||||||
val listState = rememberLazyStaggeredGridState()
|
val listState = rememberLazyStaggeredGridState()
|
||||||
|
|
||||||
@@ -78,18 +174,22 @@ fun PostList(viewModel: PostViewModel = viewModel()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyVerticalStaggeredGrid(
|
LazyVerticalStaggeredGrid(
|
||||||
state = listState,
|
state = listState,
|
||||||
columns = StaggeredGridCells.Adaptive(200.dp),
|
columns = StaggeredGridCells.Adaptive(200.dp),
|
||||||
verticalItemSpacing = 4.dp,
|
verticalItemSpacing = 4.dp,
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
content = {
|
content = {
|
||||||
items(items=postList, key = {
|
items(items = postList, key = {
|
||||||
it.postId//提供key以保持列表顺序稳定
|
it.postId//提供key以保持列表顺序稳定
|
||||||
}) {
|
}) {
|
||||||
Row(Modifier.animateItemPlacement(tween(durationMillis = 250))) {
|
Row(Modifier.animateItemPlacement(tween(durationMillis = 250))) {
|
||||||
PostItem(it)
|
PostItem(
|
||||||
|
it,
|
||||||
|
clickable = {
|
||||||
|
onNavigateToDetail.invoke(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -99,19 +199,22 @@ fun PostList(viewModel: PostViewModel = viewModel()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PostItem(post:Post){
|
fun PostItem(post: Post, clickable: (Post) -> Unit) {
|
||||||
SubcomposeAsyncImage(
|
SubcomposeAsyncImage(
|
||||||
model = post.sampleUrl,
|
model = post.sampleUrl,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.FillWidth,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
loading ={
|
loading = {
|
||||||
Box(modifier = Modifier
|
Box(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.height(300.dp)) {
|
.fillMaxWidth()
|
||||||
|
.height(300.dp)
|
||||||
|
) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(50.dp)
|
.size(50.dp)
|
||||||
.align(Alignment.Center))
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
success = {
|
success = {
|
||||||
@@ -121,5 +224,43 @@ fun PostItem(post:Post){
|
|||||||
.clip(RoundedCornerShape(5.dp))
|
.clip(RoundedCornerShape(5.dp))
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
|
.clickable { clickable.invoke(post) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun DetailScreen(navController: NavController, viewModel: PostViewModel) {
|
||||||
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
Scaffold(topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
title = {},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = { navController.popBackStack() }) {
|
||||||
|
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "back")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = { viewModel.actionDownload()}) {
|
||||||
|
Icon(painter = painterResource(id = R.drawable.ic_twotone_arrow_downward_24), contentDescription = "Download")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
content = {
|
||||||
|
AsyncImage(
|
||||||
|
model = uiState.selectPost?.sampleUrl,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(top = it.calculateTopPadding())
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.lsp.view.ui.compose
|
package com.lsp.view.ui.compose
|
||||||
|
|
||||||
|
import com.lsp.view.bean.Post
|
||||||
|
|
||||||
data class PostUiState(
|
data class PostUiState(
|
||||||
var refresh:Boolean = false,
|
var refresh:Boolean = false,
|
||||||
var searchTarget: String = "",
|
var searchTarget: String = "",
|
||||||
val source: String = "",
|
val source: String = "",
|
||||||
val safeModel: Boolean = false, //安全模式
|
val safeModel: Boolean = false, //安全模式
|
||||||
var page:Int = 1,
|
var page:Int = 1,
|
||||||
|
var selectPost: Post? = null
|
||||||
)
|
)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.lsp.view.ui.compose
|
package com.lsp.view.ui.compose
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.lsp.view.bean.YandPost
|
import com.lsp.view.bean.YandPost
|
||||||
@@ -17,6 +18,7 @@ class PostViewModel:ViewModel() {
|
|||||||
private val repository by lazy {
|
private val repository by lazy {
|
||||||
PostRepository()
|
PostRepository()
|
||||||
}
|
}
|
||||||
|
val downloadAction = MutableLiveData<Unit>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initData()
|
initData()
|
||||||
@@ -26,6 +28,10 @@ class PostViewModel:ViewModel() {
|
|||||||
fetchPost()
|
fetchPost()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun actionDownload(){
|
||||||
|
downloadAction.postValue(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
fun fetchPost(){
|
fun fetchPost(){
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
_uiState.value.refresh = true
|
_uiState.value.refresh = true
|
||||||
|
|||||||
@@ -130,17 +130,6 @@ class ImageFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun download(fileUrl: String){
|
private fun download(fileUrl: String){
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
activityContext.viewModel.postNewToast("Start download")
|
|
||||||
val result = (activityContext.application as YandViewApplication).downloadBinder.downloadImage(fileUrl)
|
|
||||||
if (result.isSuccess){
|
|
||||||
activityContext.viewModel.postNewToast("Download successfully")
|
|
||||||
}else{
|
|
||||||
val exception = result.exceptionOrNull()
|
|
||||||
activityContext.viewModel.postNewToast(exception?.message.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user