├── .idea ├── .name ├── codeStyles │ └── codeStyleConfig.xml ├── compiler.xml ├── vcs.xml ├── render.experimental.xml ├── deploymentTargetDropDown.xml └── gradle.xml ├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── font │ │ │ │ ├── work_sans.ttf │ │ │ │ ├── work_sans_bold.ttf │ │ │ │ ├── work_sans_thin.ttf │ │ │ │ ├── work_sans_black.ttf │ │ │ │ ├── work_sans_light.ttf │ │ │ │ ├── work_sans_medium.ttf │ │ │ │ ├── work_sans_semi_bold.ttf │ │ │ │ ├── work_sans_extra_bold.ttf │ │ │ │ ├── work_sans_extra_light.ttf │ │ │ │ └── work_sans_font_family.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── drawable │ │ │ │ ├── apk_file_icon.xml │ │ │ │ ├── check_box_selector.xml │ │ │ │ ├── slide_down_indicator.xml │ │ │ │ ├── home_icon.xml │ │ │ │ ├── arrow_down.xml │ │ │ │ ├── ic_baseline_check_circle_24.xml │ │ │ │ ├── close_icon.xml │ │ │ │ ├── send_icon.xml │ │ │ │ ├── close_icon_24.xml │ │ │ │ ├── ic_baseline_radio_button_unchecked_24.xml │ │ │ │ ├── person_icon.xml │ │ │ │ ├── ic_baseline_play_circle_outline_24.xml │ │ │ │ ├── ic_baseline_arrow_circle_down_24.xml │ │ │ │ ├── navigate_next.xml │ │ │ │ ├── ic_baseline_folder_open_24.xml │ │ │ │ ├── ic_baseline_folder_open_24_two.xml │ │ │ │ ├── pad_lock_icon.xml │ │ │ │ ├── history_icon.xml │ │ │ │ ├── share_icon.xml │ │ │ │ ├── video_icon.xml │ │ │ │ ├── plain_file_icon.xml │ │ │ │ ├── globe_icon.xml │ │ │ │ ├── pdf.xml │ │ │ │ ├── ic_dat_file_logo.xml │ │ │ │ ├── text_file_icon.xml │ │ │ │ └── multi_colored_apple_icon.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── values-night │ │ │ │ └── colors.xml │ │ │ ├── values │ │ │ │ ├── attrs.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── colors.xml │ │ │ ├── layout │ │ │ │ ├── category_chip.xml │ │ │ │ ├── no_item_in_transfer_or_receive_layout.xml │ │ │ │ ├── fragment_break_connection.xml │ │ │ │ ├── fragment_audio.xml │ │ │ │ ├── fragment_app.xml │ │ │ │ ├── fragment_videos.xml │ │ │ │ ├── image_layout_item.xml │ │ │ │ ├── fragment_sent_data.xml │ │ │ │ ├── all_media_on_device.xml │ │ │ │ ├── image_transfer_layout_item.xml │ │ │ │ ├── fragment_received_data.xml │ │ │ │ ├── connected_to_peer_transfer_ongoing_persistent_bottom_sheet.xml │ │ │ │ ├── media_date_modified_header.xml │ │ │ │ ├── expanded_connected_to_peer_transfer_ongoing.xml │ │ │ │ ├── collapsed_connected_to_peer_transfer_receive_ongoing_layout.xml │ │ │ │ ├── fragment_files.xml │ │ │ │ ├── discovered_peer_layout_item.xml │ │ │ │ ├── zip_bolt_header_layout.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── folder_layout_item.xml │ │ │ │ ├── fragment_image.xml │ │ │ │ ├── expanded_bottom_sheet_layout_toolbar.xml │ │ │ │ ├── wifi_p2p_device_item_layout.xml │ │ │ │ ├── home_screen_recyclerview_layout_item.xml │ │ │ │ ├── application_layout_item.xml │ │ │ │ └── zip_bolt_send_file_header_layout.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── values-v23 │ │ │ │ └── themes.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── salesground │ │ │ │ └── zipbolt │ │ │ │ ├── ZipBoltApplication.kt │ │ │ │ ├── ui │ │ │ │ ├── recyclerview │ │ │ │ │ ├── RecyclerViewItemClickedListener.kt │ │ │ │ │ ├── DataToTransferRecyclerViewDiffUtil.kt │ │ │ │ │ ├── SentAndReceivedDataItemsViewPagerAdapter.kt │ │ │ │ │ ├── videoFragment │ │ │ │ │ │ └── VideoFragmentRecyclerViewAdapter.kt │ │ │ │ │ ├── audioFragment │ │ │ │ │ │ └── AudioFragmentRecyclerViewAdapter.kt │ │ │ │ │ ├── sentDataFragment │ │ │ │ │ │ └── viewHolders │ │ │ │ │ │ │ ├── image │ │ │ │ │ │ │ ├── ImageTransferWaitingLayoutItemViewHolder.kt │ │ │ │ │ │ │ └── ImageTransferCompleteLayoutViewHolder.kt │ │ │ │ │ │ │ ├── plainFile │ │ │ │ │ │ │ ├── PlainFileTransferWaitingLayoutItemViewHolder.kt │ │ │ │ │ │ │ ├── image │ │ │ │ │ │ │ │ ├── PlainImageFileTransferWaitingLayoutItemViewHolder.kt │ │ │ │ │ │ │ │ └── PlainImageFileTransferCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ ├── video │ │ │ │ │ │ │ │ ├── PlainVideoFileTransferWaitingLayoutItemViewHolder.kt │ │ │ │ │ │ │ │ └── PlainVideoFileTransferCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ ├── document │ │ │ │ │ │ │ │ ├── PlainDocumentFileTransferWaitingLayoutItemViewHolder.kt │ │ │ │ │ │ │ │ └── PlainDocumentFileTransferCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ └── PlainFileTransferCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ ├── directory │ │ │ │ │ │ │ ├── DirectoryTransferWaitingLayoutItemViewHolder.kt │ │ │ │ │ │ │ └── DirectoryTransferCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ ├── audio │ │ │ │ │ │ │ ├── AudioTransferWaitingLayoutItemViewHolder.kt │ │ │ │ │ │ │ └── AudioTransferCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ ├── video │ │ │ │ │ │ │ ├── VideoTransferWaitingLayoutItemViewHolder.kt │ │ │ │ │ │ │ └── VideoTransferCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ └── application │ │ │ │ │ │ │ ├── ApplicationTransferWaitingViewHolder.kt │ │ │ │ │ │ │ └── ApplicationTransferCompleteLayoutViewHolder.kt │ │ │ │ │ ├── imagefragment │ │ │ │ │ │ └── DateModifiedHeaderViewHolder.kt │ │ │ │ │ ├── applicationFragment │ │ │ │ │ │ └── ApplicationFragmentAppsDisplayRecyclerViewAdapter.kt │ │ │ │ │ ├── receivedDataFragment │ │ │ │ │ │ └── viewHolders │ │ │ │ │ │ │ ├── image │ │ │ │ │ │ │ └── ImageReceiveCompleteLayoutViewHolder.kt │ │ │ │ │ │ │ ├── plainFile │ │ │ │ │ │ │ ├── document │ │ │ │ │ │ │ │ └── PlainDocumentFileReceiveCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ ├── image │ │ │ │ │ │ │ │ └── PlainImageFileReceiveCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ └── video │ │ │ │ │ │ │ │ └── PlainVideoFileReceiveCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ ├── directory │ │ │ │ │ │ │ └── DirectoryReceiveCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ ├── audio │ │ │ │ │ │ │ └── AudioReceiveCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ ├── video │ │ │ │ │ │ │ └── VideoReceiveCompleteLayoutItemViewHolder.kt │ │ │ │ │ │ │ └── application │ │ │ │ │ │ │ └── ApplicationReceiveCompleteLayoutViewHolder.kt │ │ │ │ │ └── peersDiscoveryFragment │ │ │ │ │ │ ├── PeersDiscoveredRecyclerViewAdapter.kt │ │ │ │ │ │ └── DiscoveredPeerLayoutItemViewHolder.kt │ │ │ │ ├── fragments │ │ │ │ │ └── BreakConnectionFragment.kt │ │ │ │ ├── customviews │ │ │ │ │ ├── ConstraintLayoutWithTopBorderLine.kt │ │ │ │ │ ├── SelectableLinearLayout.kt │ │ │ │ │ ├── AnimatedLoadingTextView.kt │ │ │ │ │ ├── DividerLabel.kt │ │ │ │ │ └── SelectableConstraintLayout.kt │ │ │ │ ├── AllMediaOnDeviceViewPager2Adapter.kt │ │ │ │ └── animationutils │ │ │ │ │ └── AnimationUtils.kt │ │ │ │ ├── repository │ │ │ │ ├── FileRepository.kt │ │ │ │ ├── SavedFilesRepository.kt │ │ │ │ ├── AudioRepository.kt │ │ │ │ ├── VideoRepositoryI.kt │ │ │ │ ├── ApplicationsRepositoryInterface.kt │ │ │ │ ├── ImageRepository.kt │ │ │ │ └── implementation │ │ │ │ │ ├── ZipBoltFileRepository.kt │ │ │ │ │ └── ZipBoltSavedFilesRepository.kt │ │ │ │ ├── PermissionUtils.kt │ │ │ │ ├── model │ │ │ │ ├── ui │ │ │ │ │ ├── PeerConnectionUIState.kt │ │ │ │ │ └── ImagesDisplayModel.kt │ │ │ │ ├── MediaModel.kt │ │ │ │ └── MediaType.kt │ │ │ │ ├── MainActivityDataBindingUtils.kt │ │ │ │ ├── di │ │ │ │ ├── CommunicationDIModule.kt │ │ │ │ ├── RepositoryDIModule.kt │ │ │ │ └── AndroidComponentsDIModule.kt │ │ │ │ ├── utils │ │ │ │ ├── SingleLiveDataEventForUIState.kt │ │ │ │ └── TimeUtils.kt │ │ │ │ ├── viewmodel │ │ │ │ ├── GeneralViewModel.kt │ │ │ │ ├── AudioViewModel.kt │ │ │ │ ├── VideoViewModel.kt │ │ │ │ ├── ApplicationsViewModel.kt │ │ │ │ ├── PeersDiscoveryViewModel.kt │ │ │ │ ├── MainActivityViewModel.kt │ │ │ │ ├── DataToTransferViewModel.kt │ │ │ │ └── ReceivedDataViewModel.kt │ │ │ │ ├── broadcast │ │ │ │ ├── SendDataBroadcastReceiver.kt │ │ │ │ └── DataTransferServiceConnectionStateReceiver.kt │ │ │ │ └── communication │ │ │ │ ├── MediaTransferProtocol.kt │ │ │ │ └── ZipBoltMTP.kt │ │ └── AndroidManifest.xml │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── salesground │ │ │ └── zipbolt │ │ │ ├── HiltTestRunner.kt │ │ │ ├── di │ │ │ └── TestCommunicationDIModule.kt │ │ │ └── repository │ │ │ ├── ZipBoltFileRepositoryTest.kt │ │ │ ├── ZipBoltSavedFilesRepositoryTest.kt │ │ │ ├── DeviceApplicationsRepositoryTest.kt │ │ │ └── ZipBoltImageRepositoryTest.kt │ └── test │ │ └── java │ │ └── com │ │ └── salesground │ │ └── zipbolt │ │ ├── utils │ │ └── FileExtensionsKtTest.kt │ │ ├── TestCoroutineRule.kt │ │ ├── TimeUtilsTest.kt │ │ └── LiveDataTestUtils.kt └── proguard-rules.pro ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .gitignore ├── gradle.properties └── gradlew.bat /.idea/.name: -------------------------------------------------------------------------------- 1 | SpeedForce -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/src/main/res/font/work_sans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/font/work_sans.ttf -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/font/work_sans_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/font/work_sans_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/work_sans_thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/font/work_sans_thin.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/work_sans_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/font/work_sans_black.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/work_sans_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/font/work_sans_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/work_sans_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/font/work_sans_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/work_sans_semi_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/font/work_sans_semi_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/font/work_sans_extra_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/font/work_sans_extra_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/work_sans_extra_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/font/work_sans_extra_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pekwerike/ZipBolt/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/render.experimental.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ZipBoltApplication.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class ZipBoltApplication : Application() { 8 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | } 8 | rootProject.name = "SpeedForce" 9 | include ':app' 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jun 20 22:36:34 WAT 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/apk_file_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF121212 4 | #FFFFFFFF 5 | #616161 6 | #FF121212 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/check_box_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/RecyclerViewItemClickedListener.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview 2 | 3 | 4 | class RecyclerViewItemClickedListener( 5 | private val clicked: (T) -> Unit 6 | ) { 7 | fun onClick(clickedItem: T) { 8 | return clicked(clickedItem) 9 | } 10 | } 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/slide_down_indicator.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/home_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_down.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_check_circle_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/repository/FileRepository.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository 2 | 3 | import com.salesground.zipbolt.communication.MediaTransferProtocol 4 | import com.salesground.zipbolt.model.DataToTransfer 5 | import java.io.DataInputStream 6 | import java.io.File 7 | 8 | interface FileRepository { 9 | suspend fun getRootDirectory(): File 10 | suspend fun getDirectoryChildren(directoryPath: String): List 11 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/close_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/send_icon.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/close_icon_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_radio_button_unchecked_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/person_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_play_circle_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/PermissionUtils.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.pm.PackageManager 6 | import androidx.core.app.ActivityCompat 7 | import androidx.lifecycle.Lifecycle 8 | import androidx.lifecycle.LifecycleObserver 9 | import androidx.lifecycle.OnLifecycleEvent 10 | 11 | 12 | 13 | object PermissionUtils : LifecycleObserver { 14 | 15 | const val READ_WRITE_STORAGE_REQUEST_CODE = 101 16 | 17 | 18 | } -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_arrow_circle_down_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/navigate_next.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_folder_open_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_folder_open_24_two.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/salesground/zipbolt/HiltTestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.test.runner.AndroidJUnitRunner 6 | import dagger.hilt.android.testing.HiltTestApplication 7 | 8 | class HiltTestRunner : AndroidJUnitRunner(){ 9 | 10 | 11 | override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { 12 | return super.newApplication(cl, HiltTestApplication::class.java.name, context) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/category_chip.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/no_item_in_transfer_or_receive_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_break_connection.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pad_lock_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/history_icon.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/salesground/zipbolt/utils/FileExtensionsKtTest.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.utils 2 | 3 | import androidx.core.math.MathUtils 4 | import org.junit.Assert.* 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | import org.junit.runners.JUnit4 9 | import java.math.MathContext 10 | 11 | @RunWith(JUnit4::class) 12 | class FileExtensionsKtTest { 13 | 14 | @Test 15 | fun transformDataSizeToMeasuredUnit() { 16 | assertEquals("1.2mb", 1200000L.transformDataSizeToMeasuredUnit()) 17 | assertEquals("1.3mb", 1290000L.transformDataSizeToMeasuredUnit()) 18 | assertEquals("13.1mb", 13091000L.transformDataSizeToMeasuredUnit()) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/model/ui/PeerConnectionUIState.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.model.ui 2 | 3 | import android.net.wifi.p2p.WifiP2pDevice 4 | import android.net.wifi.p2p.WifiP2pInfo 5 | import com.salesground.zipbolt.model.DataToTransfer 6 | 7 | /* 8 | "Created PeerConnectionUIState to represent the various states of the 9 | persistent bottom sheet layout during peer discovery and connection" 10 | */ 11 | sealed class PeerConnectionUIState { 12 | 13 | object NoConnectionUIAction : PeerConnectionUIState() 14 | 15 | object CollapsedConnectedToPeerTransferOngoing : PeerConnectionUIState() 16 | 17 | object ExpandedConnectedToPeerTransferOngoing : PeerConnectionUIState() 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/MainActivityDataBindingUtils.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt 2 | 3 | import android.app.Activity 4 | import android.view.ViewStub 5 | import com.salesground.zipbolt.databinding.* 6 | 7 | 8 | object MainActivityDataBindingUtils { 9 | 10 | fun getConnectedToPeerTransferOngoingPersistentBottomSheetBinding(activity: Activity): 11 | ConnectedToPeerTransferOngoingPersistentBottomSheetBinding { 12 | val view = 13 | activity.findViewById(R.id.connected_to_peer_transfer_ongoing_persistent_bottom_sheet_view_stub) 14 | .inflate() 15 | return ConnectedToPeerTransferOngoingPersistentBottomSheetBinding.bind(view) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/DataToTransferRecyclerViewDiffUtil.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import com.salesground.zipbolt.model.DataToTransfer 5 | 6 | class DataToTransferRecyclerViewDiffUtil : DiffUtil.ItemCallback() { 7 | override fun areItemsTheSame( 8 | oldItem: DataToTransfer, 9 | newItem: DataToTransfer 10 | ): Boolean { 11 | return oldItem.dataUri == newItem.dataUri 12 | } 13 | 14 | override fun areContentsTheSame( 15 | oldItem: DataToTransfer, 16 | newItem: DataToTransfer 17 | ): Boolean { 18 | return oldItem == newItem 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_audio.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/di/CommunicationDIModule.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.di 2 | 3 | import com.salesground.zipbolt.communication.MediaTransferProtocol 4 | import com.salesground.zipbolt.communication.implementation.MediaTransferProtocolImpl 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.components.ServiceComponent 9 | import dagger.hilt.components.SingletonComponent 10 | 11 | 12 | @InstallIn(ServiceComponent::class) 13 | @Module 14 | abstract class 15 | CommunicationDIModule { 16 | 17 | @Binds 18 | abstract fun getMediaTransferProtocol( 19 | mediaTransferProtocolImpl: 20 | MediaTransferProtocolImpl 21 | ): MediaTransferProtocol 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/repository/SavedFilesRepository.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository 2 | 3 | import java.io.File 4 | 5 | interface SavedFilesRepository { 6 | 7 | enum class ZipBoltMediaCategory(val categoryName: String) { 8 | IMAGES_BASE_DIRECTORY("ZipBolt Images"), 9 | VIDEOS_BASE_DIRECTORY("ZipBolt Videos"), 10 | AUDIO_BASE_DIRECTORY("ZipBolt Audios"), 11 | FILES_BASE_DIRECTORY("Files"), 12 | APPS_BASE_DIRECTORY("Apps"), 13 | FOLDERS_BASE_DIRECTORY("Folders"), 14 | DOCUMENTS_BASE_DIRECTORY("ZipBolt Documents") 15 | } 16 | 17 | fun getZipBoltBaseDirectory(): File 18 | fun getZipBoltMediaCategoryBaseDirectory(categoryType: ZipBoltMediaCategory): File 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/repository/AudioRepository.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository 2 | 3 | import com.salesground.zipbolt.communication.MediaTransferProtocol 4 | import com.salesground.zipbolt.model.DataToTransfer 5 | import java.io.DataInputStream 6 | 7 | interface AudioRepository { 8 | 9 | suspend fun insertAudioIntoMediaStore( 10 | audioName: String, 11 | audioSize: Long, 12 | audioDuration: Long, 13 | dataInputStream: DataInputStream, 14 | transferMetaDataUpdateListener: MediaTransferProtocol.TransferMetaDataUpdateListener, 15 | dataReceiveListener: MediaTransferProtocol.DataReceiveListener 16 | ) 17 | 18 | suspend fun getAudioOnDevice(): MutableList 19 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_app.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/share_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/salesground/zipbolt/di/TestCommunicationDIModule.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.di 2 | 3 | import com.salesground.zipbolt.communication.MediaTransferProtocol 4 | import com.salesground.zipbolt.communication.implementation.MediaTransferProtocolImpl 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.components.SingletonComponent 8 | import dagger.hilt.testing.TestInstallIn 9 | 10 | @Module 11 | @TestInstallIn( 12 | components = [SingletonComponent::class], 13 | replaces = [CommunicationDIModule::class] 14 | ) 15 | abstract class TestCommunicationDIModule { 16 | 17 | @Binds 18 | abstract fun getMediaTransferProtocol( 19 | mediaTransferProtocolImpl: MediaTransferProtocolImpl 20 | ): MediaTransferProtocol 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/model/MediaModel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.model 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.drawable.Drawable 5 | import android.net.Uri 6 | 7 | enum class MediaCategory(private val mediaType: String) { 8 | IMAGE("image"), 9 | VIDEO("video"), 10 | AUDIO("audio") 11 | } 12 | 13 | data class MediaModel( 14 | val mediaUri: Uri, 15 | val mediaDisplayName: String?, 16 | val mediaDateAdded: Long, 17 | val mediaSize: Long, 18 | val mediaCategory: MediaCategory, 19 | val mimeType: String, 20 | val mediaBucketName: String, 21 | val mediaDuration: Long = 0, 22 | val mediaAlbumArtPath: String = "", 23 | val mediaTitle: String = "", 24 | val mediaArtist: String = "" 25 | ) -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_videos.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/repository/VideoRepositoryI.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository 2 | 3 | import android.net.Uri 4 | import com.salesground.zipbolt.communication.MediaTransferProtocol 5 | import com.salesground.zipbolt.model.DataToTransfer 6 | import java.io.DataInputStream 7 | 8 | interface VideoRepositoryI { 9 | 10 | suspend fun insertVideoIntoMediaStore( 11 | videoName: String, 12 | videoSize: Long, 13 | videoDuration: Long, 14 | dataInputStream: DataInputStream, 15 | transferMetaDataUpdateListener: MediaTransferProtocol.TransferMetaDataUpdateListener, 16 | dataReceiveListener: MediaTransferProtocol.DataReceiveListener 17 | ) 18 | 19 | suspend fun getVideosOnDevice(): MutableList 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/image_layout_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/fragments/BreakConnectionFragment.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.fragments 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.salesground.zipbolt.R 9 | 10 | 11 | class BreakConnectionFragment : Fragment() { 12 | 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | 17 | } 18 | 19 | override fun onCreateView( 20 | inflater: LayoutInflater, container: ViewGroup?, 21 | savedInstanceState: Bundle? 22 | ): View? { 23 | // Inflate the layout for this fragment 24 | return inflater.inflate(R.layout.fragment_break_connection, container, false) 25 | } 26 | 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_sent_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/repository/ApplicationsRepositoryInterface.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository 2 | 3 | import android.content.pm.ApplicationInfo 4 | import com.salesground.zipbolt.communication.MediaTransferProtocol 5 | import com.salesground.zipbolt.model.DataToTransfer 6 | import java.io.DataInputStream 7 | 8 | interface ApplicationsRepositoryInterface { 9 | suspend fun getAllAppsOnDevice(): MutableList 10 | suspend fun getNonSystemAppsOnDevice(): List 11 | suspend fun insertApplicationIntoDevice( 12 | appFileName: String, 13 | appSize: Long, 14 | dataInputStream: DataInputStream, 15 | transferMetaDataUpdateListener: MediaTransferProtocol.TransferMetaDataUpdateListener, 16 | dataReceiveListener: MediaTransferProtocol.DataReceiveListener 17 | ) 18 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/all_media_on_device.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/image_transfer_layout_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_received_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/salesground/zipbolt/repository/ZipBoltFileRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository 2 | 3 | import com.salesground.zipbolt.repository.implementation.ZipBoltFileRepository 4 | import kotlinx.coroutines.runBlocking 5 | import org.junit.Assert.* 6 | 7 | import org.junit.After 8 | import org.junit.Before 9 | import org.junit.Test 10 | 11 | class ZipBoltFileRepositoryTest { 12 | 13 | private lateinit var zipBoltFileRepository: FileRepository 14 | 15 | @Before 16 | fun setUp() { 17 | zipBoltFileRepository = ZipBoltFileRepository() 18 | } 19 | 20 | @After 21 | fun tearDown() { 22 | } 23 | 24 | @Test 25 | fun getRootDirectory() { 26 | } 27 | 28 | @Test 29 | fun getDirectoryChildren() { 30 | } 31 | 32 | @Test 33 | fun insertDirectory() { 34 | } 35 | 36 | @Test 37 | fun insertFile() { 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/utils/SingleLiveDataEventForUIState.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.utils 2 | 3 | import androidx.lifecycle.Observer 4 | 5 | 6 | open class SingleLiveDataEventForUIState(private val content: T) { 7 | // list of all observers that have received the send button event 8 | private val mutableSet = mutableSetOf() 9 | 10 | fun getEvent(fragmentName: String): T? { 11 | return if (mutableSet.contains(fragmentName)) { 12 | null 13 | } else { 14 | mutableSet.add(fragmentName) 15 | content 16 | } 17 | } 18 | 19 | } 20 | 21 | open class Event(private val content: T) { 22 | var isHandled = false 23 | fun getEventIfNotHandled(): T? { 24 | return if (!isHandled) { 25 | null 26 | } else { 27 | isHandled = true 28 | content 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/model/ui/ImagesDisplayModel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.model.ui 2 | 3 | import com.salesground.zipbolt.model.DataToTransfer 4 | 5 | sealed class ImagesDisplayModel(val id: String) { 6 | 7 | override fun equals(other: Any?): Boolean { 8 | return when (other) { 9 | this -> true 10 | !is ImagesDisplayModel -> false 11 | else -> { 12 | other.id == this.id 13 | } 14 | } 15 | } 16 | 17 | override fun hashCode(): Int { 18 | val prime = 5 19 | val result = 7 20 | return prime * result + id.hashCode() 21 | } 22 | 23 | data class ImagesDateModifiedHeader(val dateModified: String) : ImagesDisplayModel(dateModified) 24 | 25 | data class DeviceImageDisplay(val deviceImage: DataToTransfer.DeviceImage) : 26 | ImagesDisplayModel(deviceImage.imageUri.toString()) 27 | } 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/video_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/connected_to_peer_transfer_ongoing_persistent_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/viewmodel/GeneralViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.salesground.zipbolt.utils.Event 7 | import com.salesground.zipbolt.utils.SingleLiveDataEventForUIState 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import javax.inject.Inject 10 | 11 | 12 | @HiltViewModel 13 | class GeneralViewModel @Inject constructor() : ViewModel() { 14 | 15 | private val _hasPermissionToFetchMedia = MutableLiveData>() 16 | val hasPermissionToFetchMedia : LiveData> 17 | get() = _hasPermissionToFetchMedia 18 | 19 | fun hasPermissionToFetchMedia(hasPermission: Boolean){ 20 | _hasPermissionToFetchMedia.value = SingleLiveDataEventForUIState(hasPermission) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/broadcast/SendDataBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.broadcast 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | 7 | class SendDataBroadcastReceiver( 8 | private val sendDataButtonClickedListener: SendDataButtonClickedListener 9 | ): BroadcastReceiver() { 10 | 11 | companion object{ 12 | const val ACTION_SEND_DATA_BUTTON_CLICKED = "ActionSendDataButtonClicked" 13 | } 14 | 15 | interface SendDataButtonClickedListener{ 16 | fun sendDataButtonClicked() 17 | } 18 | 19 | override fun onReceive(context: Context?, intent: Intent?) { 20 | intent?.let{ 21 | when(intent.action) { 22 | ACTION_SEND_DATA_BUTTON_CLICKED -> { 23 | sendDataButtonClickedListener.sendDataButtonClicked() 24 | } 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 53dp 4 | 12dp 5 | 16dp 6 | 4dp 7 | 8dp 8 | 4dp 9 | 48dp 10 | 8dp 11 | 4dp 12 | 2dp 13 | 4dp 14 | 8dp 15 | 8dp 16 | 4dp 17 | 16dp 18 | 8dp 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/media_date_modified_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 15 | 16 | 17 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/model/MediaType.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.model 2 | 3 | sealed class MediaType(val value: Int) { 4 | object Image : MediaType(1) 5 | object Video : MediaType(2) 6 | object Audio : MediaType(3) 7 | object App : MediaType(4) 8 | 9 | sealed class File(fileType: Int) : MediaType(fileType) { 10 | object ImageFile : File(5) 11 | object VideoFile : File(6) 12 | object AudioFile : File(7) 13 | object AppFile : File(8) 14 | object Directory : File(9) 15 | 16 | sealed class Document(documentType: Int) : File(documentType) { 17 | object PdfDocument : Document(10) 18 | object WordDocument : Document(11) 19 | object ExcelDocument : Document(12) 20 | object UnknownDocument : Document(13) 21 | object PowerPointDocument : Document(14) 22 | object ZipDocument : Document(15) 23 | object WebpageDocument : Document(16) 24 | object TextFileDocument: Document(17) 25 | object DatDocument: Document(18) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/repository/ImageRepository.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository 2 | 3 | import com.salesground.zipbolt.communication.MediaTransferProtocol.* 4 | import com.salesground.zipbolt.model.DataToTransfer 5 | import java.io.DataInputStream 6 | 7 | /** 8 | * Functions description 9 | * A. insertImageIntoMediaStore 10 | * B. getMetaDataOfImage -> This function fetches the following details of an image Uri 11 | * 1. image mimeType 12 | * 2. image size 13 | * 3. image display name 14 | * These details above will be used for socket communication when transferring the image 15 | * C. getAllImagesOnDevice 16 | * D. getTenImagesOnDevice 17 | * 18 | */ 19 | interface ImageRepository { 20 | 21 | suspend fun insertImageIntoMediaStore( 22 | displayName: String, 23 | size: Long, 24 | dataInputStream: DataInputStream, 25 | transferMetaDataUpdateListener: TransferMetaDataUpdateListener, 26 | dataReceiveListener: DataReceiveListener 27 | ) 28 | 29 | suspend fun getImagesOnDevice(limit: Int = 0): MutableList 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/test/java/com/salesground/zipbolt/TestCoroutineRule.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.ExperimentalCoroutinesApi 5 | import kotlinx.coroutines.test.* 6 | import org.junit.rules.TestRule 7 | import org.junit.runner.Description 8 | import org.junit.runners.model.Statement 9 | 10 | @ExperimentalCoroutinesApi 11 | class TestCoroutineRule : TestRule { 12 | 13 | private val testCoroutineDispatcher = TestCoroutineDispatcher() 14 | 15 | private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher) 16 | 17 | override fun apply(base: Statement, description: Description?) = object : Statement() { 18 | @Throws(Throwable::class) 19 | override fun evaluate() { 20 | Dispatchers.setMain(testCoroutineDispatcher) 21 | 22 | base.evaluate() 23 | 24 | Dispatchers.resetMain() 25 | testCoroutineScope.cleanupTestCoroutines() 26 | } 27 | } 28 | 29 | fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = 30 | testCoroutineScope.runBlockingTest { block() } 31 | 32 | } -------------------------------------------------------------------------------- /app/src/test/java/com/salesground/zipbolt/TimeUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt 2 | 3 | import com.salesground.zipbolt.utils.customizeDate 4 | import com.salesground.zipbolt.utils.formatVideoDurationToString 5 | import com.salesground.zipbolt.utils.parseDate 6 | import junit.framework.Assert.assertEquals 7 | import org.junit.Test 8 | import org.junit.runner.RunWith 9 | import org.junit.runners.JUnit4 10 | import java.time.LocalDate 11 | 12 | 13 | class TimeUtilsTest { 14 | 15 | @Test 16 | fun test_parseDate() { 17 | assertEquals("20 March, 2021", System.currentTimeMillis().parseDate()) 18 | } 19 | 20 | @Test 21 | fun test_dateDifference() { 22 | val yesterday = System.currentTimeMillis() - (24 * 60 * 60 * 1000) 23 | 24 | assertEquals("Yesterday", yesterday.parseDate().customizeDate()) 25 | } 26 | 27 | @Test 28 | fun test_formatVideoDuration() { 29 | assertEquals("59sec", 59000L.formatVideoDurationToString()) 30 | assertEquals("1min 20sec", 80_000L.formatVideoDurationToString()) 31 | assertEquals("23sec", 2300L.formatVideoDurationToString()) 32 | 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/viewmodel/AudioViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.salesground.zipbolt.model.DataToTransfer 8 | import com.salesground.zipbolt.repository.AudioRepository 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.launch 12 | import kotlinx.coroutines.withContext 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class AudioViewModel @Inject constructor(private val audioRepository: AudioRepository) : 17 | ViewModel() { 18 | 19 | private val _deviceAudio = MutableLiveData>() 20 | val deviceAudio: LiveData> 21 | get() = _deviceAudio 22 | 23 | fun getDeviceAudio() { 24 | viewModelScope.launch(Dispatchers.IO) { 25 | val deviceAudio = audioRepository.getAudioOnDevice() 26 | withContext(Dispatchers.Main) { 27 | _deviceAudio.value = deviceAudio 28 | } 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/plain_file_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /app/src/main/res/layout/expanded_connected_to_peer_transfer_ongoing.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 20 | 21 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/collapsed_connected_to_peer_transfer_receive_ongoing_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/viewmodel/VideoViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.salesground.zipbolt.model.DataToTransfer 8 | import com.salesground.zipbolt.repository.VideoRepositoryI 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.launch 12 | import kotlinx.coroutines.withContext 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class VideoViewModel @Inject constructor( 17 | private val videoRepositoryI: VideoRepositoryI 18 | ) : ViewModel() { 19 | 20 | private val _allVideosOnDevice = MutableLiveData>(mutableListOf()) 21 | val allVideosOnDevice: LiveData> 22 | get() = _allVideosOnDevice 23 | 24 | 25 | fun getAllVideosOnDevice() { 26 | viewModelScope.launch(Dispatchers.IO) { 27 | val videosOnDevice = videoRepositoryI.getVideosOnDevice() 28 | withContext(Dispatchers.Main) { 29 | _allVideosOnDevice.value = videosOnDevice 30 | } 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/SentAndReceivedDataItemsViewPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentManager 5 | import androidx.lifecycle.Lifecycle 6 | import androidx.viewpager2.adapter.FragmentStateAdapter 7 | import com.salesground.zipbolt.ui.fragments.ReceivedDataFragment 8 | import com.salesground.zipbolt.ui.fragments.SentDataFragment 9 | 10 | class SentAndReceiveDataItemsViewPagerAdapter( 11 | fragmentManager: FragmentManager, lifecycle: Lifecycle, 12 | private val isSender: Boolean 13 | ) : 14 | FragmentStateAdapter(fragmentManager, lifecycle) { 15 | 16 | override fun getItemCount(): Int { 17 | return 2 18 | } 19 | 20 | 21 | override fun createFragment(position: Int): Fragment { 22 | return when { 23 | isSender && position == 0 -> { 24 | SentDataFragment() 25 | } 26 | isSender && position == 1 -> { 27 | ReceivedDataFragment() 28 | } 29 | !isSender && position == 0 -> { 30 | ReceivedDataFragment() 31 | } 32 | else -> { 33 | SentDataFragment() 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/globe_icon.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF2E9DFF 9 | #302E9DFF 10 | #FFFF6859 11 | #54E369 12 | #FF000000 13 | #10000000 14 | #FFFFFFFF 15 | #FF121212 16 | #616161 17 | #FF121212 18 | #E0E0E0 19 | #FFFFFFFF 20 | #FF121212 21 | #2E7D57 22 | #CC992D 23 | #CC462A 24 | #FCB030 25 | #FF6F50 26 | -------------------------------------------------------------------------------- /app/src/test/java/com/salesground/zipbolt/LiveDataTestUtils.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.Observer 6 | import java.util.concurrent.CountDownLatch 7 | import java.util.concurrent.TimeUnit 8 | import java.util.concurrent.TimeoutException 9 | 10 | 11 | @VisibleForTesting(otherwise = VisibleForTesting.NONE) 12 | fun LiveData.getOrAwaitValue( 13 | time: Long = 2, 14 | timeUnit: TimeUnit = TimeUnit.SECONDS, 15 | afterObserve: () -> Unit = {} 16 | ): T { 17 | var data: T? = null 18 | val latch = CountDownLatch(1) 19 | val observer = object : Observer { 20 | override fun onChanged(o: T?) { 21 | data = o 22 | latch.countDown() 23 | this@getOrAwaitValue.removeObserver(this) 24 | } 25 | } 26 | this.observeForever(observer) 27 | 28 | try { 29 | afterObserve.invoke() 30 | 31 | // Don't wait indefinitely if the LiveData is not set. 32 | if (!latch.await(time, timeUnit)) { 33 | throw TimeoutException("LiveData value was never set.") 34 | } 35 | 36 | } finally { 37 | this.removeObserver(observer) 38 | } 39 | 40 | @Suppress("UNCHECKED_CAST") 41 | return data as T 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/videoFragment/VideoFragmentRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.videoFragment 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.ListAdapter 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.salesground.zipbolt.model.DataToTransfer 7 | import com.salesground.zipbolt.ui.recyclerview.DataToTransferRecyclerViewDiffUtil 8 | import com.salesground.zipbolt.ui.recyclerview.RecyclerViewItemClickedListener 9 | 10 | class VideoFragmentRecyclerViewAdapter( 11 | private val videoLayoutClickedListener: 12 | RecyclerViewItemClickedListener, 13 | var selectedVideos: MutableList 14 | ) : ListAdapter(DataToTransferRecyclerViewDiffUtil()) { 15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 16 | return VideoLayoutItemViewHolder.createViewHolder( 17 | parent, 18 | videoLayoutClickedListener 19 | ) 20 | } 21 | 22 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 23 | if (holder is VideoLayoutItemViewHolder) { 24 | holder.bindVideoData(getItem(position), selectedVideos) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/audioFragment/AudioFragmentRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.audioFragment 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.ListAdapter 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.salesground.zipbolt.model.DataToTransfer 7 | import com.salesground.zipbolt.ui.recyclerview.DataToTransferRecyclerViewDiffUtil 8 | import com.salesground.zipbolt.ui.recyclerview.RecyclerViewItemClickedListener 9 | 10 | class AudioFragmentRecyclerViewAdapter( 11 | private val audioLayoutClickedListener: 12 | RecyclerViewItemClickedListener, 13 | var selectedAudios: MutableList 14 | ) : ListAdapter(DataToTransferRecyclerViewDiffUtil()) { 15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 16 | return AudioLayoutItemViewHolder.createViewHolder( 17 | parent, 18 | audioLayoutClickedListener 19 | ) 20 | } 21 | 22 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 23 | if (holder is AudioLayoutItemViewHolder) { 24 | holder.bindData(getItem(position), selectedAudios) 25 | } 26 | } 27 | 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_files.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 22 | 23 | 24 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/customviews/ConstraintLayoutWithTopBorderLine.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.customviews 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.util.AttributeSet 7 | import androidx.constraintlayout.widget.ConstraintLayout 8 | import androidx.core.content.ContextCompat 9 | import com.salesground.zipbolt.R 10 | 11 | class ConstraintLayoutWithTopBorderLine @JvmOverloads constructor( 12 | context: Context, attrs: AttributeSet? = null 13 | ) : ConstraintLayout(context, attrs) { 14 | 15 | private val borderLinePaint = Paint().apply { 16 | style = Paint.Style.STROKE 17 | strokeWidth = 0.85f * resources.displayMetrics.density 18 | strokeJoin = Paint.Join.ROUND 19 | strokeCap = Paint.Cap.ROUND 20 | isDither = true 21 | isAntiAlias = true 22 | alpha = 75 23 | color = ContextCompat.getColor(context, R.color.onBackgroundColor) 24 | } 25 | 26 | override fun dispatchDraw(canvas: Canvas?) { 27 | super.dispatchDraw(canvas) 28 | canvas?.drawLine( 29 | paddingLeft.toFloat(), paddingTop.toFloat(), 30 | (measuredWidth - paddingRight).toFloat(), 31 | paddingTop.toFloat(), 32 | borderLinePaint 33 | ) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/broadcast/DataTransferServiceConnectionStateReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.broadcast 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | 7 | class DataTransferServiceConnectionStateReceiver( 8 | private val connectionStateListener: ConnectionStateListener 9 | ) : BroadcastReceiver() { 10 | 11 | companion object { 12 | const val ACTION_DISCONNECTED_FROM_PEER = "DataTransferServiceActionDisconnectedFromPeer" 13 | const val ACTION_CANNOT_CONNECT_TO_PEER_ADDRESS = 14 | "DataTransferServiceActionCannotConnectToPeerAddress" 15 | } 16 | 17 | interface ConnectionStateListener { 18 | fun disconnectedFromPeer() 19 | fun cannotConnectToPeerAddress() 20 | fun connectionBroken() 21 | } 22 | 23 | override fun onReceive(context: Context?, intent: Intent?) { 24 | intent?.let { 25 | when (intent.action) { 26 | ACTION_DISCONNECTED_FROM_PEER -> { 27 | connectionStateListener.disconnectedFromPeer() 28 | } 29 | ACTION_CANNOT_CONNECT_TO_PEER_ADDRESS -> { 30 | connectionStateListener.cannotConnectToPeerAddress() 31 | } 32 | } 33 | } 34 | } 35 | 36 | 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/di/RepositoryDIModule.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.di 2 | 3 | import com.salesground.zipbolt.repository.* 4 | import com.salesground.zipbolt.repository.implementation.* 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | @InstallIn(SingletonComponent::class) 12 | @Module 13 | abstract class RepositoryDIModule { 14 | @Singleton 15 | @Binds 16 | abstract fun getZipBoltImageRepository(advanceImageRepository: AdvanceImageRepository): ImageRepository 17 | 18 | @Singleton 19 | @Binds 20 | abstract fun getZipBoltSavedFilesRepository(zipBoltSavedFilesRepository: ZipBoltSavedFilesRepository): SavedFilesRepository 21 | 22 | @Singleton 23 | @Binds 24 | abstract fun getAapplicationsRepository(deviceApplicationsRepository: DeviceApplicationsRepository): ApplicationsRepositoryInterface 25 | 26 | @Singleton 27 | @Binds 28 | abstract fun getZipBoltVideoRepository(zipBoltVideoRepository: ZipBoltVideoRepository): VideoRepositoryI 29 | 30 | @Singleton 31 | @Binds 32 | abstract fun getZipBoltAudioRepository(zipBoltAudioRepository: ZipBoltAudioRepository): AudioRepository 33 | 34 | @Singleton 35 | @Binds 36 | abstract fun getZipBoltFileRepository(zipBoltFileRepository: ZipBoltFileRepository): FileRepository 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/image/ImageTransferWaitingLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.image 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.bumptech.glide.Glide 7 | import com.salesground.zipbolt.databinding.ImageTransferLayoutItemBinding 8 | import com.salesground.zipbolt.model.DataToTransfer 9 | 10 | class ImageTransferWaitingLayoutItemViewHolder( 11 | private val imageTransferLayoutItemBinding: ImageTransferLayoutItemBinding 12 | ) : RecyclerView.ViewHolder(imageTransferLayoutItemBinding.root) { 13 | 14 | fun bindImageData(imageData: DataToTransfer) { 15 | imageTransferLayoutItemBinding.apply { 16 | Glide.with(imageWaitingForTransferLayoutItemImageView) 17 | .load(imageData.dataUri) 18 | .into(imageWaitingForTransferLayoutItemImageView) 19 | } 20 | } 21 | 22 | companion object { 23 | fun createViewHolder(parent: ViewGroup): ImageTransferWaitingLayoutItemViewHolder { 24 | val layoutBinding = ImageTransferLayoutItemBinding.inflate( 25 | LayoutInflater.from(parent.context), 26 | parent, 27 | false 28 | ) 29 | return ImageTransferWaitingLayoutItemViewHolder(layoutBinding) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/repository/implementation/ZipBoltFileRepository.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository.implementation 2 | 3 | import android.content.Context 4 | import android.os.Environment 5 | import com.salesground.zipbolt.communication.MediaTransferProtocol 6 | import com.salesground.zipbolt.model.DataToTransfer 7 | import com.salesground.zipbolt.model.MediaType 8 | import com.salesground.zipbolt.repository.FileRepository 9 | import com.salesground.zipbolt.service.DataTransferService 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import java.io.BufferedOutputStream 12 | import java.io.DataInputStream 13 | import java.io.File 14 | import java.io.FileOutputStream 15 | import javax.inject.Inject 16 | import kotlin.math.min 17 | 18 | class ZipBoltFileRepository @Inject constructor( 19 | ) : FileRepository { 20 | private val dataBuffer = ByteArray(DataTransferService.BUFFER_SIZE) 21 | private var sizeOfDataReadFromDirectoryTransfer: Long = 0L 22 | override suspend fun getRootDirectory(): File { 23 | return Environment.getExternalStorageDirectory() 24 | } 25 | 26 | override suspend fun getDirectoryChildren(directoryPath: String): List { 27 | var children = listOf() 28 | File(directoryPath).apply { 29 | children = listFiles()?.map { 30 | DataToTransfer.DeviceFile(it) 31 | } ?: listOf() 32 | } 33 | return children 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/imagefragment/DateModifiedHeaderViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.imagefragment 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.MediaDateModifiedHeaderBinding 9 | import com.salesground.zipbolt.model.ui.ImagesDisplayModel 10 | 11 | class DateModifiedHeaderViewHolder( 12 | private val mediaDateModifiedHeaderBinding: 13 | MediaDateModifiedHeaderBinding 14 | ) : RecyclerView.ViewHolder(mediaDateModifiedHeaderBinding.root) { 15 | 16 | fun bindDateModified(dateModified: ImagesDisplayModel.ImagesDateModifiedHeader) { 17 | mediaDateModifiedHeaderBinding.apply { 18 | this.dateModified = dateModified.dateModified 19 | executePendingBindings() 20 | } 21 | } 22 | 23 | companion object { 24 | fun createDateModifiedHeaderViewHolder(parent: ViewGroup): DateModifiedHeaderViewHolder { 25 | val layoutItemBinding = DataBindingUtil.inflate( 26 | LayoutInflater.from(parent.context), 27 | R.layout.media_date_modified_header, 28 | parent, 29 | false 30 | ) 31 | return DateModifiedHeaderViewHolder(layoutItemBinding) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/viewmodel/ApplicationsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.salesground.zipbolt.model.DataToTransfer 8 | import com.salesground.zipbolt.repository.ApplicationsRepositoryInterface 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.launch 12 | import kotlinx.coroutines.withContext 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class ApplicationsViewModel @Inject constructor( 17 | private val applicationsRepositoryInterface: ApplicationsRepositoryInterface 18 | ) : ViewModel() { 19 | 20 | private var allNonSystemAppsOnDevice = listOf() 21 | private val _allApplicationsOnDevice = MutableLiveData>( 22 | listOf() 23 | ) 24 | val allApplicationsOnDevice: LiveData> 25 | get() = _allApplicationsOnDevice 26 | 27 | 28 | 29 | fun getAllApplicationsOnDevice() { 30 | viewModelScope.launch(Dispatchers.IO) { 31 | allNonSystemAppsOnDevice = applicationsRepositoryInterface.getNonSystemAppsOnDevice() 32 | withContext(Dispatchers.Main) { 33 | _allApplicationsOnDevice.value = allNonSystemAppsOnDevice 34 | } 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/discovered_peer_layout_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 20 | 21 | 26 | 27 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/plainFile/PlainFileTransferWaitingLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.plainFile 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | 11 | class PlainFileTransferWaitingLayoutItemViewHolder( 12 | private val plainFileTransferLayoutItemBinding: PlainFileTransferLayoutItemBinding 13 | ) : RecyclerView.ViewHolder(plainFileTransferLayoutItemBinding.root) { 14 | 15 | fun bindData(dataToTransfer: DataToTransfer) { 16 | plainFileTransferLayoutItemBinding.run { 17 | document = dataToTransfer as DataToTransfer.DeviceFile 18 | } 19 | } 20 | 21 | companion object { 22 | fun createViewHolder(parent: ViewGroup): PlainFileTransferWaitingLayoutItemViewHolder { 23 | val layoutBinding = DataBindingUtil.inflate( 24 | LayoutInflater.from(parent.context), 25 | R.layout.plain_file_transfer_layout_item, 26 | parent, 27 | false 28 | ) 29 | return PlainFileTransferWaitingLayoutItemViewHolder(layoutBinding) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/viewmodel/PeersDiscoveryViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.viewmodel 2 | 3 | import android.net.wifi.p2p.WifiP2pDevice 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.launch 11 | 12 | 13 | class PeersDiscoveryViewModel : ViewModel() { 14 | val deviceTransferTypeMap = HashMap() 15 | private var normalDiscoveredPeerSet = mutableSetOf() 16 | private val _discoveredPeerSet = MutableLiveData>(null) 17 | val discoveredPeerSet: LiveData> 18 | get() = _discoveredPeerSet 19 | 20 | fun addDiscoveredDevice(wifiP2pDevice: WifiP2pDevice) { 21 | if (normalDiscoveredPeerSet.find { 22 | it.deviceAddress == wifiP2pDevice.deviceAddress 23 | } == null) { 24 | normalDiscoveredPeerSet.add(wifiP2pDevice) 25 | viewModelScope.launch(Dispatchers.Main) { 26 | _discoveredPeerSet.value = normalDiscoveredPeerSet 27 | } 28 | } 29 | } 30 | 31 | fun clearDiscoveredPeerSet() { 32 | normalDiscoveredPeerSet = mutableSetOf() 33 | viewModelScope.launch(Dispatchers.Main) { 34 | _discoveredPeerSet.value = normalDiscoveredPeerSet 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/applicationFragment/ApplicationFragmentAppsDisplayRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.applicationFragment 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.ListAdapter 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.salesground.zipbolt.model.DataToTransfer 7 | import com.salesground.zipbolt.ui.recyclerview.DataToTransferRecyclerViewDiffUtil 8 | import com.salesground.zipbolt.ui.recyclerview.RecyclerViewItemClickedListener 9 | 10 | class ApplicationFragmentAppsDisplayRecyclerViewAdapter( 11 | private val applicationLayoutClickedListener: 12 | RecyclerViewItemClickedListener, 13 | var selectedApplications: MutableList 14 | ) : ListAdapter( 15 | DataToTransferRecyclerViewDiffUtil() 16 | ) { 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 19 | return ApplicationLayoutItemViewHolder.createViewHolder( 20 | parent, 21 | applicationLayoutClickedListener 22 | ) 23 | } 24 | 25 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 26 | if (holder is ApplicationLayoutItemViewHolder) { 27 | val currentItem = getItem(position) 28 | holder.bindApplicationDetails( 29 | currentItem, 30 | selectedApplications 31 | ) 32 | } 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/zip_bolt_header_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/plainFile/image/PlainImageFileTransferWaitingLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.plainFile.image 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainImageFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | 11 | class PlainImageFileTransferWaitingLayoutItemViewHolder( 12 | private val plainImageFileTransferLayoutItemBinding: PlainImageFileTransferLayoutItemBinding 13 | ) : RecyclerView.ViewHolder(plainImageFileTransferLayoutItemBinding.root) { 14 | 15 | fun bindData(dataToTransfer: DataToTransfer) { 16 | plainImageFileTransferLayoutItemBinding.run { 17 | document = dataToTransfer as DataToTransfer.DeviceFile 18 | } 19 | } 20 | 21 | companion object { 22 | fun createViewHolder(parent: ViewGroup): PlainImageFileTransferWaitingLayoutItemViewHolder { 23 | val layoutBinding = DataBindingUtil.inflate( 24 | LayoutInflater.from(parent.context), 25 | R.layout.plain_image_file_transfer_layout_item, 26 | parent, 27 | false 28 | ) 29 | return PlainImageFileTransferWaitingLayoutItemViewHolder(layoutBinding) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/plainFile/video/PlainVideoFileTransferWaitingLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.plainFile.video 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainVideoFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | 11 | class PlainVideoFileTransferWaitingLayoutItemViewHolder( 12 | private val plainVideoFileTransferLayoutItemBinding: PlainVideoFileTransferLayoutItemBinding 13 | ) : RecyclerView.ViewHolder(plainVideoFileTransferLayoutItemBinding.root) { 14 | 15 | fun bindData(dataToTransfer: DataToTransfer) { 16 | plainVideoFileTransferLayoutItemBinding.run { 17 | this.document = dataToTransfer as DataToTransfer.DeviceFile 18 | } 19 | } 20 | 21 | companion object { 22 | fun createViewHolder(parent: ViewGroup): PlainVideoFileTransferWaitingLayoutItemViewHolder { 23 | val layoutBinding = DataBindingUtil.inflate( 24 | LayoutInflater.from(parent.context), 25 | R.layout.plain_video_file_transfer_layout_item, 26 | parent, 27 | false 28 | ) 29 | return PlainVideoFileTransferWaitingLayoutItemViewHolder(layoutBinding) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/pdf.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/di/AndroidComponentsDIModule.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.di 2 | 3 | import android.app.NotificationManager 4 | import android.content.Context 5 | import android.net.ConnectivityManager 6 | import android.net.wifi.WifiManager 7 | import android.net.wifi.p2p.WifiP2pManager 8 | import androidx.localbroadcastmanager.content.LocalBroadcastManager 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.android.qualifiers.ApplicationContext 13 | import dagger.hilt.components.SingletonComponent 14 | import javax.inject.Singleton 15 | 16 | @InstallIn(SingletonComponent::class) 17 | @Module 18 | class AndroidComponentsDIModule { 19 | 20 | @Provides 21 | fun getNotificationManager(@ApplicationContext context: Context): NotificationManager { 22 | return context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 23 | } 24 | 25 | @Provides 26 | @Singleton 27 | fun getLocalBroadcastManager(@ApplicationContext context: Context): LocalBroadcastManager { 28 | return LocalBroadcastManager.getInstance(context) 29 | } 30 | 31 | @Provides 32 | @Singleton 33 | fun getWifiP2pManager(@ApplicationContext context: Context): WifiP2pManager { 34 | return context.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager 35 | } 36 | 37 | @Provides 38 | @Singleton 39 | fun getWifiManager(@ApplicationContext context: Context): WifiManager { 40 | return context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/plainFile/document/PlainDocumentFileTransferWaitingLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.plainFile.document 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainDocumentFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | 11 | class PlainDocumentFileTransferWaitingLayoutItemViewHolder( 12 | private val plainDocumentFileTransferLayoutItemBinding: PlainDocumentFileTransferLayoutItemBinding 13 | ): RecyclerView.ViewHolder(plainDocumentFileTransferLayoutItemBinding.root) { 14 | 15 | fun bindData(dataToTransfer: DataToTransfer){ 16 | plainDocumentFileTransferLayoutItemBinding.run { 17 | document = dataToTransfer as DataToTransfer.DeviceFile 18 | } 19 | } 20 | 21 | companion object{ 22 | fun createViewHolder(parent: ViewGroup): PlainDocumentFileTransferWaitingLayoutItemViewHolder{ 23 | val layoutBinding = DataBindingUtil.inflate( 24 | LayoutInflater.from(parent.context), 25 | R.layout.plain_document_file_transfer_layout_item, 26 | parent, 27 | false 28 | ) 29 | return PlainDocumentFileTransferWaitingLayoutItemViewHolder(layoutBinding) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/image/ImageTransferCompleteLayoutViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.image 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.bumptech.glide.Glide 7 | import com.salesground.zipbolt.databinding.ImageTransferLayoutItemBinding 8 | import com.salesground.zipbolt.model.DataToTransfer 9 | 10 | class ImageTransferCompleteLayoutViewHolder( 11 | private val imageTransferLayoutItemBinding: ImageTransferLayoutItemBinding 12 | ) : RecyclerView.ViewHolder(imageTransferLayoutItemBinding.root) { 13 | 14 | fun bindImageData(dataToTransfer: DataToTransfer) { 15 | imageTransferLayoutItemBinding.run { 16 | Glide.with(imageWaitingForTransferLayoutItemImageView) 17 | .load(dataToTransfer.dataUri) 18 | .into(imageWaitingForTransferLayoutItemImageView) 19 | 20 | imageTransferLayoutItemLoadingImageShimmer.run{ 21 | stopShimmer() 22 | hideShimmer() 23 | } 24 | } 25 | } 26 | 27 | companion object { 28 | fun createViewHolder(parent: ViewGroup): ImageTransferCompleteLayoutViewHolder { 29 | val layoutBinding = ImageTransferLayoutItemBinding.inflate( 30 | LayoutInflater.from(parent.context), 31 | parent, 32 | false 33 | ) 34 | 35 | return ImageTransferCompleteLayoutViewHolder(layoutBinding) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/receivedDataFragment/viewHolders/image/ImageReceiveCompleteLayoutViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.receivedDataFragment.viewHolders.image 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.bumptech.glide.Glide 7 | import com.salesground.zipbolt.databinding.ImageTransferLayoutItemBinding 8 | import com.salesground.zipbolt.model.DataToTransfer 9 | 10 | class ImageReceiveCompleteLayoutViewHolder( 11 | private val imageReceiveLayoutItemBinding: ImageTransferLayoutItemBinding 12 | ) : RecyclerView.ViewHolder(imageReceiveLayoutItemBinding.root) { 13 | 14 | fun bindImageData(dataToTransfer: DataToTransfer) { 15 | imageReceiveLayoutItemBinding.run { 16 | Glide.with(imageWaitingForTransferLayoutItemImageView) 17 | .load(dataToTransfer.dataUri) 18 | .into(imageWaitingForTransferLayoutItemImageView) 19 | 20 | imageTransferLayoutItemLoadingImageShimmer.run { 21 | stopShimmer() 22 | hideShimmer() 23 | } 24 | } 25 | } 26 | 27 | companion object { 28 | fun createViewHolder(parent: ViewGroup): ImageReceiveCompleteLayoutViewHolder { 29 | val layoutBinding = 30 | ImageTransferLayoutItemBinding.inflate( 31 | LayoutInflater.from(parent.context), 32 | parent, 33 | false 34 | ) 35 | 36 | return ImageReceiveCompleteLayoutViewHolder(layoutBinding) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/plainFile/PlainFileTransferCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.plainFile 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | 11 | class PlainFileTransferCompleteLayoutItemViewHolder( 12 | private val plainFileTransferLayoutItemBinding: PlainFileTransferLayoutItemBinding 13 | ) : RecyclerView.ViewHolder(plainFileTransferLayoutItemBinding.root) { 14 | 15 | fun bindData(dataToTransfer: DataToTransfer) { 16 | plainFileTransferLayoutItemBinding.run { 17 | document = dataToTransfer as DataToTransfer.DeviceFile 18 | plainFileTransferLayoutItemShimmer.run { 19 | stopShimmer() 20 | hideShimmer() 21 | } 22 | } 23 | } 24 | 25 | companion object { 26 | fun createViewHolder(parent: ViewGroup): PlainFileTransferCompleteLayoutItemViewHolder { 27 | val layoutBinding = DataBindingUtil.inflate( 28 | LayoutInflater.from(parent.context), 29 | R.layout.plain_file_transfer_layout_item, 30 | parent, 31 | false 32 | ) 33 | return PlainFileTransferCompleteLayoutItemViewHolder(layoutBinding) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/peersDiscoveryFragment/PeersDiscoveredRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.peersDiscoveryFragment 2 | 3 | import android.net.wifi.p2p.WifiP2pDevice 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.salesground.zipbolt.ui.recyclerview.RecyclerViewItemClickedListener 9 | 10 | class PeersDiscoveredRecyclerViewAdapter( 11 | private val deviceClickedListener: RecyclerViewItemClickedListener 12 | ) : ListAdapter(PeersDiscoveredRecyclerViewAdapterDiffUtil) { 14 | 15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 16 | return DiscoveredPeerLayoutItemViewHolder.createViewHolder( 17 | parent, 18 | deviceClickedListener 19 | ) 20 | } 21 | 22 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 23 | if (holder is DiscoveredPeerLayoutItemViewHolder) { 24 | holder.bindData(getItem(position)) 25 | } 26 | } 27 | } 28 | 29 | object PeersDiscoveredRecyclerViewAdapterDiffUtil : DiffUtil.ItemCallback() { 30 | override fun areItemsTheSame(oldItem: WifiP2pDevice, newItem: WifiP2pDevice): Boolean { 31 | return oldItem.deviceAddress == newItem.deviceAddress 32 | } 33 | 34 | override fun areContentsTheSame(oldItem: WifiP2pDevice, newItem: WifiP2pDevice): Boolean { 35 | return oldItem == newItem 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/directory/DirectoryTransferWaitingLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.directory 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.FolderTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | import com.salesground.zipbolt.utils.getDirectorySize 11 | 12 | class DirectoryTransferWaitingLayoutItemViewHolder( 13 | private val folderTransferLayoutItemBinding: FolderTransferLayoutItemBinding 14 | ) : RecyclerView.ViewHolder(folderTransferLayoutItemBinding.root) { 15 | 16 | fun bindData(dataToTransfer: DataToTransfer) { 17 | dataToTransfer as DataToTransfer.DeviceFile 18 | folderTransferLayoutItemBinding.run { 19 | folderSize = dataToTransfer.file.getDirectorySize() 20 | folderName = dataToTransfer.dataDisplayName 21 | 22 | } 23 | } 24 | 25 | companion object { 26 | fun createViewHolder(parent: ViewGroup): DirectoryTransferWaitingLayoutItemViewHolder { 27 | val layoutBinding = DataBindingUtil.inflate( 28 | LayoutInflater.from(parent.context), 29 | R.layout.folder_transfer_layout_item, 30 | parent, 31 | false 32 | ) 33 | 34 | return DirectoryTransferWaitingLayoutItemViewHolder(layoutBinding) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/repository/implementation/ZipBoltSavedFilesRepository.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository 2 | /* 3 | * Overview 4 | * This class is responsible for generating the base directories for other repositories like 5 | * ImageRepositoryInitial, VideoRepository etc to saved the files of received items before inserting 6 | * them into the media store of the various category 7 | * 8 | * Functions description 9 | * 1). getZipBoltBaseDirectory() returns the top level ZipBolt directory in the file system 10 | * 2). getZipBoltMediaCategoryBaseDirectory() returns the mediaType directory under the main directory 11 | * 3). checkIfDirectory() exists confirm that a given directory exists, if not then it creates it 12 | * */ 13 | import android.os.Environment 14 | import java.io.File 15 | import javax.inject.Inject 16 | 17 | const val ZIP_BOLT_MAIN_DIRECTORY = "ZipBolt" 18 | 19 | class ZipBoltSavedFilesRepository @Inject constructor() : SavedFilesRepository { 20 | 21 | override fun getZipBoltBaseDirectory(): File { 22 | val baseDirectory = File(Environment.getExternalStorageDirectory(), ZIP_BOLT_MAIN_DIRECTORY) 23 | checkIfDirectoryExist(baseDirectory) 24 | return baseDirectory 25 | } 26 | 27 | override fun getZipBoltMediaCategoryBaseDirectory(categoryType: SavedFilesRepository.ZipBoltMediaCategory): File { 28 | val categoryBaseDirectory = File(getZipBoltBaseDirectory(), categoryType.categoryName) 29 | checkIfDirectoryExist(categoryBaseDirectory) 30 | return categoryBaseDirectory 31 | } 32 | 33 | private fun checkIfDirectoryExist(directory: File) { 34 | if (!directory.exists()) directory.mkdirs() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/salesground/zipbolt/repository/ZipBoltSavedFilesRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository 2 | 3 | import com.salesground.zipbolt.repository.SavedFilesRepository.* 4 | import org.junit.Before 5 | 6 | import org.junit.Assert.* 7 | import org.junit.Test 8 | import java.util.zip.ZipInputStream 9 | 10 | class ZipBoltSavedFilesRepositoryTest { 11 | lateinit var zipBoltSavedFilesRepository: ZipBoltSavedFilesRepository 12 | 13 | @Before 14 | fun setUp() { 15 | zipBoltSavedFilesRepository = ZipBoltSavedFilesRepository() 16 | } 17 | 18 | @Test 19 | fun confirmThatTheAppRootDirectoryNameIsZipBolt() { 20 | val imageBaseDirectory = zipBoltSavedFilesRepository 21 | .getZipBoltMediaCategoryBaseDirectory(ZipBoltMediaCategory.IMAGES_BASE_DIRECTORY) 22 | assertEquals(imageBaseDirectory.parentFile.name, "ZipBolt") 23 | } 24 | 25 | @Test 26 | fun confirmThatDirectoriesAreCreatedForEachMediaCategory() { 27 | val mediaCategory = ZipBoltMediaCategory.values() 28 | mediaCategory.forEach { 29 | val mediaCategory = 30 | zipBoltSavedFilesRepository.getZipBoltMediaCategoryBaseDirectory(it) 31 | assertTrue(mediaCategory.exists()) 32 | } 33 | } 34 | 35 | @Test 36 | fun confirmThatDirectoryForImagesHasTheNameImages(){ 37 | val imageCategory = ZipBoltMediaCategory.IMAGES_BASE_DIRECTORY 38 | val imageCategoryBaseDirectory = 39 | zipBoltSavedFilesRepository.getZipBoltMediaCategoryBaseDirectory(imageCategory) 40 | assertEquals(imageCategoryBaseDirectory.name, ZipBoltMediaCategory.IMAGES_BASE_DIRECTORY.categoryName) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/AllMediaOnDeviceViewPager2Adapter.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentManager 5 | import androidx.fragment.app.FragmentStatePagerAdapter 6 | import androidx.lifecycle.Lifecycle 7 | import androidx.viewpager2.adapter.FragmentStateAdapter 8 | import com.salesground.zipbolt.ui.fragments.* 9 | 10 | class AllMediaOnDeviceViewPager2Adapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : 11 | FragmentStateAdapter(fragmentManager, lifecycle) { 12 | 13 | override fun getItemCount(): Int { 14 | return 5 15 | } 16 | 17 | 18 | override fun createFragment(position: Int): Fragment { 19 | return when (position) { 20 | 0 -> DeviceAppsFragment() 21 | 1 -> ImageFragment() 22 | 2 -> VideosFragment() 23 | 3 -> AudioFragment() 24 | 4 -> FilesFragment() 25 | else -> ImageFragment() 26 | } 27 | } 28 | } 29 | 30 | class AllMediaOnDeviceViewPagerAdapter(private val fragmentManager: FragmentManager) : 31 | FragmentStatePagerAdapter( 32 | fragmentManager, 33 | FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 34 | ) { 35 | override fun getItem(position: Int): Fragment { 36 | return when (position) { 37 | 0 -> DeviceAppsFragment() 38 | 1 -> ImageFragment() 39 | 2 -> VideosFragment() 40 | 3 -> AudioFragment() 41 | 4 -> FilesFragment() 42 | else -> ImageFragment() 43 | } 44 | } 45 | 46 | override fun getCount(): Int { 47 | return 5 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/plainFile/video/PlainVideoFileTransferCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.plainFile.video 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainVideoFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | 11 | class PlainVideoFileTransferCompleteLayoutItemViewHolder( 12 | private val plainVideoFileTransferLayoutItemBinding: PlainVideoFileTransferLayoutItemBinding 13 | ) : RecyclerView.ViewHolder(plainVideoFileTransferLayoutItemBinding.root) { 14 | 15 | fun bindData(dataToTransfer: DataToTransfer) { 16 | plainVideoFileTransferLayoutItemBinding.run { 17 | this.document = dataToTransfer as DataToTransfer.DeviceFile 18 | plainVideoFileTransferLayoutItemShimmer.run { 19 | stopShimmer() 20 | hideShimmer() 21 | } 22 | } 23 | } 24 | 25 | companion object { 26 | fun createViewHolder(parent: ViewGroup): PlainVideoFileTransferCompleteLayoutItemViewHolder { 27 | val layoutBinding = DataBindingUtil.inflate( 28 | LayoutInflater.from(parent.context), 29 | R.layout.plain_video_file_transfer_layout_item, 30 | parent, 31 | false 32 | ) 33 | return PlainVideoFileTransferCompleteLayoutItemViewHolder(layoutBinding) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/plainFile/document/PlainDocumentFileTransferCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.plainFile.document 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainDocumentFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | 11 | class PlainDocumentFileTransferCompleteLayoutItemViewHolder( 12 | private val plainDocumentFileTransferLayoutItemBinding: PlainDocumentFileTransferLayoutItemBinding 13 | ) : RecyclerView.ViewHolder(plainDocumentFileTransferLayoutItemBinding.root) { 14 | 15 | fun bindData(dataToTransfer: DataToTransfer){ 16 | plainDocumentFileTransferLayoutItemBinding.run { 17 | document = dataToTransfer as DataToTransfer.DeviceFile 18 | plainDocumentFileTransferLayoutItemShimmer.run { 19 | stopShimmer() 20 | hideShimmer() 21 | } 22 | } 23 | } 24 | companion object { 25 | fun createViewHolder(parent: ViewGroup): PlainDocumentFileTransferCompleteLayoutItemViewHolder { 26 | val layoutBinding = DataBindingUtil.inflate( 27 | LayoutInflater.from(parent.context), 28 | R.layout.plain_document_file_transfer_layout_item, 29 | parent, 30 | false 31 | ) 32 | return PlainDocumentFileTransferCompleteLayoutItemViewHolder(layoutBinding) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/plainFile/image/PlainImageFileTransferCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.plainFile.image 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainImageFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | 11 | class PlainImageFileTransferCompleteLayoutItemViewHolder( 12 | private val plainImageFileTransferLayoutItemBinding: PlainImageFileTransferLayoutItemBinding 13 | ) : RecyclerView.ViewHolder(plainImageFileTransferLayoutItemBinding.root) { 14 | 15 | fun bindData(dataToTransfer: DataToTransfer) { 16 | plainImageFileTransferLayoutItemBinding.run { 17 | document = dataToTransfer as DataToTransfer.DeviceFile 18 | plainImageFileTransferLayoutItemShimmer.run { 19 | stopShimmer() 20 | hideShimmer() 21 | } 22 | } 23 | } 24 | 25 | companion object { 26 | fun createViewHolder(parent: ViewGroup): PlainImageFileTransferCompleteLayoutItemViewHolder { 27 | val layoutBinding = DataBindingUtil.inflate( 28 | LayoutInflater.from(parent.context), 29 | R.layout.plain_image_file_transfer_layout_item, 30 | parent, 31 | false 32 | ) 33 | return PlainImageFileTransferCompleteLayoutItemViewHolder( 34 | layoutBinding 35 | ) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/receivedDataFragment/viewHolders/plainFile/document/PlainDocumentFileReceiveCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.receivedDataFragment.viewHolders.plainFile.document 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainDocumentFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | 11 | class PlainDocumentFileReceiveCompleteLayoutItemViewHolder( 12 | private val plainDocumentFileTransferLayoutItemBinding: PlainDocumentFileTransferLayoutItemBinding 13 | ) : RecyclerView.ViewHolder(plainDocumentFileTransferLayoutItemBinding.root) { 14 | 15 | fun bindData(dataToTransfer: DataToTransfer) { 16 | plainDocumentFileTransferLayoutItemBinding.run { 17 | document = dataToTransfer as DataToTransfer.DeviceFile 18 | plainDocumentFileTransferLayoutItemShimmer.run { 19 | stopShimmer() 20 | hideShimmer() 21 | } 22 | } 23 | } 24 | 25 | companion object { 26 | fun createViewHolder(parent: ViewGroup): PlainDocumentFileReceiveCompleteLayoutItemViewHolder { 27 | val layoutBinding = DataBindingUtil.inflate( 28 | LayoutInflater.from(parent.context), 29 | R.layout.plain_document_file_transfer_layout_item, 30 | parent, 31 | false 32 | ) 33 | return PlainDocumentFileReceiveCompleteLayoutItemViewHolder(layoutBinding) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/receivedDataFragment/viewHolders/plainFile/image/PlainImageFileReceiveCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.receivedDataFragment.viewHolders.plainFile.image 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainImageFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | 11 | class PlainImageFileReceiveCompleteLayoutItemViewHolder( 12 | private val plainImageFileTransferLayoutItemBinding: PlainImageFileTransferLayoutItemBinding 13 | ) : RecyclerView.ViewHolder(plainImageFileTransferLayoutItemBinding.root) { 14 | 15 | fun bindData(dataToTransfer: DataToTransfer) { 16 | plainImageFileTransferLayoutItemBinding.run { 17 | document = dataToTransfer as DataToTransfer.DeviceFile 18 | plainImageFileTransferLayoutItemShimmer.run { 19 | stopShimmer() 20 | hideShimmer() 21 | } 22 | } 23 | } 24 | 25 | companion object { 26 | fun createViewHolder(parent: ViewGroup): PlainImageFileReceiveCompleteLayoutItemViewHolder { 27 | val layoutItemBinding = 28 | DataBindingUtil.inflate( 29 | LayoutInflater.from(parent.context), 30 | R.layout.plain_image_file_transfer_layout_item, 31 | parent, 32 | false 33 | ) 34 | return PlainImageFileReceiveCompleteLayoutItemViewHolder(layoutItemBinding) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/audio/AudioTransferWaitingLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.audio 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.salesground.zipbolt.R 9 | import com.salesground.zipbolt.databinding.AudioTransferLayoutItemBinding 10 | import com.salesground.zipbolt.model.DataToTransfer 11 | 12 | class AudioTransferWaitingLayoutItemViewHolder( 13 | private val audioTransferLayoutItemBinding: AudioTransferLayoutItemBinding 14 | ) : RecyclerView.ViewHolder(audioTransferLayoutItemBinding.root) { 15 | 16 | fun bindData(dataToTransfer: DataToTransfer) { 17 | dataToTransfer as DataToTransfer.DeviceAudio 18 | audioTransferLayoutItemBinding.run { 19 | this.dataToTransfer = dataToTransfer 20 | 21 | Glide.with(audioTransferLayoutItemVideoPreviewImageView) 22 | .load(dataToTransfer.dataUri) 23 | .error(R.drawable.ic_icons8_music) 24 | .into(audioTransferLayoutItemVideoPreviewImageView) 25 | } 26 | } 27 | 28 | companion object { 29 | fun createViewHolder(parent: ViewGroup): AudioTransferWaitingLayoutItemViewHolder { 30 | val layoutBinding = DataBindingUtil.inflate( 31 | LayoutInflater.from(parent.context), 32 | R.layout.audio_transfer_layout_item, 33 | parent, 34 | false 35 | ) 36 | return AudioTransferWaitingLayoutItemViewHolder(layoutBinding) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/receivedDataFragment/viewHolders/directory/DirectoryReceiveCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.receivedDataFragment.viewHolders.directory 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.FolderTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | import com.salesground.zipbolt.utils.getDirectorySize 11 | 12 | class DirectoryReceiveCompleteLayoutItemViewHolder( 13 | private val folderTransferLayoutItemBinding: FolderTransferLayoutItemBinding 14 | ) : RecyclerView.ViewHolder(folderTransferLayoutItemBinding.root) { 15 | 16 | fun bindData(dataToTransfer: DataToTransfer) { 17 | dataToTransfer as DataToTransfer.DeviceFile 18 | folderTransferLayoutItemBinding.run { 19 | folderSize = dataToTransfer.file.getDirectorySize() 20 | folderName = dataToTransfer.dataDisplayName 21 | folderTransferLayoutItemShimmer.run { 22 | stopShimmer() 23 | hideShimmer() 24 | } 25 | } 26 | } 27 | 28 | companion object { 29 | fun createViewHolder(parent: ViewGroup): DirectoryReceiveCompleteLayoutItemViewHolder { 30 | val layoutBinding = DataBindingUtil.inflate( 31 | LayoutInflater.from(parent.context), 32 | R.layout.folder_transfer_layout_item, 33 | parent, 34 | false 35 | ) 36 | return DirectoryReceiveCompleteLayoutItemViewHolder(layoutBinding) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/directory/DirectoryTransferCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.directory 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.FolderTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | import com.salesground.zipbolt.utils.getDirectorySize 11 | 12 | class DirectoryTransferCompleteLayoutItemViewHolder( 13 | private val folderTransferLayoutItemBinding: FolderTransferLayoutItemBinding 14 | ) : RecyclerView.ViewHolder(folderTransferLayoutItemBinding.root) { 15 | 16 | fun bindData(dataToTransfer: DataToTransfer) { 17 | dataToTransfer as DataToTransfer.DeviceFile 18 | folderTransferLayoutItemBinding.run { 19 | folderSize = dataToTransfer.file.getDirectorySize() 20 | folderName = dataToTransfer.dataDisplayName 21 | folderTransferLayoutItemShimmer.run { 22 | stopShimmer() 23 | hideShimmer() 24 | } 25 | } 26 | } 27 | 28 | companion object { 29 | fun createViewHolder(parent: ViewGroup): DirectoryTransferCompleteLayoutItemViewHolder { 30 | val layoutBinding = DataBindingUtil.inflate( 31 | LayoutInflater.from(parent.context), 32 | R.layout.folder_transfer_layout_item, 33 | parent, 34 | false 35 | ) 36 | return DirectoryTransferCompleteLayoutItemViewHolder(layoutBinding) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/viewmodel/MainActivityViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.viewmodel 2 | 3 | import android.net.wifi.p2p.WifiP2pDevice 4 | import android.net.wifi.p2p.WifiP2pInfo 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.ViewModel 8 | import com.salesground.zipbolt.model.DataToTransfer 9 | import com.salesground.zipbolt.model.ui.PeerConnectionUIState 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class MainActivityViewModel @Inject constructor() : ViewModel() { 15 | 16 | private var hasBeenNotifiedAboutReceive: Boolean = false 17 | private val _peerConnectionUIState = 18 | MutableLiveData(PeerConnectionUIState.NoConnectionUIAction) 19 | val peerConnectionUIState: LiveData 20 | get() = _peerConnectionUIState 21 | 22 | fun peerConnectionNoAction() { 23 | _peerConnectionUIState.value = PeerConnectionUIState.NoConnectionUIAction 24 | } 25 | 26 | fun collapsedConnectedToPeerTransferOngoing() { 27 | _peerConnectionUIState.value = 28 | PeerConnectionUIState.CollapsedConnectedToPeerTransferOngoing 29 | } 30 | 31 | 32 | fun expandedConnectedToPeerReceiveOngoing() { 33 | if (!hasBeenNotifiedAboutReceive) { 34 | expandedConnectedToPeerTransferOngoing() 35 | hasBeenNotifiedAboutReceive = true 36 | } 37 | } 38 | 39 | fun expandedConnectedToPeerTransferOngoing() { 40 | _peerConnectionUIState.value = 41 | PeerConnectionUIState.ExpandedConnectedToPeerTransferOngoing 42 | } 43 | 44 | fun totalFileReceiveComplete() { 45 | hasBeenNotifiedAboutReceive = false 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/communication/MediaTransferProtocol.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.communication 2 | 3 | import android.net.Uri 4 | import com.salesground.zipbolt.model.DataToTransfer 5 | import java.io.DataInputStream 6 | import java.io.DataOutputStream 7 | 8 | interface MediaTransferProtocol { 9 | 10 | enum class MediaTransferProtocolMetaData(val value: Int) { 11 | NO_DATA(200), 12 | DATA_AVAILABLE(201), 13 | CANCEL_ON_GOING_TRANSFER(203), 14 | 15 | CANCEL_ACTIVE_RECEIVE(204), 16 | CANCEL_ACTIVE_TRANSFER(205), 17 | KEEP_RECEIVING(206), 18 | KEEP_RECEIVING_BUT_CANCEL_ACTIVE_TRANSFER(207), 19 | PAUSE_ACTIVE_TRANSFER(208) 20 | } 21 | 22 | fun cancelCurrentTransfer(transferMetaData: MediaTransferProtocolMetaData) 23 | 24 | suspend fun transferMedia( 25 | dataToTransfer: DataToTransfer, 26 | dataOutputStream: DataOutputStream, 27 | dataTransferListener: DataTransferListener 28 | ) 29 | 30 | 31 | suspend fun receiveMedia( 32 | dataInputStream: DataInputStream, 33 | dataReceiveListener: DataReceiveListener 34 | ) 35 | 36 | interface DataTransferListener { 37 | fun onTransfer( 38 | dataToTransfer: DataToTransfer, 39 | percentTransferred: Float, 40 | transferStatus: DataToTransfer.TransferStatus 41 | ) 42 | } 43 | 44 | interface DataReceiveListener { 45 | fun onReceive( 46 | dataDisplayName: String, dataSize: Long, percentageOfDataRead: Float, dataType: Int, 47 | dataUri: Uri?, dataTransferStatus: DataToTransfer.TransferStatus 48 | ) 49 | } 50 | 51 | interface TransferMetaDataUpdateListener { 52 | fun onMetaTransferDataUpdate(mediaTransferProtocolMetaData: MediaTransferProtocolMetaData) 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/video/VideoTransferWaitingLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.video 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.salesground.zipbolt.R 9 | import com.salesground.zipbolt.databinding.VideoTransferLayoutItemBinding 10 | import com.salesground.zipbolt.model.DataToTransfer 11 | 12 | class VideoTransferWaitingLayoutItemViewHolder( 13 | private val videoTransferLayoutItemBinding: VideoTransferLayoutItemBinding 14 | ) : RecyclerView.ViewHolder(videoTransferLayoutItemBinding.root) { 15 | 16 | fun bindData(dataToTransfer: DataToTransfer) { 17 | dataToTransfer as DataToTransfer.DeviceVideo 18 | videoTransferLayoutItemBinding.run { 19 | videoSize = dataToTransfer.videoSize 20 | videoDuration = dataToTransfer.videoDuration 21 | videoName = dataToTransfer.videoDisplayName 22 | 23 | Glide.with(videoTransferLayoutItemVideoPreviewImageView) 24 | .load(dataToTransfer.dataUri) 25 | .into(videoTransferLayoutItemVideoPreviewImageView) 26 | } 27 | } 28 | 29 | companion object { 30 | fun createViewHolder(parent: ViewGroup): VideoTransferWaitingLayoutItemViewHolder { 31 | val layoutBinding = DataBindingUtil.inflate( 32 | LayoutInflater.from(parent.context), 33 | R.layout.video_transfer_layout_item, 34 | parent, 35 | false 36 | ) 37 | return VideoTransferWaitingLayoutItemViewHolder(layoutBinding) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/receivedDataFragment/viewHolders/plainFile/video/PlainVideoFileReceiveCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.receivedDataFragment.viewHolders.plainFile.video 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.salesground.zipbolt.R 8 | import com.salesground.zipbolt.databinding.PlainVideoFileTransferLayoutItemBinding 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | import com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.plainFile.video.PlainVideoFileTransferCompleteLayoutItemViewHolder 11 | 12 | class PlainVideoFileReceiveCompleteLayoutItemViewHolder( 13 | private val plainVideoFileTransferLayoutItemBinding: PlainVideoFileTransferLayoutItemBinding 14 | ) : RecyclerView.ViewHolder(plainVideoFileTransferLayoutItemBinding.root) { 15 | 16 | fun bindData(dataToTransfer: DataToTransfer) { 17 | plainVideoFileTransferLayoutItemBinding.run { 18 | this.document = dataToTransfer as DataToTransfer.DeviceFile 19 | plainVideoFileTransferLayoutItemShimmer.run { 20 | stopShimmer() 21 | hideShimmer() 22 | } 23 | } 24 | } 25 | 26 | companion object { 27 | fun createViewHolder(parent: ViewGroup): PlainVideoFileReceiveCompleteLayoutItemViewHolder { 28 | val layoutBinding = DataBindingUtil.inflate( 29 | LayoutInflater.from(parent.context), 30 | R.layout.plain_video_file_transfer_layout_item, 31 | parent, 32 | false 33 | ) 34 | return PlainVideoFileReceiveCompleteLayoutItemViewHolder(layoutBinding) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/audio/AudioTransferCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.audio 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.salesground.zipbolt.R 9 | import com.salesground.zipbolt.databinding.AudioTransferLayoutItemBinding 10 | import com.salesground.zipbolt.model.DataToTransfer 11 | 12 | class AudioTransferCompleteLayoutItemViewHolder( 13 | private val audioTransferLayoutItemBinding: AudioTransferLayoutItemBinding 14 | ) : RecyclerView.ViewHolder(audioTransferLayoutItemBinding.root) { 15 | 16 | fun bindData(dataToTransfer: DataToTransfer) { 17 | dataToTransfer as DataToTransfer.DeviceAudio 18 | audioTransferLayoutItemBinding.run { 19 | this.dataToTransfer = dataToTransfer 20 | 21 | audioTransferLayoutItemShimmer.run { 22 | stopShimmer() 23 | hideShimmer() 24 | } 25 | 26 | Glide.with(audioTransferLayoutItemVideoPreviewImageView) 27 | .load(dataToTransfer.audioArtPath) 28 | .error(R.drawable.ic_icons8_music) 29 | .into(audioTransferLayoutItemVideoPreviewImageView) 30 | 31 | } 32 | } 33 | 34 | companion object { 35 | fun createViewHolder(parent: ViewGroup): AudioTransferCompleteLayoutItemViewHolder { 36 | val layoutBinding = DataBindingUtil.inflate( 37 | LayoutInflater.from(parent.context), 38 | R.layout.audio_transfer_layout_item, 39 | parent, 40 | false 41 | ) 42 | return AudioTransferCompleteLayoutItemViewHolder(layoutBinding) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/receivedDataFragment/viewHolders/audio/AudioReceiveCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.receivedDataFragment.viewHolders.audio 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.salesground.zipbolt.R 9 | import com.salesground.zipbolt.databinding.AudioTransferLayoutItemBinding 10 | import com.salesground.zipbolt.model.DataToTransfer 11 | 12 | class AudioReceiveCompleteLayoutItemViewHolder( 13 | private val audioTransferLayoutItemBinding: AudioTransferLayoutItemBinding 14 | ) : RecyclerView.ViewHolder(audioTransferLayoutItemBinding.root) { 15 | 16 | fun bindData(dataToTransfer: DataToTransfer) { 17 | dataToTransfer as DataToTransfer.DeviceAudio 18 | audioTransferLayoutItemBinding.run { 19 | this.dataToTransfer = dataToTransfer 20 | 21 | audioTransferLayoutItemShimmer.run { 22 | stopShimmer() 23 | hideShimmer() 24 | } 25 | 26 | Glide.with(audioTransferLayoutItemVideoPreviewImageView) 27 | .load(dataToTransfer.audioArtPath) 28 | .error(R.drawable.ic_icons8_music) 29 | .into(audioTransferLayoutItemVideoPreviewImageView) 30 | 31 | } 32 | } 33 | 34 | companion object { 35 | fun createViewHolder(parent: ViewGroup): AudioReceiveCompleteLayoutItemViewHolder { 36 | val layoutBinding = DataBindingUtil.inflate( 37 | LayoutInflater.from(parent.context), 38 | R.layout.audio_transfer_layout_item, 39 | parent, 40 | false 41 | ) 42 | return AudioReceiveCompleteLayoutItemViewHolder(layoutBinding) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 15 | 16 | 19 | 20 | 23 | 24 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/viewmodel/DataToTransferViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.salesground.zipbolt.model.DataToTransfer 7 | import com.salesground.zipbolt.utils.SingleLiveDataEventForUIState 8 | 9 | class DataToTransferViewModel : ViewModel() { 10 | 11 | private val _dropAllSelectedItem = MutableLiveData>() 12 | val dropAllSelectedItem: LiveData> 13 | get() = _dropAllSelectedItem 14 | 15 | private var _collectionOfDataToTransferLiveData = MutableLiveData>( 16 | mutableListOf() 17 | ) 18 | val collectionOfDataToTransferLiveData: LiveData> 19 | get() = _collectionOfDataToTransferLiveData 20 | 21 | private var _collectionOfDataToTransfer: MutableList = mutableListOf() 22 | val collectionOfDataToTransfer: MutableList 23 | get() = _collectionOfDataToTransfer 24 | 25 | fun addDataToTransfer(dataToTransfer: DataToTransfer) { 26 | collectionOfDataToTransfer.add(dataToTransfer) 27 | _collectionOfDataToTransferLiveData.value = collectionOfDataToTransfer 28 | } 29 | 30 | fun removeDataFromDataToTransfer(dataToTransfer: DataToTransfer) { 31 | collectionOfDataToTransfer.remove(dataToTransfer) 32 | _collectionOfDataToTransferLiveData.value = collectionOfDataToTransfer 33 | } 34 | 35 | private fun clearCollectionOfDataToTransfer() { 36 | _collectionOfDataToTransfer = mutableListOf() 37 | _collectionOfDataToTransferLiveData.value = collectionOfDataToTransfer 38 | } 39 | 40 | fun dropAllSelectedItems() { 41 | clearCollectionOfDataToTransfer() 42 | _dropAllSelectedItem.value = SingleLiveDataEventForUIState(true) 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/video/VideoTransferCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.video 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.salesground.zipbolt.R 9 | import com.salesground.zipbolt.databinding.VideoTransferLayoutItemBinding 10 | import com.salesground.zipbolt.model.DataToTransfer 11 | 12 | class VideoTransferCompleteLayoutItemViewHolder( 13 | private val videoTransferLayoutItemBinding: VideoTransferLayoutItemBinding 14 | ) : RecyclerView.ViewHolder(videoTransferLayoutItemBinding.root) { 15 | 16 | fun bindData(dataToTransfer: DataToTransfer) { 17 | dataToTransfer as DataToTransfer.DeviceVideo 18 | videoTransferLayoutItemBinding.run { 19 | videoSize = dataToTransfer.videoSize 20 | videoDuration = dataToTransfer.videoDuration 21 | videoName = dataToTransfer.videoDisplayName 22 | 23 | videoTransferLayoutItemShimmer.run { 24 | stopShimmer() 25 | hideShimmer() 26 | } 27 | 28 | Glide.with(videoTransferLayoutItemVideoPreviewImageView) 29 | .load(dataToTransfer.dataUri) 30 | .into(videoTransferLayoutItemVideoPreviewImageView) 31 | } 32 | } 33 | 34 | companion object { 35 | fun createViewHolder(parent: ViewGroup): VideoTransferCompleteLayoutItemViewHolder { 36 | val layoutBinding = DataBindingUtil.inflate( 37 | LayoutInflater.from(parent.context), 38 | R.layout.video_transfer_layout_item, 39 | parent, 40 | false 41 | ) 42 | return VideoTransferCompleteLayoutItemViewHolder(layoutBinding) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/receivedDataFragment/viewHolders/video/VideoReceiveCompleteLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.ongoingDataTransferRecyclerViewComponents.viewHolders.video 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.salesground.zipbolt.R 9 | import com.salesground.zipbolt.databinding.VideoTransferLayoutItemBinding 10 | import com.salesground.zipbolt.model.DataToTransfer 11 | 12 | class VideoReceiveCompleteLayoutItemViewHolder( 13 | private val videoTransferLayoutItemBinding: VideoTransferLayoutItemBinding 14 | ) : RecyclerView.ViewHolder(videoTransferLayoutItemBinding.root) { 15 | 16 | fun bindData(dataToTransfer: DataToTransfer) { 17 | dataToTransfer as DataToTransfer.DeviceVideo 18 | videoTransferLayoutItemBinding.run { 19 | videoSize = dataToTransfer.videoSize 20 | videoDuration = dataToTransfer.videoDuration 21 | videoName = dataToTransfer.videoDisplayName 22 | 23 | videoTransferLayoutItemShimmer.run { 24 | stopShimmer() 25 | hideShimmer() 26 | } 27 | 28 | Glide.with(videoTransferLayoutItemVideoPreviewImageView) 29 | .load(dataToTransfer.dataUri) 30 | .into(videoTransferLayoutItemVideoPreviewImageView) 31 | } 32 | } 33 | 34 | companion object { 35 | fun createViewHolder(parent: ViewGroup): VideoReceiveCompleteLayoutItemViewHolder { 36 | val layoutBinding = DataBindingUtil.inflate( 37 | LayoutInflater.from(parent.context), 38 | R.layout.video_transfer_layout_item, 39 | parent, 40 | false 41 | ) 42 | return VideoReceiveCompleteLayoutItemViewHolder(layoutBinding) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dat_file_logo.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/folder_layout_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 19 | 20 | 26 | 27 | 28 | 36 | 37 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/text_file_icon.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/peersDiscoveryFragment/DiscoveredPeerLayoutItemViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.peersDiscoveryFragment 2 | 3 | import android.net.wifi.p2p.WifiP2pDevice 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.databinding.DataBindingUtil 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.salesground.zipbolt.R 9 | import com.salesground.zipbolt.databinding.DiscoveredPeerLayoutItemBinding 10 | import com.salesground.zipbolt.ui.recyclerview.RecyclerViewItemClickedListener 11 | 12 | class DiscoveredPeerLayoutItemViewHolder( 13 | private val discoveredPeerLayoutItemBinding: DiscoveredPeerLayoutItemBinding, 14 | private val deviceClickedListener: RecyclerViewItemClickedListener 15 | ) : RecyclerView.ViewHolder(discoveredPeerLayoutItemBinding.root) { 16 | 17 | fun bindData(device: WifiP2pDevice) { 18 | discoveredPeerLayoutItemBinding.run { 19 | deviceName = if (device.deviceName.isNotBlank()) { 20 | device.deviceName 21 | } else { 22 | "Unspecified Device" 23 | } 24 | discoveredPeerLayoutItemViewGroup.setOnClickListener { 25 | deviceClickedListener.onClick(device) 26 | } 27 | 28 | executePendingBindings() 29 | } 30 | } 31 | 32 | companion object { 33 | fun createViewHolder( 34 | parent: ViewGroup, 35 | deviceClickedListener: RecyclerViewItemClickedListener 36 | ): DiscoveredPeerLayoutItemViewHolder { 37 | val layoutBinding = DataBindingUtil.inflate< 38 | DiscoveredPeerLayoutItemBinding>( 39 | LayoutInflater.from(parent.context), 40 | R.layout.discovered_peer_layout_item, 41 | parent, 42 | false 43 | ) 44 | return DiscoveredPeerLayoutItemViewHolder( 45 | layoutBinding, 46 | deviceClickedListener 47 | ) 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/application/ApplicationTransferWaitingViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.application 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.salesground.zipbolt.R 9 | import com.salesground.zipbolt.databinding.ApplicationLayoutItemTransferOrReceiveBinding 10 | import com.salesground.zipbolt.model.DataToTransfer 11 | import com.salesground.zipbolt.utils.transformDataSizeToMeasuredUnit 12 | 13 | class ApplicationTransferWaitingViewHolder( 14 | private val applicationLayoutItemTransferOrReceiveBinding: ApplicationLayoutItemTransferOrReceiveBinding 15 | ) : RecyclerView.ViewHolder(applicationLayoutItemTransferOrReceiveBinding.root) { 16 | 17 | fun bindData(dataToTransfer: DataToTransfer) { 18 | dataToTransfer as DataToTransfer.DeviceApplication 19 | 20 | applicationLayoutItemTransferOrReceiveBinding.run { 21 | applicationName = dataToTransfer.dataDisplayName 22 | applicationSize = dataToTransfer.dataSize.transformDataSizeToMeasuredUnit() 23 | 24 | Glide.with(applicationLayoutItemTransferOrReceiveImageView) 25 | .load(dataToTransfer.applicationIcon) 26 | .into(applicationLayoutItemTransferOrReceiveImageView) 27 | 28 | executePendingBindings() 29 | } 30 | } 31 | 32 | companion object { 33 | fun createViewHolder(parent: ViewGroup): ApplicationTransferWaitingViewHolder { 34 | val layoutBinding = 35 | DataBindingUtil.inflate( 36 | LayoutInflater.from(parent.context), 37 | R.layout.application_layout_item_transfer_or_receive, 38 | parent, 39 | false 40 | ) 41 | return ApplicationTransferWaitingViewHolder(layoutBinding) 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/expanded_bottom_sheet_layout_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 30 | 31 | 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/customviews/SelectableLinearLayout.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.customviews 2 | 3 | import android.animation.AnimatorSet 4 | import android.animation.ValueAnimator 5 | import android.content.Context 6 | import android.graphics.Canvas 7 | import android.graphics.CornerPathEffect 8 | import android.graphics.Paint 9 | import android.graphics.RectF 10 | import android.util.AttributeSet 11 | import androidx.appcompat.widget.LinearLayoutCompat 12 | import androidx.core.content.ContextCompat 13 | import com.google.android.material.animation.AnimatorSetCompat 14 | import com.salesground.zipbolt.R 15 | 16 | class SelectableLinearLayout @JvmOverloads constructor( 17 | context: Context, attrs: AttributeSet? = null 18 | ) : LinearLayoutCompat(context, attrs) { 19 | private var isViewSelected = false 20 | private val cornerRect = RectF() 21 | private val cornerRectFillPaint = Paint().apply { 22 | style = Paint.Style.FILL 23 | isAntiAlias = true 24 | isDither = true 25 | color = ContextCompat.getColor(context, R.color.blue_415_15_percent_alpha) 26 | } 27 | 28 | override fun dispatchDraw(canvas: Canvas?) { 29 | super.dispatchDraw(canvas) 30 | if (isViewSelected) { 31 | canvas?.let { 32 | drawScrim(it) 33 | } 34 | } 35 | } 36 | 37 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 38 | super.onSizeChanged(w, h, oldw, oldh) 39 | cornerRect.apply { 40 | left = 0f 41 | top = 0f 42 | right = w.toFloat() 43 | bottom = h.toFloat() 44 | } 45 | } 46 | 47 | private fun drawScrim(canvas: Canvas) { 48 | canvas.drawRect( 49 | cornerRect, 50 | cornerRectFillPaint 51 | ) 52 | } 53 | 54 | 55 | fun setIsViewSelected(selected: Boolean) { 56 | if (isViewSelected && !selected) { 57 | isViewSelected = selected 58 | invalidate() 59 | } else if (!isViewSelected && selected) { 60 | isViewSelected = selected 61 | invalidate() 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/wifi_p2p_device_item_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 14 | 15 | 28 | 29 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/salesground/zipbolt/repository/DeviceApplicationsRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | import com.salesground.zipbolt.repository.implementation.DeviceApplicationsRepository 2 | 3 | 4 | import android.content.Context 5 | import android.content.pm.ApplicationInfo 6 | import android.os.Build 7 | import androidx.test.core.app.ApplicationProvider 8 | import com.salesground.zipbolt.repository.ZipBoltSavedFilesRepository 9 | import kotlinx.coroutines.flow.collect 10 | import kotlinx.coroutines.runBlocking 11 | import org.junit.Before 12 | 13 | import org.junit.Assert.* 14 | import org.junit.Test 15 | 16 | class DeviceApplicationsRepositoryTest { 17 | 18 | private val applicationContext = ApplicationProvider.getApplicationContext() 19 | private lateinit var deviceApplicationsRepository: DeviceApplicationsRepository 20 | 21 | @Before 22 | fun setUp() { 23 | deviceApplicationsRepository = DeviceApplicationsRepository(applicationContext, 24 | ZipBoltSavedFilesRepository()) 25 | } 26 | 27 | // confirm that the mutable list allAppsOnDevice holds items 28 | @Test 29 | fun test_getAllAppsOnDevice_returnsAListOfApps() { 30 | runBlocking { 31 | val allAppsOnDevice = deviceApplicationsRepository.getAllAppsOnDevice() 32 | assertTrue(allAppsOnDevice.isNotEmpty()) 33 | } 34 | } 35 | 36 | @Test 37 | fun test_getAllAppsOnDevice_returnsOnlyAppsInstalledByTheUserInAndroidVersionGreaterThan8() { 38 | runBlocking { 39 | val allAppsOnDevice = deviceApplicationsRepository.getAllAppsOnDevice() 40 | 41 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 42 | allAppsOnDevice.forEach { 43 | assertNotEquals(it.flags, ApplicationInfo.FLAG_SYSTEM) 44 | } 45 | } 46 | } 47 | } 48 | 49 | // confirm that all apps on device has an icon 50 | @Test 51 | fun test_thatAllAppsOnDeviceHasAnIcon() { runBlocking { 52 | val allAppsOnDevice = deviceApplicationsRepository.getAllAppsOnDevice() 53 | 54 | allAppsOnDevice.forEach { 55 | assertNotNull(it.loadIcon(applicationContext.packageManager)) 56 | } 57 | } 58 | } 59 | 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/home_screen_recyclerview_layout_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 16 | 17 | 22 | 23 | 34 | 35 | 44 | 45 | 46 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/sentDataFragment/viewHolders/application/ApplicationTransferCompleteLayoutViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.sentDataFragment.viewHolders.application 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.salesground.zipbolt.R 9 | import com.salesground.zipbolt.databinding.ApplicationLayoutItemTransferOrReceiveBinding 10 | import com.salesground.zipbolt.model.DataToTransfer 11 | import com.salesground.zipbolt.utils.transformDataSizeToMeasuredUnit 12 | 13 | class ApplicationTransferCompleteLayoutViewHolder( 14 | private val applicationLayoutItemTransferOrReceiveBinding: ApplicationLayoutItemTransferOrReceiveBinding 15 | ) : RecyclerView.ViewHolder(applicationLayoutItemTransferOrReceiveBinding.root) { 16 | 17 | fun bindData(dataToTransfer: DataToTransfer) { 18 | dataToTransfer as DataToTransfer.DeviceApplication 19 | 20 | applicationLayoutItemTransferOrReceiveBinding.run { 21 | applicationName = dataToTransfer.dataDisplayName 22 | applicationSize = dataToTransfer.dataSize.transformDataSizeToMeasuredUnit() 23 | 24 | Glide.with(applicationLayoutItemTransferOrReceiveImageView) 25 | .load(dataToTransfer.applicationIcon) 26 | .into(applicationLayoutItemTransferOrReceiveImageView) 27 | 28 | applicationLayoutItemTransferOrReceiveShimmer.run { 29 | stopShimmer() 30 | hideShimmer() 31 | } 32 | executePendingBindings() 33 | } 34 | } 35 | 36 | companion object { 37 | fun createViewHolder(parent: ViewGroup): ApplicationTransferCompleteLayoutViewHolder { 38 | val layoutBinding = 39 | DataBindingUtil.inflate( 40 | LayoutInflater.from(parent.context), 41 | R.layout.application_layout_item_transfer_or_receive, 42 | parent, 43 | false 44 | ) 45 | return ApplicationTransferCompleteLayoutViewHolder(layoutBinding) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/res/values-v23/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 26 | 27 | 30 | 31 | 34 | 35 | 38 | 39 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/application_layout_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 33 | 34 | 45 | 46 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/recyclerview/receivedDataFragment/viewHolders/application/ApplicationReceiveCompleteLayoutViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.recyclerview.ongoingDataTransferRecyclerViewComponents.viewHolders.application 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.salesground.zipbolt.R 9 | import com.salesground.zipbolt.databinding.ApplicationLayoutItemTransferOrReceiveBinding 10 | import com.salesground.zipbolt.databinding.ApplicationReceiveCompleteLayoutItemBinding 11 | import com.salesground.zipbolt.model.DataToTransfer 12 | import com.salesground.zipbolt.ui.recyclerview.RecyclerViewItemClickedListener 13 | import com.salesground.zipbolt.utils.transformDataSizeToMeasuredUnit 14 | 15 | class ApplicationReceiveCompleteLayoutViewHolder( 16 | private val applicationReceiveCompleteLayoutItemBinding: ApplicationReceiveCompleteLayoutItemBinding 17 | ) : RecyclerView.ViewHolder(applicationReceiveCompleteLayoutItemBinding.root) { 18 | 19 | fun bindData(dataToTransfer: DataToTransfer) { 20 | dataToTransfer as DataToTransfer.DeviceApplication 21 | 22 | applicationReceiveCompleteLayoutItemBinding.run { 23 | applicationName = dataToTransfer.dataDisplayName 24 | applicationSize = dataToTransfer.dataSize.transformDataSizeToMeasuredUnit() 25 | 26 | Glide.with(applicationReceiveLayoutItemImageView) 27 | .load(dataToTransfer.applicationIcon) 28 | .into(applicationReceiveLayoutItemImageView) 29 | executePendingBindings() 30 | } 31 | } 32 | 33 | companion object { 34 | fun createViewHolder( 35 | parent: ViewGroup 36 | ): ApplicationReceiveCompleteLayoutViewHolder { 37 | val layoutBinding = 38 | DataBindingUtil.inflate( 39 | LayoutInflater.from(parent.context), 40 | R.layout.application_receive_complete_layout_item, 41 | parent, 42 | false 43 | ) 44 | return ApplicationReceiveCompleteLayoutViewHolder( 45 | layoutBinding 46 | ) 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/salesground/zipbolt/repository/ZipBoltImageRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.repository 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import android.os.ParcelFileDescriptor 6 | import android.util.Log 7 | import androidx.test.core.app.ApplicationProvider 8 | import com.bumptech.glide.Glide 9 | import com.salesground.zipbolt.model.DataToTransfer 10 | import com.salesground.zipbolt.repository.implementation.ZipBoltImageRepository 11 | import dagger.hilt.android.testing.HiltAndroidRule 12 | import dagger.hilt.android.testing.HiltAndroidTest 13 | import junit.framework.Assert.assertEquals 14 | import org.junit.Before 15 | import org.junit.Rule 16 | import org.junit.Test 17 | import javax.inject.Inject 18 | import kotlinx.coroutines.* 19 | import java.io.DataInputStream 20 | import java.io.FileInputStream 21 | 22 | @HiltAndroidTest 23 | class ZipBoltImageRepositoryTest { 24 | 25 | @get:Rule 26 | var hiltRule = HiltAndroidRule(this) 27 | 28 | @Inject 29 | lateinit var zipBoltImageRepository: ZipBoltImageRepository 30 | 31 | private val context = ApplicationProvider.getApplicationContext() 32 | 33 | @Before 34 | fun setUp() { 35 | hiltRule.inject() 36 | } 37 | 38 | @Test 39 | fun test_getImagesOnDevice() = runBlocking { 40 | val allImagesOnDevice = zipBoltImageRepository.getImagesOnDevice() 41 | assert(allImagesOnDevice.isNotEmpty()) 42 | val deviceImage = allImagesOnDevice[1] as DataToTransfer.DeviceImage 43 | assert(deviceImage.imageBucketName.isNotBlank()) 44 | val firstTenImagesOnDevice = zipBoltImageRepository.getImagesOnDevice(limit = 10) 45 | assert(firstTenImagesOnDevice.size <= 10) 46 | } 47 | 48 | @Test 49 | fun test_getMetaDataOfImage() = runBlocking { 50 | var firstImage = zipBoltImageRepository.getImagesOnDevice().first() as DataToTransfer.DeviceImage 51 | assert(firstImage.imageMimeType.contains("image")) 52 | assert(firstImage.imageSize > 10) 53 | assert(firstImage.imageDisplayName != "") 54 | } 55 | 56 | @Test 57 | fun test_imageUriHoldsADrawable() = runBlocking { 58 | var firstImage = zipBoltImageRepository.getImagesOnDevice().first() as DataToTransfer.DeviceImage 59 | assert(Glide.with(context).load(firstImage.imageUri).submit().get() != null) 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/customviews/AnimatedLoadingTextView.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.customviews 2 | 3 | import android.animation.ValueAnimator 4 | import android.animation.ValueAnimator.* 5 | import android.content.Context 6 | import android.graphics.Canvas 7 | import android.util.AttributeSet 8 | import android.util.Log 9 | import android.view.View 10 | import com.google.android.material.textview.MaterialTextView 11 | 12 | class AnimatedLoadingTextView @JvmOverloads constructor( 13 | context: Context, attrs: AttributeSet? = null 14 | ) : MaterialTextView(context, attrs) { 15 | private var initialTextSize: Int = 0 16 | private var initialText: String = text.toString() 17 | private var threeDots: String = "$initialText..." 18 | private var twoDots: String = "$initialText.." 19 | private var oneDot: String = "$initialText." 20 | private var textAnimator: ValueAnimator? = null 21 | 22 | init { 23 | animateText() 24 | if (visibility == View.INVISIBLE) { 25 | textAnimator?.cancel() 26 | } 27 | } 28 | 29 | override fun onDraw(canvas: Canvas?) { 30 | super.onDraw(canvas) 31 | } 32 | 33 | override fun onVisibilityChanged(changedView: View, visibility: Int) { 34 | super.onVisibilityChanged(changedView, visibility) 35 | if (visibility != View.INVISIBLE) { 36 | textAnimator?.start() 37 | } 38 | } 39 | 40 | fun setAnimatedText(newText: String) { 41 | initialText = newText 42 | threeDots = "$initialText..." 43 | twoDots = "$initialText.." 44 | oneDot = "$initialText." 45 | } 46 | 47 | 48 | private fun animateText() { 49 | initialTextSize = text.length 50 | textAnimator = ofInt(0, 4).apply { 51 | repeatMode = REVERSE 52 | repeatCount = INFINITE 53 | duration = 2500 54 | start() 55 | } 56 | textAnimator?.addUpdateListener { 57 | text = when (it.animatedValue) { 58 | 0 -> { 59 | initialText 60 | } 61 | 1 -> { 62 | oneDot 63 | } 64 | 2 -> { 65 | twoDots 66 | } 67 | 3 -> { 68 | threeDots 69 | } 70 | else -> { 71 | threeDots 72 | } 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/multi_colored_apple_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/communication/ZipBoltMTP.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.communication 2 | 3 | import android.content.Context 4 | import android.os.ParcelFileDescriptor 5 | import com.salesground.zipbolt.model.MediaModel 6 | import com.salesground.zipbolt.repository.ZipBoltSavedFilesRepository 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import java.io.* 9 | import javax.inject.Inject 10 | 11 | class ZipBoltMTP @Inject constructor ( 12 | @ApplicationContext private val context: Context, 13 | private val zipBoltSavedFilesRepository: ZipBoltSavedFilesRepository) { 14 | 15 | fun transferMedia(mediaItems: MutableList, DOS: DataOutputStream) { 16 | DOS.writeInt(mediaItems.size) 17 | mediaItems.forEach { mediaModel: MediaModel -> 18 | DOS.writeUTF(mediaModel.mediaDisplayName) 19 | DOS.writeLong(mediaModel.mediaSize) 20 | DOS.writeUTF(mediaModel.mimeType) 21 | context.contentResolver.openFileDescriptor(mediaModel.mediaUri, "r").apply { 22 | this?.let { parcelFileDescriptor: ParcelFileDescriptor -> 23 | val mediaModelFileInputStream = 24 | FileInputStream(parcelFileDescriptor.fileDescriptor) 25 | val bufferArray = ByteArray(10_000_000) 26 | var lengthRead: Int 27 | 28 | while (mediaModelFileInputStream.read(bufferArray) 29 | .also { lengthRead = it } > 0 30 | ) { 31 | DOS.write(bufferArray, 0, lengthRead) 32 | } 33 | mediaModelFileInputStream.close() 34 | } 35 | } 36 | 37 | } 38 | } 39 | 40 | fun receiveMedia(DIS: DataInputStream) { 41 | val numberOfItemsSent = DIS.readInt() 42 | // Log.i("NewTransfer", "Received $numberOfItemsSent images") 43 | for (i in 0 until numberOfItemsSent) { 44 | val mediaName = DIS.readUTF() 45 | var mediaSize = DIS.readLong() 46 | val mediaType = DIS.readUTF() 47 | 48 | // read media bytes and save it into the media store based on the mime type 49 | when { 50 | mediaType.contains("image" , true) -> { 51 | 52 | } 53 | mediaType.contains("video", true) -> { 54 | 55 | } 56 | mediaType.contains("audio", true) -> { 57 | 58 | } 59 | } 60 | 61 | } 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/customviews/DividerLabel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.customviews 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.Paint 7 | import android.graphics.Rect 8 | import android.os.Build 9 | import android.util.AttributeSet 10 | import android.util.Log 11 | import androidx.annotation.RequiresApi 12 | import androidx.compose.ui.geometry.Offset 13 | import com.google.android.material.textview.MaterialTextView 14 | import com.salesground.zipbolt.R 15 | 16 | class DividerLabel @JvmOverloads constructor( 17 | context: Context, attrs: AttributeSet? = null 18 | ) : MaterialTextView(context, attrs) { 19 | 20 | private val dividerPaint: Paint 21 | 22 | init { 23 | context.theme.obtainStyledAttributes(R.styleable.DividerLabel).apply { 24 | dividerPaint = Paint().apply { 25 | style = Paint.Style.STROKE 26 | isAntiAlias = true 27 | isDither = true 28 | color = textColors.defaultColor 29 | strokeWidth = getFloat( 30 | R.styleable.DividerLabel_strokeLineHeight, 31 | 1f 32 | ) * context.resources.displayMetrics.scaledDensity 33 | strokeCap = Paint.Cap.ROUND 34 | strokeJoin = Paint.Join.ROUND 35 | } 36 | recycle() 37 | } 38 | textAlignment = TEXT_ALIGNMENT_CENTER 39 | 40 | } 41 | 42 | 43 | override fun onDraw(canvas: Canvas?) { 44 | super.onDraw(canvas) 45 | canvas?.let { 46 | drawDividerLine( 47 | canvas = it, 48 | viewHeight = measuredHeight, 49 | viewWidth = measuredWidth, 50 | writtenTextSize = paint.measureText(text.toString()) 51 | ) 52 | } 53 | } 54 | 55 | private fun drawDividerLine( 56 | canvas: Canvas, 57 | viewHeight: Int, viewWidth: Int, 58 | writtenTextSize: Float, 59 | ) { 60 | 61 | canvas.drawLine( 62 | paddingLeft.toFloat(), 63 | viewHeight * 0.5f, 64 | (viewWidth * 0.5f) - writtenTextSize, 65 | viewHeight * 0.5f, 66 | dividerPaint 67 | ) 68 | canvas.drawLine( 69 | (viewWidth * 0.5f) + writtenTextSize, 70 | viewHeight * 0.5f, (viewWidth - paddingRight).toFloat(), 71 | viewHeight * 0.5f, 72 | dividerPaint 73 | ) 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /app/src/main/res/font/work_sans_font_family.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 23 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 64 | 68 | 72 | 76 | 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/animationutils/AnimationUtils.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.animationutils 2 | 3 | import android.animation.AnimatorSet 4 | import android.animation.ValueAnimator 5 | import android.graphics.drawable.ColorDrawable 6 | import android.view.animation.AccelerateDecelerateInterpolator 7 | import android.widget.FrameLayout 8 | import android.widget.ImageView 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.graphics.toArgb 11 | 12 | 13 | fun ImageView.scaleUpAnimation( 14 | parent: FrameLayout, 15 | currentScaleValue: Float = 0.7f, 16 | currentParentForegroundColor: Int = Color.Blue.copy(0.12f).toArgb(), 17 | animationDuration: Long = 300 18 | ) { 19 | val scaleUpAnimation = ValueAnimator.ofFloat(currentScaleValue, 1f).apply { 20 | duration = animationDuration 21 | interpolator = AccelerateDecelerateInterpolator() 22 | } 23 | scaleUpAnimation.addUpdateListener { 24 | val currentScale = it.animatedValue as Float 25 | scaleX = currentScale 26 | scaleY = currentScale 27 | } 28 | val viewGroupColorChangeAnimation = 29 | ValueAnimator.ofArgb(currentParentForegroundColor, Color.Transparent.toArgb()).apply { 30 | duration = animationDuration 31 | } 32 | viewGroupColorChangeAnimation.addUpdateListener { 33 | parent.foreground = ColorDrawable(it.animatedValue as Int) 34 | } 35 | 36 | AnimatorSet().apply { 37 | playTogether(scaleUpAnimation, viewGroupColorChangeAnimation) 38 | start() 39 | } 40 | 41 | } 42 | 43 | fun ImageView.scaleDownAnimation( 44 | parent: FrameLayout, 45 | targetScaleValue: Float = 0.7f, 46 | targetParentForegroundValue: Int = Color.Blue.copy(0.12f).toArgb(), 47 | animationDuration: Long = 300 48 | ) { 49 | val scaleDownAnimation = ValueAnimator.ofFloat(1f, targetScaleValue).apply { 50 | duration = animationDuration 51 | interpolator = AccelerateDecelerateInterpolator() 52 | } 53 | scaleDownAnimation.addUpdateListener { 54 | val currentScale = it.animatedValue as Float 55 | scaleX = currentScale 56 | scaleY = currentScale 57 | } 58 | val viewGroupColorChangeAnimation = 59 | ValueAnimator.ofArgb(Color.Transparent.toArgb(), targetParentForegroundValue).apply { 60 | duration = animationDuration 61 | } 62 | viewGroupColorChangeAnimation.addUpdateListener { 63 | parent.foreground = ColorDrawable(it.animatedValue as Int) 64 | } 65 | AnimatorSet().apply { 66 | playTogether(scaleDownAnimation, viewGroupColorChangeAnimation) 67 | start() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/utils/TimeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.utils 2 | 3 | import android.content.Context 4 | import android.database.Cursor 5 | import android.net.Uri 6 | import android.provider.MediaStore 7 | import java.text.DateFormat 8 | import java.text.SimpleDateFormat 9 | import java.util.* 10 | 11 | val dateFormat = SimpleDateFormat("d MMMM, yyyy", Locale.UK) 12 | 13 | fun Long.parseDate(): String { 14 | return dateFormat.format(this).customizeDate() 15 | } 16 | 17 | private fun String.splitDate(): List { 18 | return split(" ", ignoreCase = true) 19 | } 20 | 21 | private fun String.customizeDate(): String { 22 | var day: String = this 23 | val splitDate = splitDate() 24 | val presentDate = dateFormat.format(System.currentTimeMillis()).splitDate() 25 | // check if the month and year is the same 26 | if (splitDate[1] == presentDate[1] && splitDate[2] == presentDate[2]) { 27 | when (presentDate[0].toInt() - splitDate[0].toInt()) { 28 | 0 -> day = "Today" 29 | 1 -> day = "Yesterday" 30 | 2 -> day = "Two days ago" 31 | } 32 | } 33 | return day 34 | } 35 | 36 | fun Long.formatVideoDurationToString(): String { 37 | val durationInSeconds = this / 1000 38 | val durationInMinutes = durationInSeconds / 60 39 | val remainingSeconds = durationInSeconds % 60 40 | 41 | return if (remainingSeconds > 0 && durationInMinutes > 0) { 42 | if (remainingSeconds < 30) { 43 | "${durationInMinutes}min" 44 | } else { 45 | "${durationInMinutes + 1}min" 46 | } 47 | } else if (durationInMinutes == 0L) { 48 | "${durationInSeconds}sec" 49 | } else { 50 | "${durationInMinutes}min" 51 | } 52 | } 53 | 54 | fun Uri.getVideoDuration(context: Context): Long { 55 | var videoDuration = 0L 56 | context.contentResolver.query( 57 | this, 58 | arrayOf(MediaStore.Video.Media.DURATION), 59 | null, 60 | null, 61 | null 62 | )?.let { cursor: Cursor -> 63 | cursor.moveToFirst() 64 | videoDuration = cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media.DURATION)) 65 | } 66 | 67 | return videoDuration 68 | } 69 | 70 | fun Uri.getAudioDuration(context: Context): Long { 71 | var audioDuration = 0L 72 | context.contentResolver.query( 73 | this, 74 | arrayOf(MediaStore.Audio.Media.DURATION), 75 | null, null, null 76 | )?.let { cursor: Cursor -> 77 | cursor.moveToFirst() 78 | audioDuration = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION)) 79 | } 80 | return audioDuration 81 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/zip_bolt_send_file_header_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 23 | 24 | 33 | 34 | 46 | 47 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/viewmodel/ReceivedDataViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.viewmodel 2 | 3 | import android.net.Uri 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import com.salesground.zipbolt.model.DataToTransfer 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.launch 11 | import java.io.File 12 | 13 | class ReceivedDataViewModel : ViewModel() { 14 | 15 | var currentReceiveDataToTransferItem: DataToTransfer = DataToTransfer.DeviceFile( 16 | File( 17 | "" 18 | ) 19 | ) 20 | private val receivedDataItemsNormalList: MutableList = mutableListOf() 21 | private val _receivedDataItems = MutableLiveData>(mutableListOf()) 22 | val receivedDataItems: LiveData> 23 | get() = _receivedDataItems 24 | 25 | private val _newReceivedItemPosition = MutableLiveData(-1) 26 | val newReceivedItemPosition: LiveData 27 | get() = _newReceivedItemPosition 28 | 29 | private val _ongoingDataReceivePercent = MutableLiveData(0f) 30 | val ongoingDataReceivePercent: LiveData 31 | get() = _ongoingDataReceivePercent 32 | 33 | private val _completedReceivedDataItem = MutableLiveData(null) 34 | val completedReceivedDataItem: LiveData 35 | get() = _completedReceivedDataItem 36 | 37 | private val _dataReceiveStartedDataItem = MutableLiveData(null) 38 | val dataReceiveStartedDataItem: LiveData 39 | get() = _dataReceiveStartedDataItem 40 | 41 | fun addDataToReceivedItems(dataToTransfer: DataToTransfer) { 42 | receivedDataItemsNormalList.add(dataToTransfer) 43 | viewModelScope.launch(Dispatchers.Main) { 44 | _completedReceivedDataItem.value = dataToTransfer 45 | _receivedDataItems.value = receivedDataItemsNormalList 46 | _newReceivedItemPosition.value = receivedDataItemsNormalList.size 47 | } 48 | } 49 | 50 | fun updateOngoingReceiveDataItemReceivePercent(receivePercent: Float) { 51 | viewModelScope.launch(Dispatchers.Main) { 52 | _ongoingDataReceivePercent.value = receivePercent 53 | } 54 | } 55 | 56 | fun onDataReceiveStarted(receivedDataItem: ReceivedDataItem) { 57 | viewModelScope.launch(Dispatchers.Main) { 58 | _dataReceiveStartedDataItem.value = receivedDataItem 59 | } 60 | } 61 | } 62 | 63 | data class ReceivedDataItem( 64 | val dataDisplayName: String, 65 | val dataSize: Long, 66 | val percentageOfDataRead: Float, 67 | val dataType: Int, 68 | val dataUri: Uri? 69 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/salesground/zipbolt/ui/customviews/SelectableConstraintLayout.kt: -------------------------------------------------------------------------------- 1 | package com.salesground.zipbolt.ui.customviews 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.CornerPathEffect 6 | import android.graphics.Paint 7 | import android.graphics.RectF 8 | import android.util.AttributeSet 9 | import androidx.appcompat.widget.LinearLayoutCompat 10 | import androidx.constraintlayout.widget.ConstraintLayout 11 | import androidx.core.content.ContextCompat 12 | import com.salesground.zipbolt.R 13 | 14 | class SelectableConstraintLayout @JvmOverloads constructor( 15 | context: Context, attrs: AttributeSet? = null 16 | ) : ConstraintLayout(context, attrs) { 17 | private var isViewSelected = false 18 | private val cornerRect = RectF() 19 | private val cornerRectRadius = 4 * resources.displayMetrics.density 20 | private val cornerRectStrokePaint = Paint().apply { 21 | style = Paint.Style.STROKE 22 | isAntiAlias = true 23 | isDither = true 24 | color = ContextCompat.getColor(context, R.color.blue_415) 25 | strokeWidth = 4 * resources.displayMetrics.density 26 | pathEffect = CornerPathEffect(cornerRectRadius) 27 | strokeJoin = Paint.Join.ROUND 28 | strokeCap = Paint.Cap.ROUND 29 | } 30 | 31 | private val cornerRectFillPaint = Paint().apply { 32 | style = Paint.Style.FILL 33 | isAntiAlias = true 34 | isDither = true 35 | color = ContextCompat.getColor(context, R.color.blue_415_15_percent_alpha) 36 | 37 | } 38 | 39 | override fun dispatchDraw(canvas: Canvas?) { 40 | super.dispatchDraw(canvas) 41 | if (isViewSelected) { 42 | canvas?.let { 43 | drawScrim(it) 44 | drawRoundedCorners(it) 45 | } 46 | } 47 | } 48 | 49 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 50 | super.onSizeChanged(w, h, oldw, oldh) 51 | cornerRect.apply { 52 | left = 0f 53 | top = 0f 54 | right = measuredWidth.toFloat() 55 | bottom = measuredHeight.toFloat() 56 | } 57 | } 58 | 59 | private fun drawScrim(canvas: Canvas) { 60 | canvas.drawRect( 61 | cornerRect, 62 | cornerRectFillPaint 63 | ) 64 | } 65 | 66 | private fun drawRoundedCorners(canvas: Canvas) { 67 | canvas.drawRect( 68 | cornerRect, 69 | cornerRectStrokePaint 70 | ) 71 | } 72 | 73 | fun setIsViewSelected(selected: Boolean) { 74 | if (isViewSelected && !selected) { 75 | isViewSelected = selected 76 | invalidate() 77 | } else if (!isViewSelected && selected) { 78 | isViewSelected = selected 79 | invalidate() 80 | } 81 | } 82 | 83 | } --------------------------------------------------------------------------------