Refactored the LocalFileTransferFragment functionality to align with the Compose UI.

* Optimized the UI rendering logic to render only the necessary components, avoiding unnecessary recompositions.
* Utilized dimensions from `ComposeDimens` for better maintainability and consistency.
This commit is contained in:
MohitMaliFtechiz 2025-04-18 13:12:57 +05:30 committed by Kelson
parent 199f008811
commit 361fbbc0bd
4 changed files with 120 additions and 109 deletions

View File

@ -46,14 +46,16 @@ import android.widget.FrameLayout
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ComposeView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.cachedComponent
import org.kiwix.kiwixmobile.core.R.dimen
@ -64,19 +66,21 @@ import org.kiwix.kiwixmobile.core.base.BaseFragment
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isLandScapeMode
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isTablet
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.popNavigationBackstack
import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.navigateToAppSettings
import org.kiwix.kiwixmobile.core.ui.components.NavigationIcon
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem.Vector
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog
import org.kiwix.kiwixmobile.core.utils.files.Log
import org.kiwix.kiwixmobile.databinding.FragmentLocalFileTransferBinding
import org.kiwix.kiwixmobile.core.page.SEARCH_ICON_TESTING_TAG
import org.kiwix.kiwixmobile.localFileTransfer.WifiDirectManager.Companion.getDeviceStatus
import org.kiwix.kiwixmobile.localFileTransfer.adapter.WifiP2pDelegate
import org.kiwix.kiwixmobile.localFileTransfer.adapter.WifiPeerListAdapter
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence
import uk.co.deanwild.materialshowcaseview.ShowcaseConfig
import javax.inject.Inject
@ -113,34 +117,52 @@ class LocalFileTransferFragment :
@Inject
lateinit var sharedPreferenceUtil: SharedPreferenceUtil
private var fileListAdapter: FileListAdapter? = null
private var wifiPeerListAdapter: WifiPeerListAdapter? = null
private var fragmentLocalFileTransferBinding: FragmentLocalFileTransferBinding? = null
private var materialShowCaseSequence: MaterialShowcaseSequence? = null
private var searchIconView: View? = null
private val deviceName = mutableStateOf("")
private val isPeerSearching = mutableStateOf(false)
private val peerDeviceList = mutableStateOf(emptyList<WifiP2pDevice>())
private val transferFileList = mutableStateOf(emptyList<FileItem>())
private var composeView: ComposeView? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentLocalFileTransferBinding =
FragmentLocalFileTransferBinding.inflate(inflater, container, false)
return fragmentLocalFileTransferBinding?.root
): View? = ComposeView(requireContext()).also {
composeView = it
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupMenu()
// setupMenu()
val activity = requireActivity() as CoreMainActivity
val filesForTransfer = getFilesForTransfer()
val isReceiver = filesForTransfer.isEmpty()
setupToolbar(view, activity, isReceiver)
wifiPeerListAdapter = WifiPeerListAdapter(WifiP2pDelegate(wifiDirectManager::sendToDevice))
setupPeerDevicesList(activity)
composeView?.setContent {
LocalFileTransferScreen(
deviceName = deviceName.value,
toolbarTitle = if (isReceiver) {
R.string.receive_files_title
} else {
R.string.send_files_title
},
isPeerSearching = isPeerSearching.value,
peerDeviceList = peerDeviceList.value,
transferFileList = transferFileList.value,
actionMenuItems = actionMenuItem(),
onDeviceItemClick = { wifiDirectManager.sendToDevice(it) },
navigationIcon = {
NavigationIcon(
iconItem = IconItem.Drawable(drawable.ic_close_white_24dp),
onClick = { activity.popNavigationBackstack() }
)
}
)
}
displayFileTransferProgress(filesForTransfer)
@ -164,6 +186,15 @@ class LocalFileTransferFragment :
}
}
private fun actionMenuItem() = listOf(
ActionMenuItem(
Vector(Icons.Default.Search),
string.search_label,
{ onSearchMenuClicked() },
testingTag = SEARCH_ICON_TESTING_TAG
)
)
private fun getShowCaseViewWidth(): Int {
return when {
requireActivity().isTablet() -> {
@ -281,53 +312,24 @@ class LocalFileTransferFragment :
}
}
private fun setupPeerDevicesList(activity: CoreMainActivity) {
fragmentLocalFileTransferBinding?.listPeerDevices?.apply {
adapter = wifiPeerListAdapter
layoutManager = LinearLayoutManager(activity)
setHasFixedSize(true)
}
}
private fun setupToolbar(view: View, activity: CoreMainActivity, isReceiver: Boolean) {
val toolbar: Toolbar = view.findViewById(R.id.toolbar)
toolbar.apply {
activity.setSupportActionBar(this)
title =
if (isReceiver) {
getString(R.string.receive_files_title)
} else {
getString(R.string.send_files_title)
}
setNavigationIcon(drawable.ic_close_white_24dp)
// set the contentDescription to navigation back button
getToolbarNavigationIcon()?.setToolTipWithContentDescription(
getString(string.toolbar_back_button_content_description)
)
setNavigationOnClickListener { activity.popNavigationBackstack() }
}
}
private fun getFilesForTransfer() =
LocalFileTransferFragmentArgs.fromBundle(requireArguments()).uris?.map(::FileItem).orEmpty()
private fun showPeerDiscoveryProgressBar() { // Setup UI for searching peers
fragmentLocalFileTransferBinding?.progressBarSearchingPeers?.visibility = View.VISIBLE
fragmentLocalFileTransferBinding?.listPeerDevices?.visibility = View.INVISIBLE
fragmentLocalFileTransferBinding?.textViewEmptyPeerList?.visibility = View.INVISIBLE
isPeerSearching.value = true
}
// From WifiDirectManager.Callbacks interface
override fun onUserDeviceDetailsAvailable(userDevice: WifiP2pDevice?) {
// Update UI with user device's details
if (userDevice != null) {
fragmentLocalFileTransferBinding?.textViewDeviceName?.text = userDevice.deviceName
deviceName.value = userDevice.deviceName
Log.d(TAG, getDeviceStatus(userDevice.status))
}
}
override fun onConnectionToPeersLost() {
wifiPeerListAdapter?.items = emptyList()
peerDeviceList.value = emptyList()
}
override fun onFilesForTransferAvailable(filesForTransfer: List<FileItem>) {
@ -335,23 +337,17 @@ class LocalFileTransferFragment :
}
private fun displayFileTransferProgress(filesToSend: List<FileItem>) {
fileListAdapter = FileListAdapter(filesToSend)
fragmentLocalFileTransferBinding?.recyclerViewTransferFiles?.apply {
adapter = fileListAdapter
layoutManager =
LinearLayoutManager(requireActivity())
}
transferFileList.value = filesToSend
}
override fun onFileStatusChanged(itemIndex: Int) {
fileListAdapter?.notifyItemChanged(itemIndex)
// fileListAdapter?.notifyItemChanged(itemIndex)
}
override fun updateListOfAvailablePeers(peers: WifiP2pDeviceList) {
val deviceList: List<WifiP2pDevice> = ArrayList<WifiP2pDevice>(peers.deviceList)
fragmentLocalFileTransferBinding?.progressBarSearchingPeers?.visibility = View.GONE
fragmentLocalFileTransferBinding?.listPeerDevices?.visibility = View.VISIBLE
wifiPeerListAdapter?.items = deviceList
isPeerSearching.value = false
peerDeviceList.value = deviceList
if (deviceList.isEmpty()) {
Log.d(TAG, "No devices found")
}
@ -533,8 +529,6 @@ class LocalFileTransferFragment :
override fun onDestroyView() {
wifiDirectManager.stopWifiDirectManager()
wifiDirectManager.callbacks = null
fragmentLocalFileTransferBinding?.root?.removeAllViews()
fragmentLocalFileTransferBinding = null
searchIconView = null
materialShowCaseSequence = null
super.onDestroyView()

View File

@ -52,8 +52,6 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.kiwix.kiwixmobile.R.drawable
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.main.DELETE_MENU_BUTTON_TESTING_TAG
@ -64,23 +62,32 @@ import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem.Vector
import org.kiwix.kiwixmobile.core.ui.theme.DodgerBlue
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIFTEEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FILE_FOR_TRANSFER_TEXT_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FILE_ITEM_ICON_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FILE_ITEM_TEXT_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIVE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.NEARBY_DEVICES_TEXT_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.NEARBY_DEVICE_LIST_HEIGHT
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.NO_DEVICE_FOUND_TEXT_PADDING
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.PEER_DEVICE_ITEM_TEXT_SIZE
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.YOUR_DEVICE_TEXT_SIZE
import org.kiwix.kiwixmobile.localFileTransfer.FileItem.FileStatus.ERROR
import org.kiwix.kiwixmobile.localFileTransfer.FileItem.FileStatus.SENDING
import org.kiwix.kiwixmobile.localFileTransfer.FileItem.FileStatus.SENT
import org.kiwix.kiwixmobile.localFileTransfer.FileItem.FileStatus.TO_BE_SENT
@Preview(device = "spec:width=411dp,height=891dp")
@Preview(device = "id:Nexus S")
@Composable
fun Preview() {
LocalFileTransferScreen(
deviceName = "Google Pixel 7a",
toolbarTitle = org.kiwix.kiwixmobile.R.string.receive_files_title,
isSearching = false,
peerDevices = listOf(WifiP2pDevice().apply { deviceName = "Redmi note 9" }),
transferFiles = listOf(
isPeerSearching = true,
peerDeviceList = listOf(WifiP2pDevice().apply { deviceName = "Redmi note 9" }),
transferFileList = listOf(
FileItem("DemoFile.zim")
),
actionMenuItems = listOf(
@ -105,9 +112,9 @@ fun Preview() {
fun LocalFileTransferScreen(
deviceName: String,
@StringRes toolbarTitle: Int,
isSearching: Boolean,
peerDevices: List<WifiP2pDevice>,
transferFiles: List<FileItem>,
isPeerSearching: Boolean,
peerDeviceList: List<WifiP2pDevice>,
transferFileList: List<FileItem>,
actionMenuItems: List<ActionMenuItem>,
onDeviceItemClick: (WifiP2pDevice) -> Unit,
navigationIcon: @Composable () -> Unit
@ -133,22 +140,22 @@ fun LocalFileTransferScreen(
thickness = ONE_DP,
modifier = Modifier.padding(horizontal = FIVE_DP)
)
NearbyDevicesSection(peerDevices, isSearching, onDeviceItemClick)
NearbyDevicesSection(peerDeviceList, isPeerSearching, onDeviceItemClick)
HorizontalDivider(
color = DodgerBlue,
thickness = ONE_DP,
modifier = Modifier
.padding(horizontal = FIVE_DP)
)
TransferFilesSection(transferFiles)
TransferFilesSection(transferFileList)
}
}
}
@Composable
fun NearbyDevicesSection(
peerDevices: List<WifiP2pDevice>,
isSearching: Boolean,
peerDeviceList: List<WifiP2pDevice>,
isPeerSearching: Boolean,
onDeviceItemClick: (WifiP2pDevice) -> Unit
) {
Column(
@ -158,7 +165,7 @@ fun NearbyDevicesSection(
) {
Text(
text = stringResource(R.string.nearby_devices),
fontSize = 16.sp,
fontSize = NEARBY_DEVICES_TEXT_SIZE,
fontFamily = FontFamily.Monospace,
modifier = Modifier
.fillMaxWidth()
@ -167,29 +174,27 @@ fun NearbyDevicesSection(
textAlign = TextAlign.Center
)
if (isSearching) {
ContentLoadingProgressBar(
when {
isPeerSearching -> ContentLoadingProgressBar(
modifier = Modifier
.padding(50.dp)
.padding(NO_DEVICE_FOUND_TEXT_PADDING)
.align(Alignment.CenterHorizontally)
)
}
if (peerDevices.isEmpty() && !isSearching) {
Text(
peerDeviceList.isEmpty() -> Text(
text = stringResource(R.string.no_devices_found),
modifier = Modifier
.padding(50.dp)
.padding(NO_DEVICE_FOUND_TEXT_PADDING)
.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center
)
} else {
LazyColumn(
else -> LazyColumn(
modifier = Modifier
.fillMaxWidth()
.defaultMinSize(minHeight = NEARBY_DEVICE_LIST_HEIGHT)
) {
items(peerDevices) { device ->
items(peerDeviceList) { device ->
PeerDeviceItem(device, onDeviceItemClick)
}
}
@ -198,11 +203,11 @@ fun NearbyDevicesSection(
}
@Composable
private fun TransferFilesSection(transferFiles: List<FileItem>) {
private fun TransferFilesSection(transferFileList: List<FileItem>) {
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = stringResource(R.string.files_for_transfer),
fontSize = 16.sp,
fontSize = FILE_FOR_TRANSFER_TEXT_SIZE,
fontFamily = FontFamily.Monospace,
modifier = Modifier
.fillMaxWidth()
@ -211,7 +216,7 @@ private fun TransferFilesSection(transferFiles: List<FileItem>) {
)
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(transferFiles) { file ->
items(transferFileList) { file ->
TransferFileItem(file)
}
}
@ -220,19 +225,19 @@ private fun TransferFilesSection(transferFiles: List<FileItem>) {
@Composable
private fun YourDeviceHeader(deviceName: String) {
Column(modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp)) {
Column(modifier = Modifier.padding(horizontal = FIFTEEN_DP, vertical = FIVE_DP)) {
Text(
text = stringResource(R.string.your_device),
fontStyle = FontStyle.Italic,
fontSize = 13.sp,
fontSize = YOUR_DEVICE_TEXT_SIZE,
modifier = Modifier
.padding(top = 5.dp, bottom = 1.dp)
.padding(top = FIVE_DP, bottom = ONE_DP)
)
val contentDescription = stringResource(R.string.device_name)
Text(
text = deviceName,
fontWeight = FontWeight.Bold,
fontSize = 17.sp,
fontSize = PEER_DEVICE_ITEM_TEXT_SIZE,
modifier = Modifier
.minimumInteractiveComponentSize()
.semantics { this.contentDescription = contentDescription }
@ -250,7 +255,6 @@ fun TransferFileItem(
.padding(TEN_DP),
verticalAlignment = Alignment.CenterVertically
) {
// File name
Text(
text = fileItem.fileName,
fontSize = FILE_ITEM_TEXT_SIZE,
@ -259,20 +263,28 @@ fun TransferFileItem(
.padding(horizontal = FIVE_DP, vertical = ONE_DP)
)
if (true) {
ContentLoadingProgressBar(
modifier = Modifier
.size(FILE_ITEM_ICON_SIZE)
.padding(horizontal = FIVE_DP, vertical = ONE_DP)
)
} else {
Icon(
painter = painterResource(drawable.ic_baseline_wait_24px),
contentDescription = stringResource(R.string.status),
modifier = Modifier
.size(FILE_ITEM_ICON_SIZE)
.padding(horizontal = FIVE_DP, vertical = ONE_DP)
)
val modifier = Modifier
.size(FILE_ITEM_ICON_SIZE)
.padding(horizontal = FIVE_DP, vertical = ONE_DP)
when (fileItem.fileStatus) {
SENDING -> ContentLoadingProgressBar(modifier)
TO_BE_SENT,
SENT,
ERROR -> {
val iconRes = when (fileItem.fileStatus) {
FileItem.FileStatus.TO_BE_SENT -> drawable.ic_baseline_wait_24px
FileItem.FileStatus.SENT -> drawable.ic_baseline_check_24px
FileItem.FileStatus.ERROR -> drawable.ic_baseline_error_24px
else -> error("Unhandled status: ${fileItem.fileStatus}")
}
Icon(
painter = painterResource(iconRes),
contentDescription = stringResource(R.string.status),
modifier = modifier
)
}
}
}
}

View File

@ -53,7 +53,7 @@ import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import javax.inject.Inject
const val SEARCH_ICON_TESTING_TAG = "search"
const val SEARCH_ICON_TESTING_TAG = "searchIconTestingTag"
const val DELETE_MENU_ICON_TESTING_TAG = "deleteMenuIconTestingTag"
abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActivityExtensions {

View File

@ -44,6 +44,7 @@ object ComposeDimens {
val TWENTY_DP = 20.dp
val SEVENTEEN_DP = 17.dp
val SIXTEEN_DP = 16.dp
val FIFTEEN_DP = 15.dp
val TWELVE_DP = 12.dp
val TEN_DP = 10.dp
val EIGHT_DP = 8.dp
@ -117,5 +118,9 @@ object ComposeDimens {
val PEER_DEVICE_ITEM_TEXT_SIZE = 17.sp
val FILE_ITEM_TEXT_SIZE = 14.sp
val FILE_ITEM_ICON_SIZE = 24.dp
val NEARBY_DEVICE_LIST_HEIGHT = 180.dp
val NEARBY_DEVICE_LIST_HEIGHT = 160.dp
val NO_DEVICE_FOUND_TEXT_PADDING = 50.dp
val YOUR_DEVICE_TEXT_SIZE = 13.sp
val FILE_FOR_TRANSFER_TEXT_SIZE = 16.sp
val NEARBY_DEVICES_TEXT_SIZE = 16.sp
}