diff --git a/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/LocalFileTransferFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/LocalFileTransferFragment.kt index 6d37a45fb..8a30052e0 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/LocalFileTransferFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/LocalFileTransferFragment.kt @@ -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()) + private val transferFileList = mutableStateOf(emptyList()) + 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) { @@ -335,23 +337,17 @@ class LocalFileTransferFragment : } private fun displayFileTransferProgress(filesToSend: List) { - 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 = ArrayList(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() diff --git a/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/LocalFileTransferScreen.kt b/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/LocalFileTransferScreen.kt index 93338729b..352be5e7a 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/LocalFileTransferScreen.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/LocalFileTransferScreen.kt @@ -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, - transferFiles: List, + isPeerSearching: Boolean, + peerDeviceList: List, + transferFileList: List, actionMenuItems: List, 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, - isSearching: Boolean, + peerDeviceList: List, + 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) { +private fun TransferFilesSection(transferFileList: List) { 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) { ) LazyColumn(modifier = Modifier.fillMaxSize()) { - items(transferFiles) { file -> + items(transferFileList) { file -> TransferFileItem(file) } } @@ -220,19 +225,19 @@ private fun TransferFilesSection(transferFiles: List) { @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 + ) + } } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt index bcb4149ff..6fd658b8d 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt @@ -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 { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt index a2bcd77df..53023e7a3 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/ComposeDimens.kt @@ -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 }